Compare commits

...

160 Commits

Author SHA1 Message Date
Paweł Chmielowski 571a786b9b Change implementation of mod_offline use_mam_for_storage
Previous version was trying to determine range of messages that should
be fetched from mam by storing time when last user resource disconnected.

But that had couple edge cases that could cause problems, for example in
case of node crash we could not store information about user disconnect
and with that we didn't have data to initiate mam query.

New version don't track user disconnects, but simply ensure that we have
timestamp of first message that is gonna be put in storage, after some
measurements cost of that check with caching on top is not that costly,
and as much more robust i decided to introduce that change.
2019-05-28 14:32:17 +02:00
Evgeny Khramtsov 4eaba13189 Don't check mod_register restrictions in 'register' command
The commit reverts behaviour introduced in
1f2b8adc28
2019-05-28 15:14:45 +03:00
Holger Weiss 8b301fc93e mod_http_upload: Avoid catch-all error handling 2019-05-27 21:56:37 +02:00
Mickaël Rémond a06bdb1721 Improve captcha blocking alert wording 2019-05-25 11:30:04 +02:00
Mickaël Rémond b90fe4c5c9 Improve captcha.sh script documentation 2019-05-25 11:21:30 +02:00
Paweł Chmielowski 50f93023f5 Fix last commit 2019-05-24 15:18:15 +02:00
Paweł Chmielowski 226c09f031 Make mod_admin_extra add/delete_rosteritem reuse code from mod_roster 2019-05-24 14:02:17 +02:00
Mickaël Rémond dc126b86bb Add data clean up suggested command 2019-05-24 10:31:51 +02:00
Christophe Romain 44e1af25e5 Prepare 19.05 2019-05-23 13:42:50 +02:00
Christophe Romain 5b62a05205 Update deps in mix.lock 2019-05-23 13:40:05 +02:00
Paweł Chmielowski 5642338a73 Update deps 2019-05-23 12:13:28 +02:00
Evgeny Khramtsov 3f7a850ae8 Use different cache tables per auth module
Fixes #2322
2019-05-23 11:32:55 +03:00
Holger Weiss 729c8b0d24 Remove 'register' access rule from example config
The 'register' access rule isn't referenced from the 'mod_register'
options, so modifying it would have no effect.
2019-05-20 20:05:20 +02:00
Evgeny Khramtsov 4424f40186 Use lager 3.6.10 2019-05-20 12:10:23 +03:00
Evgeny Khramtsov 17f9ffb7e7 Merge branch 'master' of github.com:processone/ejabberd 2019-05-19 11:23:29 +03:00
Evgeny Khramtsov 2b523030cf Report better errors on SQL terms decode failure 2019-05-19 11:22:41 +03:00
Alexey Shchepin 63e9b82a46 Fix PostgreSQL compatibility in mod_offline_sql:remove_old_messages (#2695) 2019-05-18 21:16:45 +03:00
Badlop 937f07a4cc Fix typo in Change User Password adhoc command (thanks to lovetox)(#2884) 2019-05-17 15:48:03 +02:00
Badlop 2db547b557 Vcard search doesn't support * in mnesia, fix search form (#633) 2019-05-17 15:26:44 +02:00
Evgeny Khramtsov 0ed638c7fb Check hooks correctness in Travis CI 2019-05-15 21:47:39 +03:00
Evgeny Khramtsov 7c16e29984 Write hooks_type_test.erl to correct location 2019-05-15 21:45:30 +03:00
Evgeny Khramtsov 7a03a125aa Make static hooks analyzer working again 2019-05-15 21:40:36 +03:00
Evgeny Khramtsov 2aa181658a Fix mod_legacy_auth to reflect recent changes 2019-05-15 20:55:17 +03:00
Evgeny Khramtsov a4c3ea0dfb Don't process failed EXTERNAL authentication by mod_fail2ban
This will only lead to confusion because it's not considered
possible to brute force client certificates.
2019-05-15 18:13:31 +03:00
Evgeny Khramtsov 3c95764d1a Modify arguments of c2s_auth_result hook
The hook now accepts `true | {false, Reason :: binary()}` arguments
instead of just `true | false`
2019-05-15 17:21:09 +03:00
Badlop e996579dd1 Preliminary support for SQL in process_rosteritems, and move code (#2448) 2019-05-15 10:57:55 +02:00
Paweł Chmielowski 122cb4b959 Don't put duplicate polling attribute in bosh payload
This fixes issue #2790
2019-05-14 17:32:19 +02:00
Holger Weiss 1452023c93 mod_http_upload: Case-insensitive host comparison
Perform a case-insensitive lookup of the host name specified by the HTTP
client.

Fixes #2827.
2019-05-12 11:57:17 +02:00
Evgeny Khramtsov 3d8711f708 Avoid late arrival of get_disco_item response 2019-05-11 19:27:56 +03:00
Evgeny Khramtsov 4b6f1195c6 Handle TCP errors in websockets 2019-05-09 22:06:23 +03:00
Mickael Remond e427358e08 Initial Docker environment to run ejabberd test suite 2019-05-07 17:59:05 +02:00
Christophe Romain 2fff4d1ea6 Fix pubsub compliance XEP-0060 § 7.1.3.6 (#2864) 2019-05-07 16:23:36 +02:00
Mickael Remond 945c58d3db Merge branch 'master' of github.com:processone/ejabberd 2019-05-07 14:09:22 +02:00
Mickael Remond a04ea19f03 Put back the presence and s2s tests in the no_db section.
Tests are skipped anyway. This reverts ecce318304
2019-05-07 14:09:11 +02:00
Christophe Romain a6f7d7ce23 Raise api hook right before performing the call 2019-05-07 11:46:04 +02:00
Paweł Chmielowski 4dc8549738 Make anonymous auth don't {de}register user when there are other resources
This should fix issue reported in #2878
2019-05-07 11:02:53 +02:00
Paweł Chmielowski 7d23cd2899 When applying limit of max msgs in spool check only spool size 2019-05-07 09:58:14 +02:00
Paweł Chmielowski 8207ea18bf Remove compiler warnings 2019-05-06 20:03:10 +02:00
Paweł Chmielowski 5b863c25ae Test offline:use_mam_for_storage, mam:user_mucsub_from_muc_archive used together 2019-05-06 19:22:18 +02:00
Paweł Chmielowski 83b790c7c9 Do not store mucsub wrapped messages with no-store hint in offline storage
We already don't store those messages in mam and we don't store messages
that aren't wrapped with that hint in offline, so it make sense to extend
it also to mucsub messages.
2019-05-06 19:22:18 +02:00
Paweł Chmielowski 3d434cfcef Handle get_subscribed_rooms call from mod_muc_room pid
Previously sometimes we tried to post message to all online rooms, and
if that was called from muc room pid, we were not able to process that
message for that room and send response, and this did lead to timeout.
2019-05-06 19:15:48 +02:00
Paweł Chmielowski 4e7bf9207e Do not declare mod_muc as dependency of mod_mam to prevent loop in deps 2019-05-06 12:22:19 +02:00
Mickael Remond ecce318304 s2s test depend on Mnesia configuration 2019-05-06 12:00:36 +02:00
Mickael Remond b18f53c5ce Elixir mix build need to use xmpp 1.3.3 2019-05-06 11:59:52 +02:00
Mickael Remond 09d67a20d3 Remove deprecated calls 2019-05-03 15:58:24 +02:00
Mickael Remond 94f7bbc239 ct_formatter is not needed anymore as Elixir tests have been removed 2019-05-03 15:54:22 +02:00
Mickael Remond be14caddf4 Use stable xmpp version 2019-05-03 15:48:12 +02:00
Mickael Remond 7a8de9cfcf Make some standard admin command to get Mnesia info 2019-05-03 14:59:24 +02:00
Badlop 6b0f7f2a24 Fix bug that appears when importing privacy from Prosody (#2872) 2019-05-02 21:10:21 +02:00
Badlop 7a107c02a5 Store imported room in the correct ServerHost (#2874) 2019-05-02 20:32:10 +02:00
Paweł Chmielowski cd2d62bffd Set from/to in activity marker messages 2019-05-02 13:19:54 +02:00
Christophe Romain f7bc969729 Fix typo and remove forgotten log from 492da2ba 2019-05-02 12:05:20 +02:00
Mickaël Rémond 1ec3525ed6 Merge pull request #2869 from rstgroup/fix-elixir-umbrella-compilation
Fix #2540 Ejabberd doesnt compile as mix umbrella project dependency
2019-05-02 11:53:35 +02:00
Christophe Romain 492da2baac Remove logging from REST lib 2019-05-02 11:40:53 +02:00
Paweł Chmielowski 25f7ce0cb6 Always store ActivityMarker messages 2019-05-02 11:12:47 +02:00
Paweł Chmielowski 0d2720d7ab Don't issue count/message fetch queries for offline from mam when not needed 2019-05-02 11:12:22 +02:00
Mickael Remond ef1a75a628 Remove Elixir tests entry points as they were removed in december:
https://github.com/processone/ejabberd/commit/51cbbf313f478a01cd732a7ee1e21ff356402d0e#diff-098f6bcd4621d373cade4e832627b4f6
2019-05-01 11:51:17 +02:00
Paweł Chmielowski 7eb5a0877b Sqlite doesn't recognize concat() so use || on it instead 2019-04-30 18:33:12 +02:00
Paweł Chmielowski 2562f89005 Cleaner test cleanup 2019-04-30 17:35:25 +02:00
Paweł Chmielowski eac7a77b6a Fix room state cleanup from db on change of persistent option change 2019-04-30 17:34:49 +02:00
Paweł Chmielowski 63c12d18cc Add tests for user mucsub mam from muc mam 2019-04-30 15:01:25 +02:00
Paweł Chmielowski b83d30fd07 Make get_subscribed_rooms work even for non-persistant rooms
This will store info about non-persistant rooms in db, but rooms with that
that option enabled will not be restored on server restart.

This will save info about room only on subscribers change.
2019-04-30 13:41:03 +02:00
Paweł Chmielowski b071c4906f Fix escaping for sql part of mamsub from muc mam 2019-04-30 13:36:31 +02:00
Evgeny Khramtsov c7d04a82a2 Deprecate some listening options
Those are: captcha, register, web_admin, http_bind and xmlrpc
The option `request_handlers` should be used instead, e.g.:

listen:
  ...
  -
    module: ejabberd_http
    request_handlers:
      "/admin": ejabberd_web_admin
      "/bosh": mod_bosh
      "/captcha": ejabberd_captcha
      "/register": mod_register_web
      "/": ejabberd_xmlrpc
2019-04-30 11:14:14 +03:00
Evgeny Khramtsov 268750e3b7 Provide a suggestion when unknown request handler is detected 2019-04-30 10:31:03 +03:00
Evgeny Khramtsov 11e963aa78 Provide a suggestion when unknown command is detected 2019-04-30 10:05:06 +03:00
Evgeny Khramtsov 4af99f7b03 Rename ejabberd_config:similar_option/2 -> misc:best_match/2 2019-04-30 09:36:38 +03:00
Evgeny Khramtsov c56209a27d Provide a suggestion when unknown module is detected 2019-04-29 21:15:52 +03:00
Evgeny Khramtsov 39bbc7cad8 Provide a suggestion when unknown option is detected 2019-04-29 20:57:59 +03:00
Paweł Chmielowski d32a0ce566 Disable offline_from_mam tests on riak since it doesn't support mam 2019-04-29 18:34:21 +02:00
Evgeny Khramtsov 1db22c9656 Improve code for directory deletion 2019-04-29 18:50:54 +03:00
Paweł Chmielowski a0f48cf52f Fix offline from mam on mnesia 2019-04-29 17:25:06 +02:00
Paweł Chmielowski eff70951c5 Add tests for offline with mam storage 2019-04-29 16:40:47 +02:00
Paweł Chmielowski c550d36581 Properly handle infinity as max number of message in mam offline storage 2019-04-29 16:37:21 +02:00
Paweł Chmielowski aaf674160b Sort messages by stanza_id when using mam storage in mod_offline 2019-04-29 16:32:19 +02:00
Paweł Chmielowski faf9b20ac0 Return correct value from count_offline_messages with mam storage option 2019-04-29 16:31:37 +02:00
Paweł Chmielowski 17ff62d4af Make mod_offline put msg ignored by mam in spool when mam storage is on 2019-04-29 16:30:45 +02:00
Paweł Chmielowski b716b835c4 Add tests for offline use_mam_for_storage 2019-04-29 11:35:18 +02:00
Evgeny Khramtsov 830a2f209a Remove TLS options from the example config
The purpose is two-fold:

- To simplify the example config.
- To avoid old TLS configuration to be persistent across
  server updates: this might bring security problems, because
  what's considered "modern" now might be insecure in the future.
2019-04-28 17:50:52 +03:00
Evgeny Khramtsov 05d088b104 Remove OMEMO related configuration from force_node_config section
This doesn't work reliably and takes a lot of effort to change it back
2019-04-28 17:45:41 +03:00
Paweł Chmielowski b76f90fe39 Add mod_offline option for fetching data from mam instead of from spool table
This commit introduces `use_mam_for_storage` option that take boolean
argument. Enabling it will make mod_offline not use spool table for storing
offline message, but instead will use mam archive to retrieve messages
stored when offline.

Enabling this option have couple drawback currently, only messages that
were stored in mam will be available, most of flexible message retrieval
queries don't work (those that allow retrieval/deletion of messages by id).
2019-04-26 19:59:06 +02:00
Paweł Chmielowski bcfe50f817 Return "Bad request" error when origin in websocket connection doesn't match
This also allow websocket_origin option to accept multiple values instead
of just single one.
2019-04-26 15:29:43 +02:00
Paweł Chmielowski 17444ba84e Allow non-moderator subscribers to get list of room subscribers 2019-04-25 14:52:47 +02:00
Paweł Dorofiejczyk 7ab7390b9c Fix #2540 Ejabberd doesnt compile as mix umbrella project dependency 2019-04-25 13:31:08 +02:00
Evgeny Khramtsov a0c8c70c9c Use binary framing in MQTT WebSockets 2019-04-25 14:30:42 +03:00
Evgeny Khramtsov 5819733de6 Merge pull request #2868 from rstgroup/fix-rfc-6455-violation
Fix #2821 RFC6454 violation on websocket connection when validating Origin
2019-04-24 18:21:58 +03:00
Paweł Dorofiejczyk cc5829bc33 Fix RFC6454 violation on websocket connection when validating Origin header 2019-04-24 16:59:54 +02:00
Paweł Chmielowski 8b501f5fe6 Catch potential exceptions in gen_mod:wait_for_process
Seems that at ejabberd shutdown, process may terminate before that function
is called, and in that case erlang:monitor throws exception.
2019-04-24 13:46:16 +02:00
Evgeny Khramtsov fc043dd8cf Reformat try_set_password/4 function 2019-04-23 21:25:10 +03:00
Evgeny Khramtsov cbf3fec2c8 Don't call to mod_register when it's not loaded
Fixes #2828
2019-04-23 21:05:21 +03:00
Evgeny Khramtsov edba1aebb5 Add WebSockets support to mod_mqtt
Example configuration:

listen:
  ...
  -
    port: 5280
    module: ejabberd_http
    request_handlers:
      "/mqtt": mod_mqtt

modules:
  ...
  mod_mqtt: {}
2019-04-23 19:18:22 +03:00
Paweł Chmielowski d2ea905926 Fix handling of list arguments on pgsql 2019-04-23 17:46:42 +02:00
Evgeny Khramtsov feb4c7f5e9 Support other socket modules for MQTT 2019-04-23 16:22:27 +03:00
Evgeny Khramtsov 5faae61bef Move websocket options 2019-04-23 16:21:06 +03:00
Evgeny Khramtsov cc892ddc01 Improve request_handlers validator 2019-04-23 16:19:26 +03:00
Evgeny Khramtsov e623678747 Bump pkix version 2019-04-19 16:26:58 +03:00
Evgeny Khramtsov 7c45b52c86 Correctly support cache tags in ejabberd_auth 2019-04-19 15:42:24 +03:00
Evgeny Khramtsov 0789a145fc Allow returning HTTP headers in REST responses 2019-04-19 15:16:47 +03:00
Evgeny Khramtsov 7f14826564 Use new ets_cache api in ejabberd_auth 2019-04-19 15:08:41 +03:00
Paweł Chmielowski 875b2daff1 Add hook room_destroyed called when room gets destroyed 2019-04-17 18:56:25 +02:00
Paweł Chmielowski 4e2c95fe58 Change implementation of misc:unique_timestamp() 2019-04-16 11:20:55 +02:00
Paweł Chmielowski 83653c0338 Handle cdata in initial data probe of ws module 2019-04-16 10:56:11 +02:00
Paweł Chmielowski 3706e35b86 Make misc:unique_timestamp not overflow microsecond part.
This should fix issue #2860
2019-04-16 10:24:28 +02:00
Paweł Chmielowski c96a925fde Add hooks for tracking mucsub subscriptions changes 2019-04-15 12:03:30 +02:00
Evgeny Khramtsov 009b9a1fd0 Feed whole image to eimp:identify/1
Fixes #2859
2019-04-14 17:05:16 +03:00
Mickael Remond 6dac0a602e Fix syntax in Elixir config file 2019-04-12 10:40:46 +02:00
Paweł Chmielowski 8761e6e0e0 Handle correctly case where ExtraUsername is empty 2019-04-11 12:00:13 +02:00
Paweł Chmielowski c5a06e9d06 When making mucsub message from mam archive check for subject 2019-04-09 11:17:14 +02:00
Evgeny Khramtsov 2e007f1607 Use xml:lang from stanza when it's missing in <command/> element
Thanks to Philipp Hörist for spotting this
2019-04-06 18:27:28 +03:00
Evgeny Khramtsov 9f3ccd604e Add 'sessionid' attribute when required
Fixes #2852
2019-04-06 16:52:22 +03:00
Evgeny Khramtsov 909a505d65 Update mod_muc_riak 2019-04-03 16:04:58 +03:00
Evgeny Khramtsov 3013f1b9bc Update mod_mam dependencies 2019-04-03 15:01:20 +03:00
Evgeny Khramtsov 17b9dc6035 Decrease ugliness of the ugly code 2019-04-03 14:50:56 +03:00
Evgeny Khramtsov e66f594901 Change mucsub API for database backends 2019-04-03 14:20:37 +03:00
Badlop 4e591a73c5 Add ext_mod paths before checking config (processone/ejabberd-contrib#263) 2019-04-03 13:10:11 +02:00
Paweł Chmielowski 623a9ec3ba Return proper error message for duplicate or missing args in http_api call 2019-04-03 12:05:39 +02:00
Paweł Chmielowski 65a6532cd9 Log message when trying to execute http_api command with extra arguments 2019-04-03 11:50:15 +02:00
Paweł Chmielowski 33c10867e3 Formating fix 2019-04-03 11:48:59 +02:00
Paweł Chmielowski d085fff14b Make http_api command execution exception catcher log also command and args 2019-04-03 11:33:07 +02:00
Badlop cbac8a604a cache_size not applied to mod_roster on reload_config (#2769) 2019-04-02 19:52:07 +02:00
Evgeny Khramtsov d96ab48c6b Fix previous commit
Fixes #2847
2019-04-02 09:58:12 +03:00
Evgeny Khramtsov ed2abe471a Rename listening callback from start/2 to start/3
This will prevent conflicts in callback names in mod_mqtt
Old callback function is still supported.
2019-04-01 16:53:28 +03:00
Paweł Chmielowski 7eef966a04 Fix issue with creating HostMatch in mod_mam_sql
This fixes issue #2844
2019-04-01 10:56:03 +02:00
Evgeny Khramtsov 5c69122bbe Use xmpp:get_subtags/2 2019-04-01 10:47:29 +03:00
Paweł Chmielowski 9b040f65a3 Don't use deprecated gen_mod:db_type 2019-03-29 18:41:51 +01:00
Paweł Chmielowski 24b9b69783 Fix issue with mam tests
Too much copy/paste...
2019-03-29 17:18:30 +01:00
Paweł Chmielowski 0c78e01088 Implement mod_muc_sql:select_with_mucsub
This allows us to limit number of issued queries required by
user_mucsub_from_muc_archive option
2019-03-29 16:11:50 +01:00
Paweł Chmielowski a7310ffea1 Make misc:add_delay_info properly handle multiple delay tags in element 2019-03-29 11:25:35 +01:00
Paweł Chmielowski 8e05fd1d24 Add option user_mucsub_from_muc_archive to mod_muc
This option disable storing separate mucsub message for each individual
subscriber but instead when user fetches archive virtual mucsub messages
are generated from muc archives.
2019-03-28 17:42:25 +01:00
Paweł Chmielowski 063869603a Include id in mucsub notification message 2019-03-28 14:43:28 +01:00
Badlop ee2b441b0f Add 'config' tag to the reload_config command 2019-03-27 11:51:43 +01:00
Badlop 1f2b8adc28 Fix Register command to respect mod_register's Access option (#2837) 2019-03-22 15:02:51 +01:00
Christophe Romain 51e7ccc16d Update mysql driver in mix.lock also 2019-03-20 13:46:43 +01:00
Paweł Chmielowski cf733b0913 Update mysql driver 2019-03-20 13:28:13 +01:00
Badlop 6545d55473 Fix crash in mod_muc_admin:web_page_main/2 caused by just_created (#2830) 2019-03-18 17:11:00 +01:00
Paweł Dorofiejczyk 6129720838 Origin header validation on websocket connection (#2821) 2019-03-15 12:19:14 +01:00
Paweł Chmielowski 291c05715b Update mysql dependency 2019-03-15 11:59:18 +01:00
Christophe Romain 4a920dca5a Add newline to error_logger log format 2019-03-15 11:51:48 +01:00
Paweł Chmielowski 5077d39600 Add check for ljid when setting up subscribers 2019-03-14 15:54:51 +01:00
Paweł Chmielowski 3b16afeda7 Flip default bounce_groupchat flag value, muc will drop bounces anyway 2019-03-14 15:40:34 +01:00
Paweł Chmielowski 89db022da4 Add option to mod_offline to make it not bounce mucsub/groupchat messages 2019-03-14 15:17:25 +01:00
Evgeny Khramtsov 0715e62a41 Use jid() instead of ljid() 2019-03-14 14:34:15 +03:00
Evgeny Khramtsov 7a622c3392 Improve formatting of hook crashes 2019-03-14 12:28:37 +03:00
Badlop 629e568294 Delete the ping timer only when timeout_action=kill (#2820) 2019-03-13 11:28:31 +01:00
Holger Weiss 333b010d54 mod_muc: Simplify room creation checks 2019-03-12 00:05:59 +01:00
Holger Weiss 1af2b2cfc7 Merge remote-tracking branch 'processone/pr/2811'
* processone/pr/2811:
  allow room recreate for admins even if nonempty
2019-03-11 23:30:22 +01:00
Holger Weiss 328553ea3f mod_push: Check for payload in encrypted messages
While distinguishing actual chat messages from other message types,
don't classify all <encrypted/> messages as chat messages, but only
those that have a <payload/> element.
2019-03-11 22:58:53 +01:00
Christophe Romain 946baa972d Fix cond_options with new options 2019-03-11 16:58:26 +01:00
Alexey Shchepin e921b43754 Fix transaction aborting and restarting in ejabberd_sql 2019-03-07 22:14:13 +03:00
Christoph Scholz b5fa3b0e2b allow room recreate for admins even if nonempty 2019-03-05 16:51:57 +01:00
Badlop a4222fe9b3 Handle info log level when using MySQL (#2541) 2019-03-04 18:02:02 +01:00
Holger Weiss 652858c7fe Fix incorrect capitalization in German translation 2019-03-01 00:17:29 +01:00
Holger Weiss 93cebbf4a3 Document required Erlang/OTP version bump 2019-02-28 01:08:52 +01:00
Holger Weiss 598e00e80f Bump required Erlang/OTP version to 19.1
Since 538f35d05a, Erlang/OTP 19.1 is
required.
2019-02-28 01:00:05 +01:00
Holger Weiss 2f46aebca2 mod_http_upload: Log nicer warning on unknown host
If an HTTP client issues a request against an unknown host, log a
readable warning (rather than an unreadable error) and respond with a
404 (rather than a 500) status.
2019-02-28 00:28:46 +01:00
Christophe Romain 9bfe5bb618 Add mqtree in included_applications 2019-02-27 14:35:07 +01:00
Evgeny Khramtsov 7511da0f26 Add SQL schemas for MQTT tables 2019-02-27 13:06:17 +03:00
Paweł Chmielowski 456e87e8b2 Copy p1_time_compat:unique_timestamp() to misc and make use of it 2019-02-27 11:00:02 +01:00
Paweł Chmielowski 538f35d05a Replace code using p1_time_compat wrapper with native functions
Since we now require R19, we shouldn't need that anymore.

There are still couple places where p1_time_compat:unique_timestamp() is
used as there is no direct equivalent.
2019-02-27 09:56:31 +01:00
Paweł Chmielowski 77ac0584ed Remove now() calls that sneaked in in pull requests 2019-02-27 09:56:31 +01:00
124 changed files with 4846 additions and 1514 deletions
+2 -1
View File
@@ -1,7 +1,7 @@
language: erlang
otp_release:
- 19.0
- 19.1
- 20.3
- 21.2
@@ -46,6 +46,7 @@ script:
- make
- make install -s
- make xref
- ./tools/hook_deps.sh ebin
- sed -i -e 's/ct:pal/ct:log/' test/suite.erl
- ln -sf ../sql priv/
- echo "" >> rebar.config
+77
View File
@@ -1,5 +1,82 @@
# Version NEXT
# Version 19.05
* Admin
- The minimum required Erlang/OTP version is now 19.1
- Provide a suggestion when unknown command, module, option or request handler is detected
- Deprecate some listening options: captcha, register, web_admin, http_bind and xmlrpc
- Add commands to get Mnesia info: mnesia_info and mnesia_table_info
- Fix Register command to respect mod_register's Access option
- Fixes in Prosody import: privacy and rooms
- Remove TLS options from the example config
- Improve request_handlers validator
- Fix syntax in example Elixir config file
* Auth
- Correctly support cache tags in ejabberd_auth
- Don't process failed EXTERNAL authentication by mod_fail2ban
- Don't call to mod_register when it's not loaded
- Make anonymous auth don't {de}register user when there are other resources
* Developer
- Rename listening callback from start/2 to start/3
- New hook called when room gets destroyed: room_destroyed
- New hooks for tracking mucsub subscriptions changes: muc_subscribed, muc_unsubscribed
- Make static hooks analyzer working again
* MUC
- Service admins are allowed to recreate room even if archiv is nonempty
- New option user_mucsub_from_muc_archive
- Avoid late arrival of get_disco_item response
- Handle get_subscribed_rooms call from mod_muc_room pid
- Fix room state cleanup from db on change of persistent option change
- Make get_subscribed_rooms work even for non-persistant rooms
- Allow non-moderator subscribers to get list of room subscribers
* Offline
- New option bounce_groupchat: make it not bounce mucsub/groupchat messages
- New option use_mam_for_storage: fetch data from mam instead of spool table
- When applying limit of max msgs in spool check only spool size
- Do not store mucsub wrapped messages with no-store hint in offline storage
- Always store ActivityMarker messages
- Don't issue count/message fetch queries for offline from mam when not needed
- Properly handle infinity as max number of message in mam offline storage
- Sort messages by stanza_id when using mam storage in mod_offline
- Return correct value from count_offline_messages with mam storage option
- Make mod_offline put msg ignored by mam in spool when mam storage is on
* SQL:
- Add SQL schemas for MQTT tables
- Report better errors on SQL terms decode failure
- Fix PostgreSQL compatibility in mod_offline_sql:remove_old_messages
- Fix handling of list arguments on pgsql
- Preliminary support for SQL in process_rosteritems command
* Tests
- Add tests for user mucsub mam from muc mam
- Add tests for offline with mam storage
- Add tests for offline use_mam_for_storage
- Initial Docker environment to run ejabberd test suite
- Test offline:use_mam_for_storage, mam:user_mucsub_from_muc_archive used together
* Websocket
- Add WebSockets support to mod_mqtt
- Return "Bad request" error when origin in websocket connection doesn't match
- Fix RFC6454 violation on websocket connection when validating Origin header
- Origin header validation on websocket connection
* Other modules
- mod_adhoc: Use xml:lang from stanza when it's missing in <command/> element
- mod_announce: Add 'sessionid' attribute when required
- mod_bosh: Don't put duplicate polling attribute in bosh payload
- mod_http_api: Improve argument error messages and log messages
- mod_http_upload: Feed whole image to eimp:identify/1
- mod_http_upload: Log nicer warning on unknown host
- mod_http_upload: Case-insensitive host comparison
- mod_mqtt: Support other socket modules
- mod_push: Check for payload in encrypted messages
# Version 19.02
* Admin
-3
View File
@@ -375,9 +375,6 @@ test:
@cd priv && ln -sf ../sql
$(REBAR) skip_deps=true ct
quicktest:
$(REBAR) skip_deps=true ct suites=elixir
.PHONY: src edoc dialyzer Makefile TAGS clean clean-rel distclean rel \
install uninstall uninstall-binary uninstall-all translations deps test \
quicktest erlang_plt deps_plt ejabberd_plt
+1 -1
View File
@@ -107,7 +107,7 @@ To compile ejabberd you need:
- GCC.
- Libexpat ≥ 1.95.
- Libyaml ≥ 0.1.4.
- Erlang/OTP ≥ 19.0.
- Erlang/OTP ≥ 19.1.
- OpenSSL ≥ 1.0.0.
- Zlib ≥ 1.2.3, for Stream Compression support (XEP-0138). Optional.
- PAM library. Optional. For Pluggable Authentication Modules (PAM).
+6 -5
View File
@@ -12,9 +12,9 @@ defmodule Ejabberd.ConfigFile do
language: "en",
allow_contrib_modules: true,
hosts: ["localhost"],
shaper: shaper,
acl: acl,
access: access]
shaper: shaper(),
acl: acl(),
access: access()]
end
defp shaper do
@@ -131,9 +131,10 @@ defmodule Ejabberd.ConfigFile do
module :mod_register do
@opts [welcome_message: [
subject: "Welcome!",
body: "Hi.\nWelcome to this XMPP Server",
body: "Hi.\nWelcome to this XMPP Server"
],
ip_access: :trusted_network,
access: :register]]
access: :register]
end
module :mod_roster do
+1 -1
View File
@@ -3,7 +3,7 @@
AC_PREREQ(2.53)
AC_INIT(ejabberd, m4_esyscmd([echo `git describe --tags 2>/dev/null || echo 0.0` | sed 's/-g.*//;s/-/./' | tr -d '\012']), [ejabberd@process-one.net], [ejabberd])
REQUIRE_ERLANG_MIN="8.0 (Erlang/OTP 19.0)"
REQUIRE_ERLANG_MIN="8.1 (Erlang/OTP 19.1)"
REQUIRE_ERLANG_MAX="100.0.0 (No Max)"
AC_CONFIG_MACRO_DIR([m4])
+5 -30
View File
@@ -39,24 +39,6 @@ certfiles:
- "/etc/letsencrypt/live/localhost/fullchain.pem"
- "/etc/letsencrypt/live/localhost/privkey.pem"
define_macro:
# TLS options for client not being able to use modern ciphers (Windows XP+, Android 3.0+)
CIPHERS_INTERMEDIATE: "ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS"
PROTOCOL_OPTIONS_INTERMEDIATE:
- "no_sslv2"
- "no_sslv3"
# TLS options for client able to use modern ciphers (Windows 7+, Android 5.0+)
CIPHERS_MODERN: "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256"
PROTOCOL_OPTIONS_MODERN:
- "no_sslv2"
- "no_sslv3"
- "no_tlsv1"
- "no_tlsv1_1"
c2s_ciphers: CIPHERS_INTERMEDIATE
c2s_protocol_options: PROTOCOL_OPTIONS_INTERMEDIATE
listen:
-
port: 5222
@@ -75,21 +57,20 @@ listen:
port: 5443
ip: "::"
module: ejabberd_http
tls: true
request_handlers:
"/admin": ejabberd_web_admin
"/api": mod_http_api
"/bosh": mod_bosh
"/captcha": ejabberd_captcha
"/upload": mod_http_upload
"/ws": ejabberd_http_ws
web_admin: true
captcha: true
ciphers: CIPHERS_INTERMEDIATE
protocol_options: PROTOCOL_OPTIONS_INTERMEDIATE
tls: true
-
port: 5280
ip: "::"
module: ejabberd_http
web_admin: true
request_handlers:
"/admin": ejabberd_web_admin
-
port: 1883
ip: "::"
@@ -120,8 +101,6 @@ access_rules:
- allow: local
pubsub_createnode:
- allow: local
register:
- allow
trusted_network:
- allow: loopback
@@ -220,10 +199,6 @@ modules:
- "flat"
- "pep"
force_node_config:
## Change from "whitelist" to "open" to enable OMEMO support
## See https://github.com/processone/ejabberd/issues/2425
"eu.siacs.conversations.axolotl.*":
access_model: whitelist
## Avoid buggy clients to make their bookmarks public
"storage:bookmarks":
access_model: whitelist
+1 -2
View File
@@ -32,5 +32,4 @@
-record(sql_query, {hash, format_query, format_res, args, loc}).
-record(sql_escape, {string, integer, boolean}).
-record(sql_escape, {string, integer, boolean, in_array_string}).
+1 -1
View File
@@ -21,7 +21,7 @@
-record(archive_msg,
{us = {<<"">>, <<"">>} :: {binary(), binary()} | '$2',
id = <<>> :: binary() | '_',
timestamp = p1_time_compat:timestamp() :: erlang:timestamp() | '_' | '$1',
timestamp = erlang:timestamp() :: erlang:timestamp() | '_' | '$1',
peer = {<<"">>, <<"">>, <<"">>} :: ljid() | '_' | '$3' | undefined,
bare_peer = {<<"">>, <<"">>, <<"">>} :: ljid() | '_' | '$3',
packet = #xmlel{} :: xmlel() | message() | '_',
+1 -1
View File
@@ -113,7 +113,7 @@
history :: lqueue(),
subject = [] :: [text()],
subject_author = <<"">> :: binary(),
just_created = misc:now_to_usec(now()) :: true | integer(),
just_created = erlang:system_time(microsecond) :: true | integer(),
activity = treap:empty() :: treap:treap(),
room_shaper = none :: shaper:shaper(),
room_queue :: p1_queue:queue() | undefined
+1 -1
View File
@@ -18,7 +18,7 @@
%%%----------------------------------------------------------------------
-record(push_session,
{us = {<<"">>, <<"">>} :: {binary(), binary()},
timestamp = p1_time_compat:timestamp() :: erlang:timestamp(),
timestamp = erlang:timestamp() :: erlang:timestamp(),
service = {<<"">>, <<"">>, <<"">>} :: ljid(),
node = <<"">> :: binary(),
xml :: undefined | xmlel()}).
-130
View File
@@ -1,130 +0,0 @@
defmodule ExUnit.CTFormatter do
@moduledoc false
use GenEvent
import ExUnit.Formatter, only: [format_time: 2, format_test_failure: 5,
format_test_case_failure: 5]
def init(opts) do
file = File.open! "exunit.log", [:append]
# We do not print filter in log file as exclusion of test with tag
# pending: true is always done
config = %{
file: file,
seed: opts[:seed],
trace: opts[:trace],
colors: Keyword.put_new(opts[:colors], :enabled, false),
width: 80,
tests_counter: 0,
failures_counter: 0,
skipped_counter: 0,
invalids_counter: 0
}
{:ok, config}
end
def handle_event({:suite_started, _opts}, config) do
{:ok, config}
end
def handle_event({:suite_finished, run_us, load_us}, config) do
print_suite(config, run_us, load_us)
File.close config[:file]
:remove_handler
end
def handle_event({:test_started, %ExUnit.Test{} = test}, config) do
if config.tests_counter == 0, do: IO.binwrite config[:file], "== Running #{test.case} ==\n\n"
{:ok, config}
end
def handle_event({:test_finished, %ExUnit.Test{state: nil} = _test}, config) do
IO.binwrite config[:file], "."
{:ok, %{config | tests_counter: config.tests_counter + 1}}
end
def handle_event({:test_finished, %ExUnit.Test{state: {:skip, _}} = _test}, config) do
{:ok, %{config | tests_counter: config.tests_counter + 1,
skipped_counter: config.skipped_counter + 1}}
end
def handle_event({:test_finished, %ExUnit.Test{state: {:invalid, _}} = _test}, config) do
IO.binwrite config[:file], "?"
{:ok, %{config | tests_counter: config.tests_counter + 1,
invalids_counter: config.invalids_counter + 1}}
end
def handle_event({:test_finished, %ExUnit.Test{state: {:failed, failures}} = test}, config) do
formatted = format_test_failure(test, failures, config.failures_counter + 1,
config.width, &formatter(&1, &2, config))
print_failure(formatted, config)
print_logs(test.logs)
{:ok, %{config | tests_counter: config.tests_counter + 1,
failures_counter: config.failures_counter + 1}}
end
def handle_event({:case_started, %ExUnit.TestCase{}}, config) do
{:ok, config}
end
def handle_event({:case_finished, %ExUnit.TestCase{state: nil}}, config) do
{:ok, config}
end
def handle_event({:case_finished, %ExUnit.TestCase{state: {:failed, failures}} = test_case}, config) do
formatted = format_test_case_failure(test_case, failures, config.failures_counter + 1,
config.width, &formatter(&1, &2, config))
print_failure(formatted, config)
{:ok, %{config | failures_counter: config.failures_counter + 1}}
end
## Printing
defp print_suite(config, run_us, load_us) do
IO.binwrite config[:file], "\n\n"
IO.binwrite config[:file], format_time(run_us, load_us)
IO.binwrite config[:file], "\n\n"
# singular/plural
test_pl = pluralize(config.tests_counter, "test", "tests")
failure_pl = pluralize(config.failures_counter, "failure", "failures")
message =
"#{config.tests_counter} #{test_pl}, #{config.failures_counter} #{failure_pl}"
|> if_true(config.skipped_counter > 0, & &1 <> ", #{config.skipped_counter} skipped")
|> if_true(config.invalids_counter > 0, & &1 <> ", #{config.invalids_counter} invalid")
cond do
config.failures_counter > 0 -> IO.binwrite config[:file], message
config.invalids_counter > 0 -> IO.binwrite config[:file], message
true -> IO.binwrite config[:file], message
end
IO.binwrite config[:file], "\nRandomized with seed #{config.seed}\n\n\n\n"
end
defp if_true(value, false, _fun), do: value
defp if_true(value, true, fun), do: fun.(value)
defp print_failure(formatted, config) do
IO.binwrite config[:file], "\n"
IO.binwrite config[:file], formatted
IO.binwrite config[:file], "\n"
end
defp formatter(_, msg, _config),
do: msg
defp pluralize(1, singular, _plural), do: singular
defp pluralize(_, _singular, plural), do: plural
defp print_logs(""), do: nil
defp print_logs(output) do
indent = "\n "
output = String.replace(output, "\n", indent)
IO.puts([" The following output was logged:", indent | output])
end
end
+2 -2
View File
@@ -49,8 +49,8 @@ defmodule Ejabberd.Config do
"""
def get_ejabberd_opts do
get_general_opts()
|> Dict.put(:modules, get_modules_parsed_in_order())
|> Dict.put(:listeners, get_listeners_parsed_in_order())
|> Map.put(:modules, get_modules_parsed_in_order())
|> Map.put(:listeners, get_listeners_parsed_in_order())
|> Ejabberd.Config.OptsFormatter.format_opts_for_ejabberd
end
+14 -6
View File
@@ -3,7 +3,7 @@ defmodule Ejabberd.Mixfile do
def project do
[app: :ejabberd,
version: "19.2.0",
version: "19.5.0",
description: description(),
elixir: "~> 1.4",
elixirc_paths: ["lib"],
@@ -27,7 +27,7 @@ defmodule Ejabberd.Mixfile do
[mod: {:ejabberd_app, []},
applications: [:kernel, :stdlib, :sasl, :ssl],
included_applications: [:lager, :mnesia, :inets, :p1_utils, :cache_tab,
:fast_tls, :stringprep, :fast_xml, :xmpp,
:fast_tls, :stringprep, :fast_xml, :xmpp, :mqtree,
:stun, :fast_yaml, :esip, :jiffy, :p1_oauth2,
:eimp, :base64url, :jose, :pkix, :os_mon]
++ cond_apps()]
@@ -62,7 +62,11 @@ defmodule Ejabberd.Mixfile do
end
defp cond_options do
for {:true, option} <- [{config(:graphics), {:d, :GRAPHICS}}], do:
for {:true, option} <- [{config(:sip), {:d, :SIP}},
{config(:stun), {:d, :STUN}},
{config(:roster_gateway_workaround), {:d, :ROSTER_GATWAY_WORKAROUND}},
{config(:new_sql_schema), {:d, :NEW_SQL_SCHEMA}}
], do:
option
end
@@ -92,9 +96,13 @@ defmodule Ejabberd.Mixfile do
end
defp deps_include(deps) do
base = case Mix.Project.deps_paths()[:ejabberd] do
nil -> "deps"
_ -> ".."
base = if Mix.Project.umbrella?() do
"../../deps"
else
case Mix.Project.deps_paths()[:ejabberd] do
nil -> "deps"
_ -> ".."
end
end
Enum.map(deps, fn dep -> base<>"/#{dep}/include" end)
end
+22 -22
View File
@@ -1,37 +1,37 @@
%{
"artificery": {:hex, :artificery, "0.4.0", "e0b8d3eb9dfe8f42c08a620f90a2aa9cef5dba9fcdfcecad5c2be451df159a77", [:mix], [], "hexpm"},
"artificery": {:hex, :artificery, "0.4.2", "3ded6e29e13113af52811c72f414d1e88f711410cac1b619ab3a2666bbd7efd4", [:mix], [], "hexpm"},
"base64url": {:hex, :base64url, "0.0.1", "36a90125f5948e3afd7be97662a1504b934dd5dac78451ca6e9abf85a10286be", [:rebar], [], "hexpm"},
"cache_tab": {:hex, :cache_tab, "1.0.18", "3c703adef8da7aa91a9ee044426e18465f93a2e162e66165131c53bf37bc0a02", [:rebar3], [{:p1_utils, "1.0.14", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm"},
"distillery": {:hex, :distillery, "2.0.12", "6e78fe042df82610ac3fa50bd7d2d8190ad287d120d3cd1682d83a44e8b34dfb", [:mix], [{:artificery, "~> 0.2", [hex: :artificery, repo: "hexpm", optional: false]}], "hexpm"},
"earmark": {:hex, :earmark, "1.3.1", "73812f447f7a42358d3ba79283cfa3075a7580a3a2ed457616d6517ac3738cb9", [:mix], [], "hexpm"},
"eimp": {:hex, :eimp, "1.0.10", "d7c898ef403741c2dfa45a18124effe2e9108eb95ec1c7827c7b61dbe17c6d61", [:rebar3], [{:p1_utils, "1.0.14", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm"},
"epam": {:hex, :epam, "1.0.5", "9c8afa9abe2d8ce76755a2dff2dc6874f249c469189482d2af03a48639076867", [:rebar3], [], "hexpm"},
"eredis": {:hex, :eredis, "1.1.0", "8d8d74496f35216679b97726b75fb1c8715e99dd7f3ef9f9824a2264c3e0aac0", [:rebar3], [], "hexpm"},
"esip": {:hex, :esip, "1.0.28", "0a71e4d1b7140bbce75757243c055217380a41ea4be5f5381f36df11d58009db", [:rebar3], [{:fast_tls, "1.1.0", [hex: :fast_tls, repo: "hexpm", optional: false]}, {:p1_utils, "1.0.14", [hex: :p1_utils, repo: "hexpm", optional: false]}, {:stun, "1.0.27", [hex: :stun, repo: "hexpm", optional: false]}], "hexpm"},
"ex_doc": {:hex, :ex_doc, "0.19.3", "3c7b0f02851f5fc13b040e8e925051452e41248f685e40250d7e40b07b9f8c10", [:mix], [{:earmark, "~> 1.2", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.10", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"},
"ezlib": {:hex, :ezlib, "1.0.5", "671e48f13cc1c5741e4c4d3d6cb9ff66d93613bc844db779c15172394f9c9d86", [:rebar3], [], "hexpm"},
"fast_tls": {:hex, :fast_tls, "1.1.0", "68bba1eb4edcb64799b5cb5f7f09e7252174757dfb119c28f465b90c230ace93", [:rebar3], [{:p1_utils, "1.0.14", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm"},
"fast_xml": {:hex, :fast_xml, "1.1.35", "71bf779f1ba88f65fcc4fa693f745546843295c4c386a7acb818120b1742242e", [:rebar3], [{:p1_utils, "1.0.14", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm"},
"fast_yaml": {:hex, :fast_yaml, "1.0.18", "f670a4ab0d7009768177ce993959de2de800e811bd5632e7a8577a8b45266534", [:rebar3], [{:p1_utils, "1.0.14", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm"},
"cache_tab": {:hex, :cache_tab, "1.0.19", "9409b890b9526afbfeeea1cd191dec5bb9d4b0089af3684f58cbb7fd88457546", [:rebar3], [{:p1_utils, "1.0.15", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm"},
"distillery": {:hex, :distillery, "2.0.14", "25fc1cdad06282334dbf4a11b6e869cc002855c4e11825157498491df2eed594", [:mix], [{:artificery, "~> 0.2", [hex: :artificery, repo: "hexpm", optional: false]}], "hexpm"},
"earmark": {:hex, :earmark, "1.3.2", "b840562ea3d67795ffbb5bd88940b1bed0ed9fa32834915125ea7d02e35888a5", [:mix], [], "hexpm"},
"eimp": {:hex, :eimp, "1.0.11", "40d11f0dca444a8c5601ac4aaee4e89a1fade721f66527093fa6baecd2e6e119", [:rebar3], [{:p1_utils, "1.0.15", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm"},
"epam": {:hex, :epam, "1.0.6", "6e57e1f5a330fa02a08ee0d4b16d9161f95177351e48c6dfede2f89b7e2f589f", [:rebar3], [], "hexpm"},
"eredis": {:hex, :eredis, "1.2.0", "0b8e9cfc2c00fa1374cd107ea63b49be08d933df2cf175e6a89b73dd9c380de4", [:rebar3], [], "hexpm"},
"esip": {:hex, :esip, "1.0.29", "fc710d5858e14a32933bb83803b0419113761a00ecd888c5c1ab2b9057df600b", [:rebar3], [{:fast_tls, "1.1.1", [hex: :fast_tls, repo: "hexpm", optional: false]}, {:p1_utils, "1.0.15", [hex: :p1_utils, repo: "hexpm", optional: false]}, {:stun, "1.0.28", [hex: :stun, repo: "hexpm", optional: false]}], "hexpm"},
"ex_doc": {:hex, :ex_doc, "0.20.2", "1bd0dfb0304bade58beb77f20f21ee3558cc3c753743ae0ddbb0fd7ba2912331", [:mix], [{:earmark, "~> 1.3", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.10", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"},
"ezlib": {:hex, :ezlib, "1.0.6", "d43a3377006f91c853f65d5efd563d61bbc289f0115a311657c728f5e6e8c39f", [:rebar3], [], "hexpm"},
"fast_tls": {:hex, :fast_tls, "1.1.1", "eb73c12d0659ec3c8e294962a23b2a912b20b08a59bee9271fee100521032d0d", [:rebar3], [{:p1_utils, "1.0.15", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm"},
"fast_xml": {:hex, :fast_xml, "1.1.36", "535d5fe79a8856491eef1902c2d56fe7df50c212b5a650a9e86df18b2d27f56a", [:rebar3], [{:p1_utils, "1.0.15", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm"},
"fast_yaml": {:hex, :fast_yaml, "1.0.19", "9953d71ed0dc176621834f2cfc6c99569a1e730acaa1ea2c2c0d43a184791b7c", [:rebar3], [{:p1_utils, "1.0.15", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm"},
"goldrush": {:hex, :goldrush, "0.1.9", "f06e5d5f1277da5c413e84d5a2924174182fb108dabb39d5ec548b27424cd106", [:rebar3], [], "hexpm"},
"hamcrest": {:hex, :basho_hamcrest, "0.4.1", "fb7b2c92d252a1e9db936750b86089addaebeb8f87967fb4bbdda61e8863338e", [:make, :mix, :rebar3], [], "hexpm"},
"jiffy": {:hex, :jiffy, "0.14.13", "225a9a35e26417832c611526567194b4d3adc4f0dfa5f2f7008f4684076f2a01", [:rebar3], [], "hexpm"},
"jose": {:hex, :jose, "1.9.0", "4167c5f6d06ffaebffd15cdb8da61a108445ef5e85ab8f5a7ad926fdf3ada154", [:mix, :rebar3], [{:base64url, "~> 0.0.1", [hex: :base64url, repo: "hexpm", optional: false]}], "hexpm"},
"lager": {:hex, :lager, "3.6.8", "897efc7679bb82383448646c96768cdc4e747464dd18b999c7aaca485686b0da", [:rebar3], [{:goldrush, "0.1.9", [hex: :goldrush, repo: "hexpm", optional: false]}], "hexpm"},
"lager": {:hex, :lager, "3.6.10", "6172b43ab720ac33914ccd0aeb21fdbdf88213847707d4b91e6af57b2ae5c4d2", [:rebar3], [{:goldrush, "0.1.9", [hex: :goldrush, repo: "hexpm", optional: false]}], "hexpm"},
"luerl": {:hex, :luerl, "0.3.1", "5412807630aac1aaf59ffe5a1bc09259c447b4faeb1d3fe2d4ef41b87676cb04", [:rebar3], [], "hexpm"},
"makeup": {:hex, :makeup, "0.8.0", "9cf32aea71c7fe0a4b2e9246c2c4978f9070257e5c9ce6d4a28ec450a839b55f", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"},
"makeup_elixir": {:hex, :makeup_elixir, "0.13.0", "be7a477997dcac2e48a9d695ec730b2d22418292675c75aa2d34ba0909dcdeda", [:mix], [{:makeup, "~> 0.8", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm"},
"mqtree": {:hex, :mqtree, "1.0.2", "8c41098424b13c67bddb71c966aa19ae72209d8b8f066d0ac9157d5c2834b68b", [:rebar3], [{:p1_utils, "1.0.14", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm"},
"mqtree": {:hex, :mqtree, "1.0.3", "f90212159632055811613efece8e3a28580e168ac967caed297720cad254ab47", [:rebar3], [{:p1_utils, "1.0.15", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm"},
"nimble_parsec": {:hex, :nimble_parsec, "0.5.0", "90e2eca3d0266e5c53f8fbe0079694740b9c91b6747f2b7e3c5d21966bba8300", [:mix], [], "hexpm"},
"p1_mysql": {:hex, :p1_mysql, "1.0.9", "43ca869d3797b0248630cac0e05042e5e1b77d5cc3164206b10079c8d9182e0e", [:rebar3], [], "hexpm"},
"p1_oauth2": {:hex, :p1_oauth2, "0.6.4", "7898999a940814707a26a5b113f39ff1b1fd7f6ea3d31fcd414cd765b2f1fd28", [:rebar3], [], "hexpm"},
"p1_mysql": {:hex, :p1_mysql, "1.0.11", "ae20e1daa2c0634bb61c1529d8401b08b855297b1c7d9af980b2e063d8b58482", [:rebar3], [], "hexpm"},
"p1_oauth2": {:hex, :p1_oauth2, "0.6.5", "a39db41de0287d4d1af3190beaa80edf17335b20f1d0ccace6c09580e0853987", [:rebar3], [], "hexpm"},
"p1_pgsql": {:hex, :p1_pgsql, "1.1.7", "ef64d34adbbe08258cc10b1532649446d8c086ff8663d44f430d837ec31a89f8", [:rebar3], [], "hexpm"},
"p1_utils": {:hex, :p1_utils, "1.0.14", "1f8176b02d787b858d099c1a2276c452ee2e4952766a13837dc93db3e1413a43", [:rebar3], [], "hexpm"},
"pkix": {:hex, :pkix, "1.0.1", "e1f6f9418bd871db842f31e96c7e1ecfcc7f8c4868f5bd384a8a24de9be5d3a1", [:rebar3], [], "hexpm"},
"p1_utils": {:hex, :p1_utils, "1.0.15", "731f76ae1f31f4554afb2ae629cb5589d53bd13efc72b11f5a7c3b1242f91046", [:rebar3], [], "hexpm"},
"pkix": {:hex, :pkix, "1.0.2", "f618455c4edbcc7ebf8be5e655b269876fa36e890b806d18d8b2b708dfb1e583", [:rebar3], [], "hexpm"},
"riak_pb": {:hex, :riak_pb, "2.3.2", "48ffbf66dbb3f136ab9a7134bac4e496754baa5ef58c4f50a61326736d996390", [:make, :mix, :rebar3], [{:hamcrest, "~> 0.4.1", [hex: :basho_hamcrest, repo: "hexpm", optional: false]}], "hexpm"},
"riakc": {:hex, :riakc, "2.5.3", "6132d9e687a0dfd314b2b24c4594302ca8b55568a5d733c491d8fb6cd4004763", [:make, :mix, :rebar3], [{:riak_pb, "~> 2.3", [hex: :riak_pb, repo: "hexpm", optional: false]}], "hexpm"},
"sqlite3": {:hex, :sqlite3, "1.1.6", "4ea71af0b45908b5f02c9b09e4c87177039ef404f20accb35049cd8924cc417c", [:rebar3], [], "hexpm"},
"stringprep": {:hex, :stringprep, "1.0.15", "37cfdbfad4ff4d6cb8582f958ea105b5a86e7c97763a78cde0b7281093df06ef", [:rebar3], [{:p1_utils, "1.0.14", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm"},
"stun": {:hex, :stun, "1.0.27", "37bf4e14a7aa739e5a51e9c826e9479f56dec6d1f9310348009af3ca862582fd", [:rebar3], [{:fast_tls, "1.1.0", [hex: :fast_tls, repo: "hexpm", optional: false]}, {:p1_utils, "1.0.14", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm"},
"xmpp": {:hex, :xmpp, "1.3.2", "db2996fa0aa15cb28775faa15fd7d53a6616b6dbddd569dce0da37d763ff8532", [:rebar3], [{:ezlib, "1.0.5", [hex: :ezlib, repo: "hexpm", optional: false]}, {:fast_tls, "1.1.0", [hex: :fast_tls, repo: "hexpm", optional: false]}, {:fast_xml, "1.1.35", [hex: :fast_xml, repo: "hexpm", optional: false]}, {:p1_utils, "1.0.14", [hex: :p1_utils, repo: "hexpm", optional: false]}, {:stringprep, "1.0.15", [hex: :stringprep, repo: "hexpm", optional: false]}], "hexpm"},
"stringprep": {:hex, :stringprep, "1.0.16", "5a7e617cabba5791aed45b394307d46b9f22ac2eef3bbcf6a4b639637079c84d", [:rebar3], [{:p1_utils, "1.0.15", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm"},
"stun": {:hex, :stun, "1.0.28", "ee81bc075f955f529679213157f76c3d3355a1dec4625108a62872006c3db8a0", [:rebar3], [{:fast_tls, "1.1.1", [hex: :fast_tls, repo: "hexpm", optional: false]}, {:p1_utils, "1.0.15", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm"},
"xmpp": {:hex, :xmpp, "1.3.4", "28f213bdf82510111b883897581037874bb771147847ab5c742aed6ac987c55f", [:rebar3], [{:ezlib, "1.0.6", [hex: :ezlib, repo: "hexpm", optional: false]}, {:fast_tls, "1.1.1", [hex: :fast_tls, repo: "hexpm", optional: false]}, {:fast_xml, "1.1.36", [hex: :fast_xml, repo: "hexpm", optional: false]}, {:p1_utils, "1.0.15", [hex: :p1_utils, repo: "hexpm", optional: false]}, {:stringprep, "1.0.16", [hex: :stringprep, repo: "hexpm", optional: false]}], "hexpm"},
}
+1 -1
View File
@@ -98,7 +98,7 @@
{"Enter path to jabberd14 spool dir","Geben Sie den Pfad zum jabberd14-Spool-Verzeichnis ein"}.
{"Enter path to jabberd14 spool file","Geben Sie den Pfad zur jabberd14-Spool-Datei ein"}.
{"Enter path to text file","Geben Sie den Pfad zur Textdatei ein"}.
{"Enter the text you see","Geben Sie den Text den sie sehen ein"}.
{"Enter the text you see","Geben Sie den Text den Sie sehen ein"}.
{"Erlang Jabber Server","Erlang Jabber-Server"}.
{"Error","Fehler"}.
{"Export all tables as SQL queries to a file:","Alle Tabellen als SQL-Abfragen in eine Datei exportieren:"}.
+1 -1
View File
@@ -485,7 +485,7 @@ msgstr "Geben Sie den Pfad zur Textdatei ein"
#: ejabberd_captcha.erl:71
msgid "Enter the text you see"
msgstr "Geben Sie den Text den sie sehen ein"
msgstr "Geben Sie den Text den Sie sehen ein"
#: mod_vcard.erl:202
msgid "Erlang Jabber Server"
+18 -18
View File
@@ -18,32 +18,32 @@
%%%
%%%----------------------------------------------------------------------
{deps, [{lager, ".*", {git, "https://github.com/erlang-lager/lager", "3.6.7"}},
{p1_utils, ".*", {git, "https://github.com/processone/p1_utils", {tag, "1.0.14"}}},
{cache_tab, ".*", {git, "https://github.com/processone/cache_tab", {tag, "1.0.18"}}},
{fast_tls, ".*", {git, "https://github.com/processone/fast_tls", {tag, "1.1.0"}}},
{stringprep, ".*", {git, "https://github.com/processone/stringprep", {tag, "1.0.15"}}},
{fast_xml, ".*", {git, "https://github.com/processone/fast_xml", {tag, "1.1.35"}}},
{xmpp, ".*", {git, "https://github.com/processone/xmpp", {tag, "1.3.2"}}},
{fast_yaml, ".*", {git, "https://github.com/processone/fast_yaml", {tag, "1.0.18"}}},
{deps, [{lager, ".*", {git, "https://github.com/erlang-lager/lager", "3.6.10"}},
{p1_utils, ".*", {git, "https://github.com/processone/p1_utils", {tag, "1.0.15"}}},
{cache_tab, ".*", {git, "https://github.com/processone/cache_tab", {tag, "1.0.19"}}},
{fast_tls, ".*", {git, "https://github.com/processone/fast_tls", {tag, "1.1.1"}}},
{stringprep, ".*", {git, "https://github.com/processone/stringprep", {tag, "1.0.16"}}},
{fast_xml, ".*", {git, "https://github.com/processone/fast_xml", {tag, "1.1.36"}}},
{xmpp, ".*", {git, "https://github.com/processone/xmpp", {tag, "1.3.4"}}},
{fast_yaml, ".*", {git, "https://github.com/processone/fast_yaml", {tag, "1.0.19"}}},
{jiffy, ".*", {git, "https://github.com/davisp/jiffy", {tag, "0.14.8"}}},
{p1_oauth2, ".*", {git, "https://github.com/processone/p1_oauth2", {tag, "0.6.4"}}},
{pkix, ".*", {git, "https://github.com/processone/pkix", {tag, "1.0.1"}}},
{p1_oauth2, ".*", {git, "https://github.com/processone/p1_oauth2", {tag, "0.6.5"}}},
{pkix, ".*", {git, "https://github.com/processone/pkix", {tag, "1.0.2"}}},
{jose, ".*", {git, "https://github.com/potatosalad/erlang-jose", {tag, "1.8.4"}}},
{eimp, ".*", {git, "https://github.com/processone/eimp", {tag, "1.0.10"}}},
{mqtree, ".*", {git, "https://github.com/processone/mqtree", {tag, "1.0.2"}}},
{if_var_true, stun, {stun, ".*", {git, "https://github.com/processone/stun", {tag, "1.0.27"}}}},
{if_var_true, sip, {esip, ".*", {git, "https://github.com/processone/esip", {tag, "1.0.28"}}}},
{eimp, ".*", {git, "https://github.com/processone/eimp", {tag, "1.0.11"}}},
{mqtree, ".*", {git, "https://github.com/processone/mqtree", {tag, "1.0.3"}}},
{if_var_true, stun, {stun, ".*", {git, "https://github.com/processone/stun", {tag, "1.0.28"}}}},
{if_var_true, sip, {esip, ".*", {git, "https://github.com/processone/esip", {tag, "1.0.29"}}}},
{if_var_true, mysql, {p1_mysql, ".*", {git, "https://github.com/processone/p1_mysql",
{tag, "1.0.9"}}}},
{tag, "1.0.11"}}}},
{if_var_true, pgsql, {p1_pgsql, ".*", {git, "https://github.com/processone/p1_pgsql",
{tag, "1.1.7"}}}},
{tag, "1.1.8"}}}},
{if_var_true, sqlite, {sqlite3, ".*", {git, "https://github.com/processone/erlang-sqlite3",
{tag, "1.1.6"}}}},
{if_var_true, pam, {epam, ".*", {git, "https://github.com/processone/epam",
{tag, "1.0.5"}}}},
{tag, "1.0.6"}}}},
{if_var_true, zlib, {ezlib, ".*", {git, "https://github.com/processone/ezlib",
{tag, "1.0.5"}}}},
{tag, "1.0.6"}}}},
{if_var_true, riak, {riakc, ".*", {git, "https://github.com/processone/riak-erlang-client",
{tag, {if_version_above, "19", "develop", "2.5.3"}}}}},
%% Elixir support, needed to run tests
+17
View File
@@ -464,3 +464,20 @@ CREATE TABLE mix_pam (
CREATE UNIQUE INDEX i_mix_pam ON mix_pam (username, server_host, channel, service);
CREATE INDEX i_mix_pam_us ON mix_pam (username, server_host);
CREATE TABLE mqtt_pub (
username text NOT NULL,
server_host text NOT NULL,
resource text NOT NULL,
topic text NOT NULL,
qos smallint NOT NULL,
payload blob NOT NULL,
payload_format smallint NOT NULL,
content_type text NOT NULL,
response_topic text NOT NULL,
correlation_data blob NOT NULL,
user_properties blob NOT NULL,
expiry bigint NOT NULL
);
CREATE UNIQUE INDEX i_mqtt_topic_server ON mqtt_pub (topic, server_host);
+16
View File
@@ -432,3 +432,19 @@ CREATE TABLE mix_pam (
CREATE UNIQUE INDEX i_mix_pam ON mix_pam (username, channel, service);
CREATE INDEX i_mix_pam_us ON mix_pam (username);
CREATE TABLE mqtt_pub (
username text NOT NULL,
resource text NOT NULL,
topic text NOT NULL,
qos smallint NOT NULL,
payload blob NOT NULL,
payload_format smallint NOT NULL,
content_type text NOT NULL,
response_topic text NOT NULL,
correlation_data blob NOT NULL,
user_properties blob NOT NULL,
expiry bigint NOT NULL
);
CREATE UNIQUE INDEX i_mqtt_topic ON mqtt_pub (topic);
+16
View File
@@ -480,3 +480,19 @@ CREATE TABLE mix_pam (
CREATE UNIQUE INDEX i_mix_pam ON mix_pam (username(191), server_host(191), channel(191), service(191));
CREATE INDEX i_mix_pam_us ON mix_pam (username(191), server_host(191));
CREATE TABLE mqtt_pub (
username varchar(191) NOT NULL,
server_host varchar(191) NOT NULL,
resource varchar(191) NOT NULL,
topic text NOT NULL,
qos tinyint NOT NULL,
payload blob NOT NULL,
payload_format tinyint NOT NULL,
content_type text NOT NULL,
response_topic text NOT NULL,
correlation_data blob NOT NULL,
user_properties blob NOT NULL,
expiry int unsigned NOT NULL,
UNIQUE KEY i_mqtt_topic_server (topic(191), server_host)
);
+15
View File
@@ -448,3 +448,18 @@ CREATE TABLE mix_pam (
CREATE UNIQUE INDEX i_mix_pam ON mix_pam (username(191), channel(191), service(191));
CREATE INDEX i_mix_pam_u ON mix_pam (username(191));
CREATE TABLE mqtt_pub (
username varchar(191) NOT NULL,
resource varchar(191) NOT NULL,
topic text NOT NULL,
qos tinyint NOT NULL,
payload blob NOT NULL,
payload_format tinyint NOT NULL,
content_type text NOT NULL,
response_topic text NOT NULL,
correlation_data blob NOT NULL,
user_properties blob NOT NULL,
expiry int unsigned NOT NULL,
UNIQUE KEY i_mqtt_topic (topic(191))
);
+17
View File
@@ -625,3 +625,20 @@ CREATE TABLE mix_pam (
CREATE UNIQUE INDEX i_mix_pam ON mix_pam (username, server_host, channel, service);
CREATE INDEX i_mix_pam_us ON mix_pam (username, server_host);
CREATE TABLE mqtt_pub (
username text NOT NULL,
server_host text NOT NULL,
resource text NOT NULL,
topic text NOT NULL,
qos smallint NOT NULL,
payload bytea NOT NULL,
payload_format smallint NOT NULL,
content_type text NOT NULL,
response_topic text NOT NULL,
correlation_data bytea NOT NULL,
user_properties bytea NOT NULL,
expiry bigint NOT NULL
);
CREATE UNIQUE INDEX i_mqtt_topic_server ON mqtt_pub (topic, server_host);
+16
View File
@@ -452,3 +452,19 @@ CREATE TABLE mix_pam (
CREATE UNIQUE INDEX i_mix_pam ON mix_pam (username, channel, service);
CREATE INDEX i_mix_pam_us ON mix_pam (username);
CREATE TABLE mqtt_pub (
username text NOT NULL,
resource text NOT NULL,
topic text NOT NULL,
qos smallint NOT NULL,
payload bytea NOT NULL,
payload_format smallint NOT NULL,
content_type text NOT NULL,
response_topic text NOT NULL,
correlation_data bytea NOT NULL,
user_properties bytea NOT NULL,
expiry bigint NOT NULL
);
CREATE UNIQUE INDEX i_mqtt_topic ON mqtt_pub (topic);
+22 -3
View File
@@ -50,6 +50,7 @@
set_master/1,
backup_mnesia/1, restore_mnesia/1,
dump_mnesia/1, dump_table/2, load_mnesia/1,
mnesia_info/0, mnesia_table_info/1,
install_fallback_mnesia/1,
dump_to_textfile/1, dump_to_textfile/2,
mnesia_change_nodename/4,
@@ -198,7 +199,7 @@ get_commands_spec() ->
result_example = [<<"example.com">>, <<"anon.example.com">>],
args = [],
result = {vhosts, {list, {vhost, string}}}},
#ejabberd_commands{name = reload_config, tags = [server],
#ejabberd_commands{name = reload_config, tags = [server, config],
desc = "Reload config file in memory",
module = ?MODULE, function = reload_config,
args = [],
@@ -357,6 +358,16 @@ get_commands_spec() ->
args_desc = ["Full path to the text file"],
args_example = ["/var/lib/ejabberd/database.txt"],
args = [{file, string}], result = {res, restuple}},
#ejabberd_commands{name = mnesia_info, tags = [mnesia],
desc = "Dump info on global Mnesia state",
module = ?MODULE, function = mnesia_info,
args = [], result = {res, string}},
#ejabberd_commands{name = mnesia_table_info, tags = [mnesia],
desc = "Dump info on Mnesia table state",
module = ?MODULE, function = mnesia_table_info,
args_desc = ["Mnesia table name"],
args_example = ["roster"],
args = [{table, string}], result = {res, string}},
#ejabberd_commands{name = install_fallback, tags = [mnesia],
desc = "Install the database from a fallback file",
module = ?MODULE, function = install_fallback_mnesia,
@@ -485,8 +496,9 @@ register(User, Host, Password) ->
Msg = io_lib:format("User ~s@~s already registered", [User, Host]),
{error, conflict, 10090, Msg};
{error, Reason} ->
String = io_lib:format("Can't register user ~s@~s at node ~p: ~p",
[User, Host, node(), Reason]),
String = io_lib:format("Can't register user ~s@~s at node ~p: ~s",
[User, Host, node(),
mod_register:format_error(Reason)]),
{error, cannot_register, 10001, String}
end.
@@ -708,6 +720,13 @@ load_mnesia(Path) ->
{cannot_load, String}
end.
mnesia_info() ->
lists:flatten(io_lib:format("~p", [mnesia:system_info(all)])).
mnesia_table_info(Table) ->
ATable = list_to_atom(Table),
lists:flatten(io_lib:format("~p", [mnesia:table_info(ATable, all)])).
install_fallback_mnesia(Path) ->
case mnesia:install_fallback(Path) of
ok ->
+55 -36
View File
@@ -52,7 +52,6 @@
-include("scram.hrl").
-include("logger.hrl").
-define(AUTH_CACHE, auth_cache).
-define(SALT_LENGTH, 16).
-record(state, {host_modules = #{} :: map()}).
@@ -546,14 +545,14 @@ db_try_register(User, Server, Password, Mod) ->
case use_cache(Mod, Server) of
true ->
case ets_cache:update(
?AUTH_CACHE, {User, Server}, {ok, Password},
cache_tab(Mod), {User, Server}, {ok, Password},
fun() -> Mod:try_register(User, Server, Password1) end,
cache_nodes(Mod, Server)) of
{ok, _} -> ok;
{error, _} = Err -> Err
end;
false ->
Mod:try_register(User, Server, Password1)
ets_cache:untag(Mod:try_register(User, Server, Password1))
end;
false ->
{error, not_allowed}
@@ -569,14 +568,14 @@ db_set_password(User, Server, Password, Mod) ->
case use_cache(Mod, Server) of
true ->
case ets_cache:update(
?AUTH_CACHE, {User, Server}, {ok, Password},
cache_tab(Mod), {User, Server}, {ok, Password},
fun() -> Mod:set_password(User, Server, Password1) end,
cache_nodes(Mod, Server)) of
{ok, _} -> ok;
{error, _} = Err -> Err
end;
false ->
Mod:set_password(User, Server, Password1)
ets_cache:untag(Mod:set_password(User, Server, Password1))
end;
false ->
{error, not_allowed}
@@ -586,7 +585,7 @@ db_get_password(User, Server, Mod) ->
UseCache = use_cache(Mod, Server),
case erlang:function_exported(Mod, get_password, 2) of
false when UseCache ->
case ets_cache:lookup(?AUTH_CACHE, {User, Server}) of
case ets_cache:lookup(cache_tab(Mod), {User, Server}) of
{ok, exists} -> error;
Other -> Other
end;
@@ -594,10 +593,10 @@ db_get_password(User, Server, Mod) ->
error;
true when UseCache ->
ets_cache:lookup(
?AUTH_CACHE, {User, Server},
cache_tab(Mod), {User, Server},
fun() -> Mod:get_password(User, Server) end);
true ->
Mod:get_password(User, Server)
ets_cache:untag(Mod:get_password(User, Server))
end.
db_user_exists(User, Server, Mod) ->
@@ -608,12 +607,15 @@ db_user_exists(User, Server, Mod) ->
case {Mod:store_type(Server), use_cache(Mod, Server)} of
{external, true} ->
case ets_cache:lookup(
?AUTH_CACHE, {User, Server},
cache_tab(Mod), {User, Server},
fun() ->
case Mod:user_exists(User, Server) of
true -> {ok, exists};
false -> error;
{error, _} = Err -> Err
{error, _} = Err -> Err;
{CacheTag, true} -> {CacheTag, {ok, exists}};
{CacheTag, false} -> {CacheTag, error};
{_, {error, _}} = Err -> Err
end
end) of
{ok, _} ->
@@ -624,7 +626,7 @@ db_user_exists(User, Server, Mod) ->
Err
end;
{external, false} ->
Mod:user_exists(User, Server);
ets_cache:untag(Mod:user_exists(User, Server));
_ ->
false
end
@@ -639,14 +641,14 @@ db_check_password(User, AuthzId, Server, ProvidedPassword,
case {Mod:store_type(Server), use_cache(Mod, Server)} of
{external, true} ->
case ets_cache:update(
?AUTH_CACHE, {User, Server}, {ok, ProvidedPassword},
cache_tab(Mod), {User, Server}, {ok, ProvidedPassword},
fun() ->
case Mod:check_password(
User, AuthzId, Server, ProvidedPassword) of
true ->
{ok, ProvidedPassword};
false ->
error
true -> {ok, ProvidedPassword};
false -> error;
{CacheTag, true} -> {CacheTag, {ok, ProvidedPassword}};
{CacheTag, false} -> {CacheTag, error}
end
end) of
{ok, _} ->
@@ -655,7 +657,8 @@ db_check_password(User, AuthzId, Server, ProvidedPassword,
false
end;
{external, false} ->
Mod:check_password(User, AuthzId, Server, ProvidedPassword);
ets_cache:untag(
Mod:check_password(User, AuthzId, Server, ProvidedPassword));
_ ->
false
end
@@ -664,11 +667,11 @@ db_check_password(User, AuthzId, Server, ProvidedPassword,
db_remove_user(User, Server, Mod) ->
case erlang:function_exported(Mod, remove_user, 2) of
true ->
case Mod:remove_user(User, Server) of
case ets_cache:untag(Mod:remove_user(User, Server)) of
ok ->
case use_cache(Mod, Server) of
true ->
ets_cache:delete(?AUTH_CACHE, {User, Server},
ets_cache:delete(cache_tab(Mod), {User, Server},
cache_nodes(Mod, Server));
false ->
ok
@@ -683,7 +686,7 @@ db_remove_user(User, Server, Mod) ->
db_get_users(Server, Opts, Mod) ->
case erlang:function_exported(Mod, get_users, 2) of
true ->
Mod:get_users(Server, Opts);
ets_cache:untag(Mod:get_users(Server, Opts));
false ->
case use_cache(Mod, Server) of
true ->
@@ -692,7 +695,7 @@ db_get_users(Server, Opts, Mod) ->
[{User, Server}|Users];
(_, _, Users) ->
Users
end, [], ?AUTH_CACHE);
end, [], cache_tab(Mod));
false ->
[]
end
@@ -701,7 +704,7 @@ db_get_users(Server, Opts, Mod) ->
db_count_users(Server, Opts, Mod) ->
case erlang:function_exported(Mod, count_users, 2) of
true ->
Mod:count_users(Server, Opts);
ets_cache:untag(Mod:count_users(Server, Opts));
false ->
case use_cache(Mod, Server) of
true ->
@@ -710,7 +713,7 @@ db_count_users(Server, Opts, Mod) ->
Num + 1;
(_, _, Num) ->
Num
end, 0, ?AUTH_CACHE);
end, 0, cache_tab(Mod));
false ->
0
end
@@ -751,12 +754,16 @@ password_to_scram(Password, IterationCount) ->
%%%----------------------------------------------------------------------
-spec init_cache(map()) -> ok.
init_cache(HostModules) ->
case use_cache(HostModules) of
true ->
ets_cache:new(?AUTH_CACHE, cache_opts());
false ->
ets_cache:delete(?AUTH_CACHE)
end.
CacheOpts = cache_opts(),
{True, False} = use_cache(HostModules),
lists:foreach(
fun(Module) ->
ets_cache:new(cache_tab(Module), CacheOpts)
end, True),
lists:foreach(
fun(Module) ->
ets_cache:delete(cache_tab(Module))
end, False).
-spec cache_opts() -> [proplists:property()].
cache_opts() ->
@@ -774,14 +781,22 @@ cache_opts() ->
end,
[{max_size, MaxSize}, {cache_missed, CacheMissed}, {life_time, LifeTime}].
-spec use_cache(map()) -> boolean().
-spec use_cache(map()) -> {True :: [module()], False :: [module()]}.
use_cache(HostModules) ->
lists:any(
fun({Host, Modules}) ->
lists:any(fun(Module) ->
use_cache(Module, Host)
end, Modules)
end, maps:to_list(HostModules)).
{Enabled, Disabled} =
maps:fold(
fun(Host, Modules, Acc) ->
lists:foldl(
fun(Module, {True, False}) ->
case use_cache(Module, Host) of
true ->
{sets:add_element(Module, True), False};
false ->
{True, sets:add_element(Module, False)}
end
end, Acc, Modules)
end, {sets:new(), sets:new()}, HostModules),
{sets:to_list(Enabled), sets:to_list(sets:subtract(Disabled, Enabled))}.
-spec use_cache(module(), binary()) -> boolean().
use_cache(Mod, LServer) ->
@@ -800,6 +815,10 @@ cache_nodes(Mod, LServer) ->
false -> ejabberd_cluster:get_nodes()
end.
-spec cache_tab(module()) -> atom().
cache_tab(Mod) ->
list_to_atom(atom_to_list(Mod) ++ "_cache").
%%%----------------------------------------------------------------------
%%% Internal functions
%%%----------------------------------------------------------------------
+15 -3
View File
@@ -114,10 +114,16 @@ anonymous_user_exist(User, Server) ->
%% Register connection
-spec register_connection(ejabberd_sm:sid(), jid(), ejabberd_sm:info()) -> ok.
register_connection(_SID,
#jid{luser = LUser, lserver = LServer}, Info) ->
#jid{luser = LUser, lserver = LServer, lresource = LResource}, Info) ->
case proplists:get_value(auth_module, Info) of
?MODULE ->
ejabberd_hooks:run(register_user, LServer, [LUser, LServer]);
% Register user only if we are first resource
case ejabberd_sm:get_user_resources(LUser, LServer) of
[LResource] ->
ejabberd_hooks:run(register_user, LServer, [LUser, LServer]);
_ ->
ok
end;
_ ->
ok
end.
@@ -128,7 +134,13 @@ unregister_connection(_SID,
#jid{luser = LUser, lserver = LServer}, Info) ->
case proplists:get_value(auth_module, Info) of
?MODULE ->
ejabberd_hooks:run(remove_user, LServer, [LUser, LServer]);
% Remove user data only if there is no more resources around
case ejabberd_sm:get_user_resources(LUser, LServer) of
[] ->
ejabberd_hooks:run(remove_user, LServer, [LUser, LServer]);
_ ->
ok
end;
_ ->
ok
end.
+8 -9
View File
@@ -284,7 +284,7 @@ init([#body{attrs = Attrs}, IP, SID]) ->
buf_new(XMPPDomain)),
Opts2}
end,
case ejabberd_c2s:start({?MODULE, Socket}, [{receiver, self()}|Opts]) of
case ejabberd_c2s:start(?MODULE, Socket, [{receiver, self()}|Opts]) of
{ok, C2SPid} ->
ejabberd_c2s:accept(C2SPid),
Inactivity = gen_mod:get_module_opt(XMPPDomain,
@@ -324,10 +324,10 @@ wait_for_session(#body{attrs = Attrs} = Req, From,
NewKey = get_attr(newkey, Attrs),
Type = get_attr(type, Attrs),
Requests = Hold + 1,
{PollTime, Polling} = if Wait == 0, Hold == 0 ->
{p1_time_compat:timestamp(), [{polling, ?DEFAULT_POLLING}]};
true -> {undefined, []}
end,
PollTime = if
Wait == 0, Hold == 0 -> erlang:timestamp();
true -> undefined
end,
MaxPause = gen_mod:get_module_opt(State#state.host,
mod_bosh, max_pause),
Resp = #body{attrs =
@@ -337,8 +337,7 @@ wait_for_session(#body{attrs = Attrs} = Req, From,
{hold, Hold}, {'xmpp:restartlogic', true},
{requests, Requests}, {secure, true},
{maxpause, MaxPause}, {'xmlns:xmpp', ?NS_BOSH},
{'xmlns:stream', ?NS_STREAM}, {from, State#state.host}
| Polling]},
{'xmlns:stream', ?NS_STREAM}, {from, State#state.host}]},
{ShaperState, _} =
ejabberd_shaper:update(State#state.shaper_state, Req#body.size),
State1 = State#state{wait_timeout = Wait,
@@ -479,7 +478,7 @@ active1(#body{attrs = Attrs} = Req, From, State) ->
Pause = get_attr(pause, Attrs, undefined),
NewPoll = case State#state.prev_poll of
undefined -> undefined;
_ -> p1_time_compat:timestamp()
_ -> erlang:timestamp()
end,
State5 = State4#state{prev_poll = NewPoll,
prev_key = NewKey},
@@ -736,7 +735,7 @@ is_valid_key(PrevKey, Key) ->
is_overactivity(undefined) -> false;
is_overactivity(PrevPoll) ->
PollPeriod = timer:now_diff(p1_time_compat:timestamp(), PrevPoll) div
PollPeriod = timer:now_diff(erlang:timestamp(), PrevPoll) div
1000000,
if PollPeriod < (?DEFAULT_POLLING) -> true;
true -> false
+36 -25
View File
@@ -27,7 +27,7 @@
-protocol({rfc, 6121}).
%% ejabberd_listener callbacks
-export([start/2, start_link/2, accept/1, listen_opt_type/1, listen_options/0]).
-export([start/3, start_link/3, accept/1, listen_opt_type/1, listen_options/0]).
%% ejabberd_config callbacks
-export([opt_type/1, transform_listen_option/2]).
%% xmpp_stream_in callbacks
@@ -42,7 +42,7 @@
handle_auth_success/4, handle_auth_failure/4, handle_send/3,
handle_recv/3, handle_cdata/2, handle_unbinded_packet/2]).
%% Hooks
-export([handle_unexpected_cast/2,
-export([handle_unexpected_cast/2, process_auth_result/3,
reject_unauthenticated_packet/2, process_closed/2,
process_terminated/2, process_info/2]).
%% API
@@ -63,12 +63,12 @@
%%%===================================================================
%%% ejabberd_listener API
%%%===================================================================
start(SockData, Opts) ->
xmpp_stream_in:start(?MODULE, [SockData, Opts],
start(SockMod, Socket, Opts) ->
xmpp_stream_in:start(?MODULE, [{SockMod, Socket}, Opts],
ejabberd_config:fsm_limit_opts(Opts)).
start_link(SockData, Opts) ->
xmpp_stream_in:start_link(?MODULE, [SockData, Opts],
start_link(SockMod, Socket, Opts) ->
xmpp_stream_in:start_link(?MODULE, [{SockMod, Socket}, Opts],
ejabberd_config:fsm_limit_opts(Opts)).
accept(Ref) ->
@@ -159,6 +159,8 @@ host_up(Host) ->
reject_unauthenticated_packet, 100),
ejabberd_hooks:add(c2s_handle_info, Host, ?MODULE,
process_info, 100),
ejabberd_hooks:add(c2s_auth_result, Host, ?MODULE,
process_auth_result, 100),
ejabberd_hooks:add(c2s_handle_cast, Host, ?MODULE,
handle_unexpected_cast, 100).
@@ -171,6 +173,8 @@ host_down(Host) ->
reject_unauthenticated_packet, 100),
ejabberd_hooks:delete(c2s_handle_info, Host, ?MODULE,
process_info, 100),
ejabberd_hooks:delete(c2s_auth_result, Host, ?MODULE,
process_auth_result, 100),
ejabberd_hooks:delete(c2s_handle_cast, Host, ?MODULE,
handle_unexpected_cast, 100).
@@ -257,6 +261,25 @@ reject_unauthenticated_packet(State, _Pkt) ->
Err = xmpp:serr_not_authorized(),
send(State, Err).
process_auth_result(#{sasl_mech := Mech, auth_module := AuthModule,
socket := Socket, ip := IP, lserver := LServer} = State,
true, User) ->
?INFO_MSG("(~s) Accepted c2s ~s authentication for ~s@~s by ~s backend from ~s",
[xmpp_socket:pp(Socket), Mech, User, LServer,
ejabberd_auth:backend_type(AuthModule),
ejabberd_config:may_hide_data(misc:ip_to_list(IP))]),
State;
process_auth_result(#{sasl_mech := Mech,
socket := Socket, ip := IP, lserver := LServer} = State,
{false, Reason}, User) ->
?WARNING_MSG("(~s) Failed c2s ~s authentication ~sfrom ~s: ~s",
[xmpp_socket:pp(Socket), Mech,
if User /= <<"">> -> ["for ", User, "@", LServer, " "];
true -> ""
end,
ejabberd_config:may_hide_data(misc:ip_to_list(IP)), Reason]),
State.
process_closed(State, Reason) ->
stop(State#{stop_reason => Reason}).
@@ -436,26 +459,14 @@ handle_stream_end(Reason, #{lserver := LServer} = State) ->
State1 = State#{stop_reason => Reason},
ejabberd_hooks:run_fold(c2s_closed, LServer, State1, [Reason]).
handle_auth_success(User, Mech, AuthModule,
#{socket := Socket,
ip := IP, lserver := LServer} = State) ->
?INFO_MSG("(~s) Accepted c2s ~s authentication for ~s@~s by ~s backend from ~s",
[xmpp_socket:pp(Socket), Mech, User, LServer,
ejabberd_auth:backend_type(AuthModule),
ejabberd_config:may_hide_data(misc:ip_to_list(IP))]),
handle_auth_success(User, _Mech, AuthModule,
#{lserver := LServer} = State) ->
State1 = State#{auth_module => AuthModule},
ejabberd_hooks:run_fold(c2s_auth_result, LServer, State1, [true, User]).
handle_auth_failure(User, Mech, Reason,
#{socket := Socket,
ip := IP, lserver := LServer} = State) ->
?WARNING_MSG("(~s) Failed c2s ~s authentication ~sfrom ~s: ~s",
[xmpp_socket:pp(Socket), Mech,
if User /= <<"">> -> ["for ", User, "@", LServer, " "];
true -> ""
end,
ejabberd_config:may_hide_data(misc:ip_to_list(IP)), Reason]),
ejabberd_hooks:run_fold(c2s_auth_result, LServer, State, [false, User]).
handle_auth_failure(User, _Mech, Reason,
#{lserver := LServer} = State) ->
ejabberd_hooks:run_fold(c2s_auth_result, LServer, State, [{false, Reason}, User]).
handle_unbinded_packet(Pkt, #{lserver := LServer} = State) ->
ejabberd_hooks:run_fold(c2s_unbinded_packet, LServer, State, [Pkt]).
@@ -725,7 +736,7 @@ process_self_presence(#{lserver := LServer} = State,
{Pres1, State1} = ejabberd_hooks:run_fold(
c2s_self_presence, LServer, {Pres, State}, []),
State2 = State1#{pres_last => Pres1,
pres_timestamp => p1_time_compat:timestamp()},
pres_timestamp => erlang:timestamp()},
FromUnavailable = PreviousPres == undefined,
broadcast_presence_available(State2, Pres1, FromUnavailable);
process_self_presence(State, _Pres) ->
@@ -888,7 +899,7 @@ bounce_message_queue() ->
new_uniq_id() ->
iolist_to_binary(
[p1_rand:get_string(),
integer_to_binary(p1_time_compat:unique_integer([positive]))]).
integer_to_binary(erlang:unique_integer([positive]))]).
-spec get_conn_type(state()) -> c2s | c2s_tls | c2s_compressed | websocket |
c2s_compressed_tls | http_bind.
+3 -3
View File
@@ -101,8 +101,8 @@ create_captcha(SID, From, To, Lang, Limiter, Args) ->
mk_ocr_field(Lang, CID, Type)],
X = #xdata{type = form, fields = Fs},
Captcha = #xcaptcha{xdata = X},
BodyString = {<<"Your messages to ~s are being blocked. "
"To unblock them, visit ~s">>, [JID, get_url(Id)]},
BodyString = {<<"Your subscription request and/or messages to ~s have been blocked. "
"To unblock your subscription request, visit ~s">>, [JID, get_url(Id)]},
Body = xmpp:mk_text(BodyString, Lang),
OOB = #oob_x{url = get_url(Id)},
Hint = #hint{type = 'no-store'},
@@ -589,7 +589,7 @@ callback(_, _, _) ->
ok.
now_priority() ->
-p1_time_compat:system_time(micro_seconds).
-erlang:system_time(microsecond).
-spec opt_type(atom()) -> fun((any()) -> any()) | [atom()].
opt_type(captcha_cmd) ->
+8 -4
View File
@@ -38,7 +38,8 @@
default_db/1, default_db/2, default_ram_db/1, default_ram_db/2,
default_queue_type/1, queue_dir/0, fsm_limit_opts/1,
use_cache/1, cache_size/1, cache_missed/1, cache_life_time/1,
codec_options/1, get_plain_terms_file/2, negotiation_timeout/0]).
codec_options/1, get_plain_terms_file/2, negotiation_timeout/0,
get_modules/0]).
-export([start/2]).
@@ -71,9 +72,10 @@ start() ->
[named_table, public, {read_concurrency, true}]),
catch ets:new(ejabberd_db_modules,
[named_table, public, {read_concurrency, true}]),
ext_mod:add_paths(),
case load_file(ConfigFile) of
{ok, State1} ->
UnixTime = p1_time_compat:system_time(seconds),
UnixTime = erlang:system_time(second),
SharedKey = case erlang:get_cookie() of
nocookie ->
str:sha(p1_rand:get_string());
@@ -113,7 +115,7 @@ start(Hosts, Opts) ->
[named_table, public, {read_concurrency, true}]),
catch ets:new(ejabberd_db_modules,
[named_table, public, {read_concurrency, true}]),
UnixTime = p1_time_compat:system_time(seconds),
UnixTime = erlang:system_time(second),
SharedKey = case erlang:get_cookie() of
nocookie ->
str:sha(p1_rand:get_string());
@@ -1098,7 +1100,9 @@ validate_opts(#state{opts = Opts} = State, ModOpts) ->
erlang:error(invalid_option)
end;
_ ->
?ERROR_MSG("Unknown option '~s'", [Opt]),
KnownOpts = dict:fetch_keys(ModOpts),
?ERROR_MSG("Unknown option '~s', did you mean '~s'?",
[Opt, misc:best_match(Opt, KnownOpts)]),
erlang:error(unknown_option)
end
end, Opts),
+11 -3
View File
@@ -190,6 +190,9 @@ process(["restart"], _Version) ->
init:restart(),
?STATUS_SUCCESS;
%% TODO: Mnesia operations should not be hardcoded in ejabberd_ctl module.
%% For now, I leave them there to avoid breaking those commands for people that
%% may be using it (as format of response is going to change).
process(["mnesia"], _Version) ->
print("~p~n", [mnesia:system_info(all)]),
?STATUS_SUCCESS;
@@ -319,13 +322,18 @@ try_run_ctp(Args, Auth, AccessCommands, Version) ->
%% @spec (Args::[string()], Auth, AccessCommands) -> string() | integer() | {string(), integer()}
try_call_command(Args, Auth, AccessCommands, Version) ->
try call_command(Args, Auth, AccessCommands, Version) of
{error, command_unknown} ->
{io_lib:format("Error: command ~p not known.", [hd(Args)]), ?STATUS_ERROR};
{error, wrong_command_arguments} ->
{"Error: wrong arguments", ?STATUS_ERROR};
Res ->
Res
catch
throw:{error, unknown_command} ->
KnownCommands = [Cmd || {Cmd, _, _} <- ejabberd_commands:list_commands(Version)],
UnknownCommand = list_to_atom(hd(Args)),
{io_lib:format(
"Error: unknown command '~s'. Did you mean '~s'?",
[hd(Args), misc:best_match(UnknownCommand, KnownCommands)]),
?STATUS_ERROR};
throw:Error ->
{io_lib:format("~p", [Error]), ?STATUS_ERROR};
?EX_RULE(A, Why, Stack) ->
@@ -340,7 +348,7 @@ call_command([CmdString | Args], Auth, _AccessCommands, Version) ->
Command = list_to_atom(binary_to_list(CmdStringU)),
case ejabberd_commands:get_command_format(Command, Auth, Version) of
{error, command_unknown} ->
{error, command_unknown};
throw({error, unknown_command});
{ArgsFormat, ResultFormat} ->
case (catch format_args(Args, ArgsFormat)) of
ArgsFormatted when is_list(ArgsFormatted) ->
+7 -4
View File
@@ -381,10 +381,13 @@ safe_apply(Hook, Module, Function, Args) ->
apply(Module, Function, Args)
end
catch ?EX_RULE(E, R, St) when E /= exit; R /= normal ->
?ERROR_MSG("Hook ~p crashed when running ~p:~p/~p:~n"
"** Reason = ~p~n"
"** Arguments = ~p",
?ERROR_MSG("Hook ~p crashed when running ~p:~p/~p:~n" ++
string:join(
["** Reason = ~p"|
["** Arg " ++ integer_to_list(I) ++ " = ~p"
|| I <- lists:seq(1, length(Args))]],
"~n"),
[Hook, Module, Function, length(Args),
{E, R, ?EX_STACK(St)}, Args]),
{E, R, ?EX_STACK(St)}|Args]),
'EXIT'
end.
+65 -21
View File
@@ -30,12 +30,12 @@
-author('alexey@process-one.net').
%% External exports
-export([start/2, start_link/2,
-export([start/3, start_link/3,
accept/1, receive_headers/1, recv_file/2,
transform_listen_option/2, listen_opt_type/1,
listen_options/0]).
-export([init/2, opt_type/1]).
-export([init/3, opt_type/1]).
-include("logger.hrl").
-include("xmpp.hrl").
@@ -89,17 +89,17 @@
-define(SEND_BUF, 65536).
-define(MAX_POST_SIZE, 20971520). %% 20Mb
start(SockData, Opts) ->
start(SockMod, Socket, Opts) ->
{ok,
proc_lib:spawn(ejabberd_http, init,
[SockData, Opts])}.
[SockMod, Socket, Opts])}.
start_link(SockData, Opts) ->
start_link(SockMod, Socket, Opts) ->
{ok,
proc_lib:spawn_link(ejabberd_http, init,
[SockData, Opts])}.
[SockMod, Socket, Opts])}.
init({SockMod, Socket}, Opts) ->
init(SockMod, Socket, Opts) ->
TLSEnabled = proplists:get_bool(tls, Opts),
TLSOpts1 = lists:filter(fun ({ciphers, _}) -> true;
({dhfile, _}) -> true;
@@ -397,10 +397,11 @@ process(Handlers, Request) ->
%% requested path is "/test/foo/bar", the local path is
%% ["foo", "bar"]
LocalPath = lists:nthtail(length(HandlerPathPrefix), Request#request.path),
R = try
HandlerModule:socket_handoff(
LocalPath, Request, HandlerOpts)
catch error:undef ->
R = case erlang:function_exported(HandlerModule, socket_handoff, 3) of
true ->
HandlerModule:socket_handoff(
LocalPath, Request, HandlerOpts);
false ->
HandlerModule:process(LocalPath, Request)
end,
ejabberd_hooks:run(http_request_debug, [{LocalPath, Request}]),
@@ -962,6 +963,38 @@ transform_listen_option({request_handlers, Hs}, Opts) ->
transform_listen_option(Opt, Opts) ->
[Opt|Opts].
prepare_request_module(mod_http_bind) ->
mod_bosh;
prepare_request_module(Mod) when is_atom(Mod) ->
case code:ensure_loaded(Mod) of
{module, Mod} ->
Mod;
Err ->
?ERROR_MSG(
"Failed to load request handler ~s, "
"did you mean ~s? Hint: "
"make sure there is no typo and file ~s.beam "
"exists inside either ~s or ~s directory",
[Mod,
misc:best_match(Mod, ejabberd_config:get_modules()),
Mod,
filename:dirname(code:which(?MODULE)),
ext_mod:modules_dir()]),
erlang:error(Err)
end.
emit_option_replacement(Option, Path, Handler) ->
?WARNING_MSG(
"Listening option '~s' is deprecated, enable it via request handlers, e.g.:~n"
"listen:~n"
" ...~n"
" -~n"
" module: ~s~n"
" request_handlers:~n"
" ...~n"
" \"~s\": ~s~n",
[Option, ?MODULE, Path, Handler]).
-spec opt_type(atom()) -> fun((any()) -> any()) | [atom()].
opt_type(trusted_proxies) ->
fun (all) -> all;
@@ -983,15 +1016,30 @@ listen_opt_type(certfile = Opt) ->
File
end;
listen_opt_type(captcha) ->
fun(B) when is_boolean(B) -> B end;
fun(B) when is_boolean(B) ->
emit_option_replacement(captcha, "/captcha", ejabberd_captcha),
B
end;
listen_opt_type(register) ->
fun(B) when is_boolean(B) -> B end;
fun(B) when is_boolean(B) ->
emit_option_replacement(register, "/register", mod_register_web),
B
end;
listen_opt_type(web_admin) ->
fun(B) when is_boolean(B) -> B end;
fun(B) when is_boolean(B) ->
emit_option_replacement(web_admin, "/admin", ejabberd_web_admin),
B
end;
listen_opt_type(http_bind) ->
fun(B) when is_boolean(B) -> B end;
fun(B) when is_boolean(B) ->
emit_option_replacement(http_bind, "/bosh", mod_bosh),
B
end;
listen_opt_type(xmlrpc) ->
fun(B) when is_boolean(B) -> B end;
fun(B) when is_boolean(B) ->
emit_option_replacement(xmlrpc, "/", ejabberd_xmlrpc),
B
end;
listen_opt_type(tag) ->
fun(B) when is_binary(B) -> B end;
listen_opt_type(request_handlers) ->
@@ -1003,11 +1051,7 @@ listen_opt_type(request_handlers) ->
Hs2 = [{str:tokens(
iolist_to_binary(Path), <<"/">>),
Mod} || {Path, Mod} <- Hs1],
[{Path,
case Mod of
mod_http_bind -> mod_bosh;
_ -> Mod
end} || {Path, Mod} <- Hs2]
[{Path, prepare_request_module(Mod)} || {Path, Mod} <- Hs2]
end;
listen_opt_type(default_host) ->
fun iolist_to_binary/1;
+9 -16
View File
@@ -24,7 +24,6 @@
%%%----------------------------------------------------------------------
-module(ejabberd_http_ws).
-author('ecestari@process-one.net').
-behaviour(ejabberd_config).
-behaviour(xmpp_socket).
-behaviour(p1_fsm).
@@ -33,7 +32,7 @@
terminate/3, send_xml/2, setopts/2, sockname/1,
peername/1, controlling_process/2, get_owner/1,
reset_stream/1, close/1, change_shaper/2,
socket_handoff/3, get_transport/1, opt_type/1]).
socket_handoff/3, get_transport/1]).
-include("logger.hrl").
@@ -143,7 +142,7 @@ init([{#ws{ip = IP, http_opts = HOpts}, _} = WS]) ->
Socket = {http_ws, self(), IP},
?DEBUG("Client connected through websocket ~p",
[Socket]),
case ejabberd_c2s:start({?MODULE, Socket}, [{receiver, self()}|Opts]) of
case ejabberd_c2s:start(?MODULE, Socket, [{receiver, self()}|Opts]) of
{ok, C2SPid} ->
ejabberd_c2s:accept(C2SPid),
Timer = erlang:start_timer(WSTimeout, self(), []),
@@ -202,15 +201,15 @@ handle_sync_event({send_xml, Packet}, _From, StateName,
case Packet2 of
{xmlstreamstart, Name, Attrs3} ->
B = fxml:element_to_binary(#xmlel{name = Name, attrs = Attrs3}),
WsPid ! {send, <<(binary:part(B, 0, byte_size(B)-2))/binary, ">">>};
WsPid ! {text, <<(binary:part(B, 0, byte_size(B)-2))/binary, ">">>};
{xmlstreamend, Name} ->
WsPid ! {send, <<"</", Name/binary, ">">>};
WsPid ! {text, <<"</", Name/binary, ">">>};
{xmlstreamelement, El} ->
WsPid ! {send, fxml:element_to_binary(El)};
WsPid ! {text, fxml:element_to_binary(El)};
{xmlstreamraw, Bin} ->
WsPid ! {send, Bin};
WsPid ! {text, Bin};
{xmlstreamcdata, Bin2} ->
WsPid ! {send, Bin2};
WsPid ! {text, Bin2};
skip ->
ok
end,
@@ -225,7 +224,7 @@ handle_sync_event(close, _From, StateName, #state{ws = {_, WsPid}, rfc_compilant
when StateName /= stream_end_sent ->
Close = #xmlel{name = <<"close">>,
attrs = [{<<"xmlns">>, <<"urn:ietf:params:xml:ns:xmpp-framing">>}]},
WsPid ! {send, fxml:element_to_binary(Close)},
WsPid ! {text, fxml:element_to_binary(Close)},
{stop, normal, StateData};
handle_sync_event(close, _From, _StateName, StateData) ->
{stop, normal, StateData}.
@@ -359,6 +358,7 @@ parsed_items(List) ->
when element(1, El) == xmlel;
element(1, El) == xmlstreamstart;
element(1, El) == xmlstreamelement;
element(1, El) == xmlstreamcdata;
element(1, El) == xmlstreamend ->
parsed_items([El | List]);
{'$gen_event', {xmlstreamerror, _}} ->
@@ -366,10 +366,3 @@ parsed_items(List) ->
after 0 ->
lists:reverse(List)
end.
opt_type(websocket_ping_interval) ->
fun (I) when is_integer(I), I >= 0 -> I end;
opt_type(websocket_timeout) ->
fun (I) when is_integer(I), I > 0 -> I end;
opt_type(_) ->
[websocket_ping_interval, websocket_timeout].
+1 -1
View File
@@ -110,7 +110,7 @@ code_change(_OldVsn, State, _Extra) ->
%%%===================================================================
-spec current_time() -> non_neg_integer().
current_time() ->
p1_time_compat:system_time(milli_seconds).
erlang:system_time(millisecond).
-spec clean({non_neg_integer(), binary()} | '$end_of_table')
-> non_neg_integer() | infinity.
+34 -18
View File
@@ -43,10 +43,12 @@
-type endpoint() :: {inet:port_number(), inet:ip_address(), transport()}.
-type listen_opts() :: [proplists:property()].
-type listener() :: {endpoint(), module(), listen_opts()}.
-type sockmod() :: gen_tcp.
-type socket() :: inet:socket().
-callback start({gen_tcp, inet:socket()}, listen_opts()) ->
-callback start(sockmod(), socket(), listen_opts()) ->
{ok, pid()} | {error, any()} | ignore.
-callback start_link({gen_tcp, inet:socket()}, listen_opts()) ->
-callback start_link(sockmod(), socket(), listen_opts()) ->
{ok, pid()} | {error, any()} | ignore.
-callback accept(pid()) -> any().
-callback listen_opt_type(atom()) -> fun((term()) -> term()).
@@ -197,10 +199,15 @@ split_opts(Opts) ->
-spec accept(inet:socket(), module(), listen_opts(), atom()) -> no_return().
accept(ListenSocket, Module, Opts, Sup) ->
Interval = proplists:get_value(accept_interval, Opts, 0),
accept(ListenSocket, Module, Opts, Sup, Interval).
Arity = case erlang:function_exported(Module, start, 3) of
true -> 3;
false -> 2
end,
accept(ListenSocket, Module, Opts, Sup, Interval, Arity).
-spec accept(inet:socket(), module(), listen_opts(), atom(), non_neg_integer()) -> no_return().
accept(ListenSocket, Module, Opts, Sup, Interval) ->
-spec accept(inet:socket(), module(), listen_opts(), atom(),
non_neg_integer(), 2|3) -> no_return().
accept(ListenSocket, Module, Opts, Sup, Interval, Arity) ->
NewInterval = check_rate_limit(Interval),
case gen_tcp:accept(ListenSocket) of
{ok, Socket} ->
@@ -213,7 +220,7 @@ accept(ListenSocket, Module, Opts, Sup, Interval) ->
gen_tcp:close(Socket);
{{Addr, Port}, {PAddr, PPort}} = SP ->
Opts2 = [{sock_peer_name, SP} | Opts],
Receiver = case start_connection(Module, Socket, Opts2, Sup) of
Receiver = case start_connection(Module, Arity, Socket, Opts2, Sup) of
{ok, RecvPid} ->
RecvPid;
_ ->
@@ -228,7 +235,7 @@ accept(ListenSocket, Module, Opts, Sup, Interval) ->
_ ->
case {inet:sockname(Socket), inet:peername(Socket)} of
{{ok, {Addr, Port}}, {ok, {PAddr, PPort}}} ->
Receiver = case start_connection(Module, Socket, Opts, Sup) of
Receiver = case start_connection(Module, Arity, Socket, Opts, Sup) of
{ok, RecvPid} ->
RecvPid;
_ ->
@@ -243,11 +250,11 @@ accept(ListenSocket, Module, Opts, Sup, Interval) ->
gen_tcp:close(Socket)
end
end,
accept(ListenSocket, Module, Opts, Sup, NewInterval);
accept(ListenSocket, Module, Opts, Sup, NewInterval, Arity);
{error, Reason} ->
?ERROR_MSG("(~w) Failed TCP accept: ~s",
[ListenSocket, inet:format_error(Reason)]),
accept(ListenSocket, Module, Opts, Sup, NewInterval)
accept(ListenSocket, Module, Opts, Sup, NewInterval, Arity)
end.
-spec udp_recv(inet:socket(), module(), listen_opts()) -> no_return().
@@ -269,12 +276,18 @@ udp_recv(Socket, Module, Opts) ->
throw({error, Reason})
end.
-spec start_connection(module(), inet:socket(), listen_opts(), atom()) ->
-spec start_connection(module(), 2|3, inet:socket(), listen_opts(), atom()) ->
{ok, pid()} | {error, any()} | ignore.
start_connection(Module, Socket, Opts, Sup) ->
start_connection(Module, Arity, Socket, Opts, Sup) ->
Res = case Sup of
undefined -> Module:start({gen_tcp, Socket}, Opts);
_ -> supervisor:start_child(Sup, [{gen_tcp, Socket}, Opts])
undefined when Arity == 3 ->
Module:start(gen_tcp, Socket, Opts);
undefined ->
Module:start({gen_tcp, Socket}, Opts);
_ when Arity == 3 ->
supervisor:start_child(Sup, [gen_tcp, Socket, Opts]);
_ ->
supervisor:start_child(Sup, [{gen_tcp, Socket}, Opts])
end,
case Res of
{ok, Pid} ->
@@ -517,8 +530,11 @@ validate_module(Mod) ->
case code:ensure_loaded(Mod) of
{module, Mod} ->
lists:foreach(
fun({Fun, Arity}) ->
case erlang:function_exported(Mod, Fun, Arity) of
fun({Fun, Arities}) ->
case lists:any(
fun(Arity) ->
erlang:function_exported(Mod, Fun, Arity)
end, Arities) of
true -> ok;
false ->
?ERROR_MSG("Failed to load listening module ~s, "
@@ -526,11 +542,11 @@ validate_module(Mod) ->
"The module is either not a listening module "
"or it is a third-party module which "
"requires update",
[Mod, Fun, Arity]),
[Mod, Fun, hd(Arities)]),
erlang:error(badarg)
end
end, [{start, 2}, {start_link, 2},
{accept, 1}, {listen_options, 0}]);
end, [{start, [3,2]}, {start_link, [3,2]},
{accept, [1]}, {listen_options, [0]}]);
_ ->
?ERROR_MSG("Failed to load unknown listening module ~s: "
"make sure there is no typo and ~s.beam "
+2 -2
View File
@@ -366,7 +366,7 @@ init([I]) ->
handle_call(connect, From, #state{connection = undefined,
pending_q = Q} = State) ->
CurrTime = p1_time_compat:monotonic_time(milli_seconds),
CurrTime = erlang:monotonic_time(millisecond),
Q2 = try p1_queue:in({From, CurrTime}, Q)
catch error:full ->
Q1 = clean_queue(Q, CurrTime),
@@ -590,7 +590,7 @@ get_queue_type() ->
-spec flush_queue(p1_queue:queue()) -> p1_queue:queue().
flush_queue(Q) ->
CurrTime = p1_time_compat:monotonic_time(milli_seconds),
CurrTime = erlang:monotonic_time(millisecond),
p1_queue:dropwhile(
fun({From, Time}) ->
if (CurrTime - Time) >= ?CALL_TIMEOUT ->
+2 -2
View File
@@ -413,8 +413,8 @@ get_component_number(LDomain) ->
-spec get_domain_balancing(jid(), jid(), binary()) -> any().
get_domain_balancing(From, To, LDomain) ->
case ejabberd_config:get_option({domain_balancing, LDomain}) of
undefined -> p1_time_compat:system_time();
random -> p1_time_compat:system_time();
undefined -> erlang:system_time();
random -> erlang:system_time();
source -> jid:tolower(From);
destination -> jid:tolower(To);
bare_source -> jid:remove_resource(jid:tolower(From));
+3 -3
View File
@@ -112,7 +112,7 @@ external_host_overloaded(Host) ->
"seconds",
[Host, ?S2S_OVERLOAD_BLOCK_PERIOD]),
mnesia:transaction(fun () ->
Time = p1_time_compat:monotonic_time(),
Time = erlang:monotonic_time(),
mnesia:write(#temporarily_blocked{host = Host,
timestamp = Time})
end).
@@ -123,8 +123,8 @@ is_temporarly_blocked(Host) ->
case mnesia:dirty_read(temporarily_blocked, Host) of
[] -> false;
[#temporarily_blocked{timestamp = T} = Entry] ->
Diff = p1_time_compat:monotonic_time() - T,
case p1_time_compat:convert_time_unit(Diff, native, micro_seconds) of
Diff = erlang:monotonic_time() - T,
case erlang:convert_time_unit(Diff, native, microsecond) of
N when N > (?S2S_OVERLOAD_BLOCK_PERIOD) * 1000 * 1000 ->
mnesia:dirty_delete_object(Entry), false;
_ -> true
+5 -5
View File
@@ -24,7 +24,7 @@
-behaviour(ejabberd_listener).
%% ejabberd_listener callbacks
-export([start/2, start_link/2, accept/1, listen_opt_type/1, listen_options/0]).
-export([start/3, start_link/3, accept/1, listen_opt_type/1, listen_options/0]).
%% xmpp_stream_in callbacks
-export([init/1, handle_call/3, handle_cast/2,
handle_info/2, terminate/2, code_change/3]).
@@ -50,12 +50,12 @@
%%%===================================================================
%%% API
%%%===================================================================
start(SockData, Opts) ->
xmpp_stream_in:start(?MODULE, [SockData, Opts],
start(SockMod, Socket, Opts) ->
xmpp_stream_in:start(?MODULE, [{SockMod, Socket}, Opts],
ejabberd_config:fsm_limit_opts(Opts)).
start_link(SockData, Opts) ->
xmpp_stream_in:start_link(?MODULE, [SockData, Opts],
start_link(SockMod, Socket, Opts) ->
xmpp_stream_in:start_link(?MODULE, [{SockMod, Socket}, Opts],
ejabberd_config:fsm_limit_opts(Opts)).
close(Ref) ->
+5 -5
View File
@@ -26,7 +26,7 @@
-protocol({xep, 114, '1.6'}).
%% ejabberd_listener callbacks
-export([start/2, start_link/2, accept/1]).
-export([start/3, start_link/3, accept/1]).
-export([listen_opt_type/1, listen_options/0, transform_listen_option/2]).
%% xmpp_stream_in callbacks
-export([init/1, handle_info/2, terminate/2, code_change/3]).
@@ -44,12 +44,12 @@
%%%===================================================================
%%% API
%%%===================================================================
start(SockData, Opts) ->
xmpp_stream_in:start(?MODULE, [SockData, Opts],
start(SockMod, Socket, Opts) ->
xmpp_stream_in:start(?MODULE, [{SockMod, Socket}, Opts],
ejabberd_config:fsm_limit_opts(Opts)).
start_link(SockData, Opts) ->
xmpp_stream_in:start_link(?MODULE, [SockData, Opts],
start_link(SockMod, Socket, Opts) ->
xmpp_stream_in:start_link(?MODULE, [{SockMod, Socket}, Opts],
ejabberd_config:fsm_limit_opts(Opts)).
accept(Ref) ->
+9 -9
View File
@@ -1,7 +1,7 @@
%%%-------------------------------------------------------------------
%%% File : ejabberd_sip.erl
%%% Author : Evgeny Khramtsov <ekhramtsov@process-one.net>
%%% Purpose :
%%% Purpose :
%%% Created : 30 Apr 2017 by Evgeny Khramtsov <ekhramtsov@process-one.net>
%%%
%%%
@@ -28,7 +28,7 @@
-ifndef(SIP).
-include("logger.hrl").
-export([accept/1, start/2, start_link/2, listen_options/0]).
-export([accept/1, start/3, start_link/3, listen_options/0]).
fail() ->
?CRITICAL_MSG("Listening module ~s is not available: "
"ejabberd is not compiled with SIP support",
@@ -38,14 +38,14 @@ accept(_) ->
fail().
listen_options() ->
fail().
start(_, _) ->
start(_, _, _) ->
fail().
start_link(_, _) ->
start_link(_, _, _) ->
fail().
-else.
%% API
-export([tcp_init/2, udp_init/2, udp_recv/5, start/2,
start_link/2, accept/1, listen_options/0]).
-export([tcp_init/2, udp_init/2, udp_recv/5, start/3,
start_link/3, accept/1, listen_options/0]).
%%%===================================================================
@@ -62,10 +62,10 @@ udp_init(Socket, Opts) ->
udp_recv(Sock, Addr, Port, Data, Opts) ->
esip_socket:udp_recv(Sock, Addr, Port, Data, Opts).
start(Opaque, Opts) ->
esip_socket:start(Opaque, Opts).
start(SockMod, Socket, Opts) ->
esip_socket:start({SockMod, Socket}, Opts).
start_link({gen_tcp, Sock}, Opts) ->
start_link(gen_tcp, Sock, Opts) ->
esip_socket:start_link(Sock, Opts).
accept(_) ->
+1 -1
View File
@@ -1040,7 +1040,7 @@ kick_user(User, Server, Resource) ->
end.
make_sid() ->
{p1_time_compat:unique_timestamp(), self()}.
{misc:unique_timestamp(), self()}.
-spec opt_type(atom()) -> fun((any()) -> any()) | [atom()].
opt_type(sm_db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
+41 -16
View File
@@ -56,7 +56,8 @@
odbcinst_config/0,
init_mssql/1,
keep_alive/2,
to_list/2]).
to_list/2,
to_array/2]).
%% gen_fsm callbacks
-export([init/1, handle_event/3, handle_sync_event/4,
@@ -167,7 +168,7 @@ sql_call(Host, Msg) ->
none -> {error, <<"Unknown Host">>};
Pid ->
sync_send_event(Pid,{sql_cmd, Msg,
p1_time_compat:monotonic_time(milli_seconds)},
erlang:monotonic_time(millisecond)},
query_timeout(Host))
end;
_State -> nested_op(Msg)
@@ -176,7 +177,7 @@ sql_call(Host, Msg) ->
keep_alive(Host, PID) ->
case sync_send_event(PID,
{sql_cmd, {sql_query, ?KEEPALIVE_QUERY},
p1_time_compat:monotonic_time(milli_seconds)},
erlang:monotonic_time(millisecond)},
query_timeout(Host)) of
{selected,_,[[<<"1">>]]} ->
ok;
@@ -264,6 +265,10 @@ to_list(EscapeFun, Val) ->
Escaped = lists:join(<<",">>, lists:map(EscapeFun, Val)),
[<<"(">>, Escaped, <<")">>].
to_array(EscapeFun, Val) ->
Escaped = lists:join(<<",">>, lists:map(EscapeFun, Val)),
[<<"{">>, Escaped, <<"}">>].
encode_term(Term) ->
escape(list_to_binary(
erl_prettypr:format(erl_syntax:abstract(Term),
@@ -271,9 +276,23 @@ encode_term(Term) ->
decode_term(Bin) ->
Str = binary_to_list(<<Bin/binary, ".">>),
{ok, Tokens, _} = erl_scan:string(Str),
{ok, Term} = erl_parse:parse_term(Tokens),
Term.
try
{ok, Tokens, _} = erl_scan:string(Str),
{ok, Term} = erl_parse:parse_term(Tokens),
Term
catch _:{badmatch, {error, {Line, Mod, Reason}, _}} ->
?ERROR_MSG("Corrupted Erlang term in SQL database:~n"
"** Scanner error: at line ~B: ~s~n"
"** Term: ~s",
[Line, Mod:format_error(Reason), Bin]),
erlang:error(badarg);
_:{badmatch, {error, {Line, Mod, Reason}}} ->
?ERROR_MSG("Corrupted Erlang term in SQL database:~n"
"** Parser error: at line ~B: ~s~n"
"** Term: ~s",
[Line, Mod:format_error(Reason), Bin]),
erlang:error(badarg)
end.
-spec sqlite_db(binary()) -> atom().
sqlite_db(Host) ->
@@ -450,7 +469,7 @@ print_state(State) -> State.
run_sql_cmd(Command, From, State, Timestamp) ->
QueryTimeout = query_timeout(State#state.host),
case p1_time_compat:monotonic_time(milli_seconds) - Timestamp of
case erlang:monotonic_time(millisecond) - Timestamp of
Age when Age < QueryTimeout ->
put(?NESTING_KEY, ?TOP_LEVEL_TXN),
put(?STATE_KEY, State),
@@ -524,6 +543,7 @@ outer_transaction(F, NRestarts, _Reason) ->
catch
?EX_RULE(throw, {aborted, Reason}, _) when NRestarts > 0 ->
sql_query_internal([<<"rollback;">>]),
put(?NESTING_KEY, ?TOP_LEVEL_TXN),
outer_transaction(F, NRestarts - 1, Reason);
?EX_RULE(throw, {aborted, Reason}, Stack) when NRestarts =:= 0 ->
?ERROR_MSG("SQL transaction restarts exceeded~n** "
@@ -610,6 +630,7 @@ sql_query_internal(#sql_query{} = Query) ->
check_error(Res, Query);
sql_query_internal(F) when is_function(F) ->
case catch execute_fun(F) of
{aborted, Reason} -> {error, Reason};
{'EXIT', Reason} -> {error, Reason};
Res -> Res
end;
@@ -674,10 +695,11 @@ generic_sql_query_format(SQLQuery) ->
generic_escape() ->
#sql_escape{string = fun(X) -> <<"'", (escape(X))/binary, "'">> end,
integer = fun(X) -> misc:i2l(X) end,
boolean = fun(true) -> <<"1">>;
integer = fun(X) -> misc:i2l(X) end,
boolean = fun(true) -> <<"1">>;
(false) -> <<"0">>
end
end,
in_array_string = fun(X) -> <<"'", (escape(X))/binary, "'">> end
}.
sqlite_sql_query(SQLQuery) ->
@@ -691,10 +713,11 @@ sqlite_sql_query_format(SQLQuery) ->
sqlite_escape() ->
#sql_escape{string = fun(X) -> <<"'", (standard_escape(X))/binary, "'">> end,
integer = fun(X) -> misc:i2l(X) end,
boolean = fun(true) -> <<"1">>;
integer = fun(X) -> misc:i2l(X) end,
boolean = fun(true) -> <<"1">>;
(false) -> <<"0">>
end
end,
in_array_string = fun(X) -> <<"'", (standard_escape(X))/binary, "'">> end
}.
standard_escape(S) ->
@@ -715,10 +738,11 @@ pgsql_prepare(SQLQuery, State) ->
pgsql_execute_escape() ->
#sql_escape{string = fun(X) -> X end,
integer = fun(X) -> [misc:i2l(X)] end,
boolean = fun(true) -> "1";
integer = fun(X) -> [misc:i2l(X)] end,
boolean = fun(true) -> "1";
(false) -> "0"
end
end,
in_array_string = fun(X) -> <<"\"", (escape(X))/binary, "\"">> end
}.
pgsql_execute_sql_query(SQLQuery, State) ->
@@ -965,6 +989,7 @@ get_db_version(State) ->
log(Level, Format, Args) ->
case Level of
debug -> ?DEBUG(Format, Args);
info -> ?INFO_MSG(Format, Args);
normal -> ?INFO_MSG(Format, Args);
error -> ?ERROR_MSG(Format, Args)
end.
+62 -9
View File
@@ -42,7 +42,8 @@
res_pos = 0,
server_host_used = false,
used_vars = [],
use_new_schema}).
use_new_schema,
need_array_pass = false}).
-define(QUERY_RECORD, "sql_query").
@@ -183,12 +184,24 @@ transform_sql(Arg) ->
Pos, no_server_host),
[]
end,
set_pos(
make_schema_check(
make_sql_query(ParseRes),
make_sql_query(ParseResOld)
),
Pos).
case ParseRes#state.need_array_pass of
true ->
{PR1, PR2} = perform_array_pass(ParseRes),
{PRO1, PRO2} = perform_array_pass(ParseResOld),
set_pos(make_schema_check(
erl_syntax:list([erl_syntax:tuple([erl_syntax:atom(pgsql), make_sql_query(PR2)]),
erl_syntax:tuple([erl_syntax:atom(any), make_sql_query(PR1)])]),
erl_syntax:list([erl_syntax:tuple([erl_syntax:atom(pgsql), make_sql_query(PRO2)]),
erl_syntax:tuple([erl_syntax:atom(any), make_sql_query(PRO1)])])),
Pos);
false ->
set_pos(
make_schema_check(
make_sql_query(ParseRes),
make_sql_query(ParseResOld)
),
Pos)
end.
transform_upsert(Form, TableArg, FieldsArg) ->
Table = erl_syntax:string_value(TableArg),
@@ -315,8 +328,23 @@ parse1([$%, $( | S], Acc, State) ->
erl_syntax:atom(?ESCAPE_RECORD),
erl_syntax:atom(InternalType)),
erl_syntax:variable(Name)]),
State2#state{'query' = [{var, Var} | State2#state.'query'],
args = [Convert | State2#state.args],
IT2 = case InternalType of
string ->
in_array_string;
_ ->
InternalType
end,
ConvertArr = erl_syntax:application(
erl_syntax:atom(ejabberd_sql),
erl_syntax:atom(to_array),
[erl_syntax:record_access(
erl_syntax:variable(?ESCAPE_VAR),
erl_syntax:atom(?ESCAPE_RECORD),
erl_syntax:atom(IT2)),
erl_syntax:variable(Name)]),
State2#state{'query' = [[{var, Var}] | State2#state.'query'],
need_array_pass = true,
args = [[Convert, ConvertArr] | State2#state.args],
params = [Var | State2#state.params],
param_pos = State2#state.param_pos + 1,
used_vars = [Name | State2#state.used_vars]};
@@ -389,6 +417,31 @@ make_var(V) ->
Var = "__V" ++ integer_to_list(V),
erl_syntax:variable(Var).
perform_array_pass(State) ->
{NQ, PQ, Rest} = lists:foldl(
fun([{var, _} = Var], {N, P, {str, Str} = Prev}) ->
Str2 = re:replace(Str, "(^|\s+)in\s*$", " = any(", [{return, list}]),
{[Var, Prev | N], [{str, ")"}, Var, {str, Str2} | P], none};
([{var, _}], _) ->
throw({error, State#state.loc, ["List variable not following 'in' operator"]});
(Other, {N, P, none}) ->
{N, P, Other};
(Other, {N, P, Prev}) ->
{[Prev | N], [Prev | P], Other}
end, {[], [], none}, State#state.query),
{NQ2, PQ2} = case Rest of
none ->
{NQ, PQ};
_ -> {[Rest | NQ], [Rest | PQ]}
end,
{NA, PA} = lists:foldl(
fun([V1, V2], {N, P}) ->
{[V1 | N], [V2 | P]};
(Other, {N, P}) ->
{[Other | N], [Other | P]}
end, {[], []}, State#state.args),
{State#state{query = lists:reverse(NQ2), args = lists:reverse(NA), need_array_pass = false},
State#state{query = lists:reverse(PQ2), args = lists:reverse(PA), need_array_pass = false}}.
make_sql_query(State) ->
Hash = erlang:phash2(State#state{loc = undefined, use_new_schema = true}),
+9 -9
View File
@@ -30,7 +30,7 @@
-ifndef(STUN).
-include("logger.hrl").
-export([accept/1, start/2, start_link/2, listen_options/0]).
-export([accept/1, start/3, start_link/3, listen_options/0]).
fail() ->
?CRITICAL_MSG("Listening module ~s is not available: "
"ejabberd is not compiled with STUN/TURN support",
@@ -40,13 +40,13 @@ accept(_) ->
fail().
listen_options() ->
fail().
start(_, _) ->
start(_, _, _) ->
fail().
start_link(_, _) ->
start_link(_, _, _) ->
fail().
-else.
-export([tcp_init/2, udp_init/2, udp_recv/5, start/2,
start_link/2, accept/1, listen_opt_type/1, listen_options/0]).
-export([tcp_init/2, udp_init/2, udp_recv/5, start/3,
start_link/3, accept/1, listen_opt_type/1, listen_options/0]).
-include("logger.hrl").
@@ -64,11 +64,11 @@ udp_init(Socket, Opts) ->
udp_recv(Socket, Addr, Port, Packet, Opts) ->
stun:udp_recv(Socket, Addr, Port, Packet, Opts).
start(Opaque, Opts) ->
stun:start(Opaque, Opts).
start(SockMod, Socket, Opts) ->
stun:start({SockMod, Socket}, Opts).
start_link({gen_tcp, Sock}, Opts) ->
stun:start_link(Sock, Opts).
start_link(_SockMod, Socket, Opts) ->
stun:start_link(Socket, Opts).
accept(_Pid) ->
ok.
+6 -6
View File
@@ -94,7 +94,7 @@ handle_event({set_alarm, {process_memory_high_watermark, Pid}}, State) ->
case proc_stat(Pid, get_app_pids()) of
#proc_stat{name = Name} = ProcStat ->
error_logger:warning_msg(
"Process ~p consumes more than 5% of OS memory (~s)",
"Process ~p consumes more than 5% of OS memory (~s)~n",
[Name, format_proc(ProcStat)]),
handle_overload(State),
{ok, State};
@@ -104,7 +104,7 @@ handle_event({set_alarm, {process_memory_high_watermark, Pid}}, State) ->
handle_event({clear_alarm, process_memory_high_watermark}, State) ->
{ok, State};
handle_event(Event, State) ->
error_logger:warning_msg("unexpected event: ~p", [Event]),
error_logger:warning_msg("unexpected event: ~p~n", [Event]),
{ok, State}.
handle_call(_Request, State) ->
@@ -114,7 +114,7 @@ handle_info({timeout, _TRef, handle_overload}, State) ->
handle_overload(State),
{ok, restart_timer(State)};
handle_info(Info, State) ->
error_logger:warning_msg("unexpected info: ~p", [Info]),
error_logger:warning_msg("unexpected info: ~p~n", [Info]),
{ok, State}.
terminate(_Reason, _State) ->
@@ -141,7 +141,7 @@ handle_overload(_State, Procs) ->
"The system is overloaded with ~b messages "
"queued by ~b process(es) (~b%) "
"from the following applications: ~s; "
"the top processes are:~n~s",
"the top processes are:~n~s~n",
[TotalMsgs, ProcsNum,
round(ProcsNum*100/length(Procs)),
format_apps(Apps),
@@ -274,7 +274,7 @@ do_kill(Stats, Threshold) ->
true ->
error_logger:warning_msg(
"Unable to kill process ~p from whitelisted "
"application ~p", [Name, App]),
"application ~p~n", [Name, App]),
false;
false ->
case kill_proc(Name) of
@@ -291,7 +291,7 @@ do_kill(Stats, Threshold) ->
TotalKilled = length(Killed),
if TotalKilled > 0 ->
error_logger:error_msg(
"Killed ~b process(es) consuming more than ~b message(s) each",
"Killed ~b process(es) consuming more than ~b message(s) each~n",
[TotalKilled, Threshold]);
true ->
ok
+1 -1
View File
@@ -1463,7 +1463,7 @@ user_parse_query1(Action, User, Server, Query) ->
end.
list_last_activity(Host, Lang, Integral, Period) ->
TimeStamp = p1_time_compat:system_time(seconds),
TimeStamp = erlang:system_time(second),
case Period of
<<"all">> -> TS = 0, Days = infinity;
<<"year">> -> TS = TimeStamp - 366 * 86400, Days = 366;
+74 -27
View File
@@ -37,12 +37,12 @@
%%%----------------------------------------------------------------------
-module(ejabberd_websocket).
-behaviour(ejabberd_config).
-protocol({rfc, 6455}).
-author('ecestari@process-one.net').
-export([check/2, socket_handoff/5]).
-export([socket_handoff/5, opt_type/1]).
-include("logger.hrl").
@@ -62,28 +62,39 @@
?AC_ALLOW_HEADERS, ?AC_MAX_AGE]).
-define(HEADER, [?CT_XML, ?AC_ALLOW_ORIGIN, ?AC_ALLOW_HEADERS]).
check(_Path, Headers) ->
RequiredHeaders = [{'Upgrade', <<"websocket">>},
{'Connection', ignore}, {'Host', ignore},
{<<"Sec-Websocket-Key">>, ignore},
{<<"Sec-Websocket-Version">>, <<"13">>}],
is_valid_websocket_upgrade(_Path, Headers) ->
HeadersToValidate = [{'Upgrade', <<"websocket">>},
{'Connection', ignore},
{'Host', ignore},
{<<"Sec-Websocket-Key">>, ignore},
{<<"Sec-Websocket-Version">>, <<"13">>}],
Res = lists:all(
fun({Tag, Val}) ->
case lists:keyfind(Tag, 1, Headers) of
false ->
false;
{_, _} when Val == ignore ->
true;
{_, HVal} ->
str:to_lower(HVal) == Val
end
end, HeadersToValidate),
F = fun ({Tag, Val}) ->
case lists:keyfind(Tag, 1, Headers) of
false -> true; % header not found, keep in list
{_, HVal} ->
case Val of
ignore -> false; % ignore value -> ok, remove from list
_ ->
% expected value -> ok, remove from list (false)
% value is different, keep in list (true)
str:to_lower(HVal) /= Val
end
end
end,
case lists:filter(F, RequiredHeaders) of
[] -> true;
_MissingHeaders -> false
case {Res, lists:keyfind(<<"Origin">>, 1, Headers), get_origin()} of
{false, _, _} ->
false;
{true, _, []} ->
true;
{true, {_, HVal}, Origins} ->
HValLow = str:to_lower(HVal),
case lists:any(fun(V) -> V == HValLow end, Origins) of
true ->
true;
_ ->
invalid_origin
end;
{true, false, _} ->
true
end.
socket_handoff(LocalPath, #request{method = 'GET', ip = IP, q = Q, path = Path,
@@ -91,7 +102,7 @@ socket_handoff(LocalPath, #request{method = 'GET', ip = IP, q = Q, path = Path,
socket = Socket, sockmod = SockMod,
data = Buf, opts = HOpts},
_Opts, HandlerModule, InfoMsgFun) ->
case check(LocalPath, Headers) of
case is_valid_websocket_upgrade(LocalPath, Headers) of
true ->
WS = #ws{socket = Socket,
sockmod = SockMod,
@@ -106,8 +117,11 @@ socket_handoff(LocalPath, #request{method = 'GET', ip = IP, q = Q, path = Path,
http_opts = HOpts},
connect(WS, HandlerModule);
_ ->
{200, ?HEADER, InfoMsgFun()}
false ->
{200, ?HEADER, InfoMsgFun()};
invalid_origin ->
{403, ?HEADER, #xmlel{name = <<"h1">>,
children = [{xmlcdata, <<"403 Bad Request - Invalid origin">>}]}}
end;
socket_handoff(_, #request{method = 'OPTIONS'}, _, _, _) ->
{200, ?OPTIONS_HEADER, []};
@@ -189,6 +203,9 @@ ws_loop(FrameInfo, Socket, WsHandleLoopPid, SocketMode) ->
{tcp_closed, _Socket} ->
?DEBUG("tcp connection was closed, exit", []),
websocket_close(Socket, WsHandleLoopPid, SocketMode, 0);
{tcp_error, Socket, Reason} ->
?DEBUG("tcp connection error: ~s", [inet:format_error(Reason)]),
websocket_close(Socket, WsHandleLoopPid, SocketMode, 0);
{'DOWN', Ref, process, WsHandleLoopPid, Reason} ->
Code = case Reason of
normal ->
@@ -201,10 +218,14 @@ ws_loop(FrameInfo, Socket, WsHandleLoopPid, SocketMode) ->
end,
erlang:demonitor(Ref),
websocket_close(Socket, WsHandleLoopPid, SocketMode, Code);
{send, Data} ->
{text, Data} ->
SocketMode:send(Socket, encode_frame(Data, 1)),
ws_loop(FrameInfo, Socket, WsHandleLoopPid,
SocketMode);
{data, Data} ->
SocketMode:send(Socket, encode_frame(Data, 2)),
ws_loop(FrameInfo, Socket, WsHandleLoopPid,
SocketMode);
{ping, Data} ->
SocketMode:send(Socket, encode_frame(Data, 9)),
ws_loop(FrameInfo, Socket, WsHandleLoopPid,
@@ -406,3 +427,29 @@ websocket_close(Socket, WsHandleLoopPid,
websocket_close(Socket, WsHandleLoopPid, SocketMode, _CloseCode) ->
WsHandleLoopPid ! closed,
SocketMode:close(Socket).
get_origin() ->
ejabberd_config:get_option(websocket_origin, []).
opt_type(websocket_ping_interval) ->
fun (I) when is_integer(I), I >= 0 -> I end;
opt_type(websocket_timeout) ->
fun (I) when is_integer(I), I > 0 -> I end;
opt_type(websocket_origin) ->
fun Verify(V) when is_binary(V) ->
Verify([V]);
Verify([]) ->
[];
Verify([<<"null">> | R]) ->
[<<"null">> | Verify(R)];
Verify([null | R]) ->
[<<"null">> | Verify(R)];
Verify([V | R]) when is_binary(V) ->
URIs = [_|_] = lists:filtermap(
fun(<<>>) -> false;
(URI) -> {true, misc:try_url(URI)}
end, re:split(V, "\\s+")),
[str:join(URIs, <<" ">>) | Verify(R)]
end;
opt_type(_) ->
[websocket_ping_interval, websocket_timeout, websocket_origin].
+5 -5
View File
@@ -35,7 +35,7 @@
-author('badlop@process-one.net').
-export([start/2, start_link/2, handler/2, process/2, accept/1,
-export([start/3, start_link/3, handler/2, process/2, accept/1,
transform_listen_option/2, listen_opt_type/1, listen_options/0]).
-include("logger.hrl").
@@ -188,11 +188,11 @@
%% Listener interface
%% -----------------------------
start({gen_tcp = _SockMod, Socket}, Opts) ->
ejabberd_http:start({gen_tcp, Socket}, [{xmlrpc, true}|Opts]).
start(gen_tcp = _SockMod, Socket, Opts) ->
ejabberd_http:start(gen_tcp, Socket, [{xmlrpc, true}|Opts]).
start_link({gen_tcp = _SockMod, Socket}, Opts) ->
ejabberd_http:start_link({gen_tcp, Socket}, [{xmlrpc, true}|Opts]).
start_link(gen_tcp = _SockMod, Socket, Opts) ->
ejabberd_http:start_link(gen_tcp, Socket, [{xmlrpc, true}|Opts]).
accept(Pid) ->
ejabberd_http:accept(Pid).
+1 -1
View File
@@ -103,7 +103,7 @@ normalize_pid(Metadata) ->
%% Return timestamp with milliseconds
timestamp(Time, UTCLog) ->
{_, _, Micro} = p1_time_compat:timestamp(),
{_, _, Micro} = erlang:timestamp(),
{Date, {Hours, Minutes, Seconds}} =
case UTCLog of
true -> calendar:now_to_universal_time(Time);
+6 -3
View File
@@ -32,7 +32,7 @@
-export([start_link/0, update/0, check/1,
available_command/0, available/0, available/1,
installed_command/0, installed/0, installed/1,
install/1, uninstall/1, upgrade/0, upgrade/1,
install/1, uninstall/1, upgrade/0, upgrade/1, add_paths/0,
add_sources/1, add_sources/2, del_sources/1, modules_dir/0,
config_dir/0, opt_type/1, get_commands_spec/0]).
@@ -54,13 +54,16 @@ start_link() ->
init([]) ->
process_flag(trap_exit, true),
[code:add_patha(module_ebin_dir(Module))
|| {Module, _} <- installed()],
add_paths(),
application:start(inets),
inets:start(httpc, [{profile, ext_mod}]),
ejabberd_commands:register_commands(get_commands_spec()),
{ok, #state{}}.
add_paths() ->
[code:add_patha(module_ebin_dir(Module))
|| {Module, _} <- installed()].
handle_call(_Request, _From, State) ->
Reply = ok,
{reply, Reply, State}.
+2 -2
View File
@@ -171,7 +171,7 @@ code_change(_OldVsn, State, _Extra) ->
%%%===================================================================
-spec curr_time() -> non_neg_integer().
curr_time() ->
p1_time_compat:monotonic_time(milli_seconds).
erlang:monotonic_time(millisecond).
-spec start_port(string()) -> {port(), integer() | undefined}.
start_port(Path) ->
@@ -188,7 +188,7 @@ call_port(Server, Args) ->
call_port(Server, Args, ?CALL_TIMEOUT).
call_port(Server, Args, Timeout) ->
StartTime = p1_time_compat:monotonic_time(milli_seconds),
StartTime = erlang:monotonic_time(millisecond),
Pool = pool_name(Server),
PoolSize = pool_size(Server),
I = p1_rand:round_robin(PoolSize),
+31 -6
View File
@@ -44,7 +44,7 @@
%% Deprecated functions
-export([get_opt/3, get_opt/4, get_module_opt/4, get_module_opt/5,
get_opt_host/3, get_opt_hosts/3, db_type/2, db_type/3,
ram_db_type/2, ram_db_type/3]).
ram_db_type/2, ram_db_type/3, update_module_opts/3]).
-deprecated([{get_opt, 3},
{get_opt, 4},
{get_opt_host, 3},
@@ -305,6 +305,20 @@ store_options(Host, Module, Opts, Order) ->
#ejabberd_module{module_host = {Module, Host},
opts = Opts, order = Order}).
-spec update_module_opts(binary(), module(), opts()) -> ok | {ok, pid()} | error.
update_module_opts(Host, Module, NewValues) ->
case ets:lookup(ejabberd_modules, {Module, Host}) of
[#ejabberd_module{opts = Opts, order = Order}] ->
NewOpts = lists:foldl(
fun({K, _} = KV, Acc) ->
lists:keystore(K, 1, Acc, KV)
end, Opts, NewValues),
reload_module(Host, Module, NewOpts, Opts, Order);
Other ->
?WARNING_MSG("Unable to update module opts: (~p, ~p) -> ~p",
[Host, Module, Other])
end.
maybe_halt_ejabberd() ->
case is_app_running(ejabberd) of
false ->
@@ -364,8 +378,13 @@ stop_module_keep_config(Host, Module) ->
end.
wait_for_process(Process) ->
MonitorReference = erlang:monitor(process, Process),
wait_for_stop(Process, MonitorReference).
try erlang:monitor(process, Process) of
MonitorReference ->
wait_for_stop(Process, MonitorReference)
catch
_:_ ->
ok
end.
wait_for_stop(Process, MonitorReference) ->
receive
@@ -563,8 +582,10 @@ validate_opts(Host, Module, Opts0) ->
module_error(ErrTxt);
_:{unknown_option, Opt, KnownOpts} ->
ErrTxt = io_lib:format("Unknown option '~s' of module '~s',"
" available options are: ~s",
" did you mean '~s'?"
" Available options are: ~s",
[Opt, Module,
misc:best_match(Opt, KnownOpts),
misc:join_atoms(KnownOpts, <<", ">>)]),
module_error(ErrTxt)
end.
@@ -719,11 +740,15 @@ format_module_error(Module, Fun, Arity, Opts, Class, Reason, St) ->
IsCallbackExported = erlang:function_exported(Module, Fun, Arity),
case {Class, Reason} of
{error, undef} when not IsLoaded ->
io_lib:format("Failed to ~s unknown module ~s: "
io_lib:format("Failed to ~s unknown module ~s, "
"did you mean ~s? Hint: "
"make sure there is no typo and ~s.beam "
"exists inside either ~s or ~s "
"directory",
[Fun, Module, Module,
[Fun, Module,
misc:best_match(
Module, ejabberd_config:get_modules()),
Module,
filename:dirname(code:which(?MODULE)),
ext_mod:modules_dir()]);
{error, undef} when not IsCallbackExported ->
+69 -10
View File
@@ -38,7 +38,8 @@
compile_exprs/2, join_atoms/2, try_read_file/1, get_descr/2,
css_dir/0, img_dir/0, js_dir/0, msgs_dir/0, sql_dir/0, lua_dir/0,
read_css/1, read_img/1, read_js/1, read_lua/1, try_url/1,
intersection/2, format_val/1, cancel_timer/1]).
intersection/2, format_val/1, cancel_timer/1, unique_timestamp/0,
is_mucsub_message/1, best_match/2]).
%% Deprecated functions
-export([decode_base64/1, encode_base64/1]).
@@ -58,16 +59,18 @@ add_delay_info(Stz, From, Time) ->
-spec add_delay_info(stanza(), jid(), erlang:timestamp(), binary()) -> stanza().
add_delay_info(Stz, From, Time, Desc) ->
NewDelay = #delay{stamp = Time, from = From, desc = Desc},
case xmpp:get_subtag(Stz, #delay{stamp = {0,0,0}}) of
#delay{from = OldFrom} when is_record(OldFrom, jid) ->
case jid:tolower(From) == jid:tolower(OldFrom) of
true ->
Stz;
false ->
xmpp:append_subtags(Stz, [NewDelay])
end;
Delays = xmpp:get_subtags(Stz, #delay{stamp = {0,0,0}}),
Matching = lists:any(
fun(#delay{from = OldFrom}) when is_record(OldFrom, jid) ->
jid:tolower(From) == jid:tolower(OldFrom);
(_) ->
false
end, Delays),
case Matching of
true ->
Stz;
_ ->
NewDelay = #delay{stamp = Time, from = From, desc = Desc},
xmpp:append_subtags(Stz, [NewDelay])
end.
@@ -109,6 +112,26 @@ unwrap_mucsub_message(#message{} = OuterMsg) ->
unwrap_mucsub_message(_Packet) ->
false.
-spec is_mucsub_message(xmpp_element()) -> boolean().
is_mucsub_message(#message{} = OuterMsg) ->
case xmpp:get_subtag(OuterMsg, #ps_event{}) of
#ps_event{
items = #ps_items{
node = Node}}
when Node == ?NS_MUCSUB_NODES_MESSAGES;
Node == ?NS_MUCSUB_NODES_SUBJECT;
Node == ?NS_MUCSUB_NODES_AFFILIATIONS;
Node == ?NS_MUCSUB_NODES_CONFIG;
Node == ?NS_MUCSUB_NODES_PARTICIPANTS;
Node == ?NS_MUCSUB_NODES_PRESENCE;
Node == ?NS_MUCSUB_NODES_SUBSCRIBERS ->
true;
_ ->
false
end;
is_mucsub_message(_Packet) ->
false.
-spec is_standalone_chat_state(stanza()) -> boolean().
is_standalone_chat_state(Stanza) ->
case unwrap_carbon(Stanza) of
@@ -402,6 +425,18 @@ cancel_timer(TRef) when is_reference(TRef) ->
cancel_timer(_) ->
ok.
-spec best_match(atom(), [atom()]) -> atom().
best_match(Pattern, []) ->
Pattern;
best_match(Pattern, Opts) ->
String = atom_to_list(Pattern),
{Ds, _} = lists:mapfoldl(
fun(Opt, Cache) ->
{Distance, Cache1} = ld(String, atom_to_list(Opt), Cache),
{{Distance, Opt}, Cache1}
end, #{}, Opts),
element(2, lists:min(Ds)).
%%%===================================================================
%%% Internal functions
%%%===================================================================
@@ -456,3 +491,27 @@ get_dir(Type) ->
Path ->
Path
end.
%% Generates erlang:timestamp() that is guaranteed to unique
-spec unique_timestamp() -> erlang:timestamp().
unique_timestamp() ->
{MS, S, _} = erlang:timestamp(),
{MS, S, erlang:unique_integer([positive, monotonic]) rem 1000000}.
%% Levenshtein distance
-spec ld(string(), string(), map()) -> {non_neg_integer(), map()}.
ld([] = S, T, Cache) ->
{length(T), maps:put({S, T}, length(T), Cache)};
ld(S, [] = T, Cache) ->
{length(S), maps:put({S, T}, length(S), Cache)};
ld([X|S], [X|T], Cache) ->
ld(S, T, Cache);
ld([_|ST] = S, [_|TT] = T, Cache) ->
try {maps:get({S, T}, Cache), Cache}
catch _:{badkey, _} ->
{L1, C1} = ld(S, TT, Cache),
{L2, C2} = ld(ST, T, C1),
{L3, C3} = ld(ST, TT, C2),
L = 1 + lists:min([L1, L2, L3]),
{L, maps:put({S, T}, L, C3)}
end.
+7 -2
View File
@@ -215,10 +215,10 @@ process_adhoc_request(#iq{from = From, to = To,
Res = case Type of
local ->
ejabberd_hooks:run_fold(adhoc_local_commands, Host, empty,
[From, To, SubEl]);
[From, To, fix_lang(Lang, SubEl)]);
sm ->
ejabberd_hooks:run_fold(adhoc_sm_commands, Host, empty,
[From, To, SubEl])
[From, To, fix_lang(Lang, SubEl)])
end,
case Res of
ignore ->
@@ -266,6 +266,11 @@ ping_command(_Acc, _From, _To,
end;
ping_command(Acc, _From, _To, _Request) -> Acc.
fix_lang(Lang, #adhoc_command{lang = <<>>} = Cmd) ->
Cmd#adhoc_command{lang = Lang};
fix_lang(_, Cmd) ->
Cmd.
depends(_Host, _Opts) ->
[].
+43 -176
View File
@@ -57,7 +57,7 @@
% Roster
add_rosteritem/7, delete_rosteritem/4,
process_rosteritems/5, get_roster/2, push_roster/3,
get_roster/2, push_roster/3,
push_roster_all/1, push_alltoall/2,
push_roster_item/5, build_roster_item/3,
@@ -506,7 +506,7 @@ get_commands_spec() ->
args_desc = ["User name", "Server name", "Contact user name", "Contact server name"],
result = {res, rescode}},
#ejabberd_commands{name = process_rosteritems, tags = [roster],
desc = "List/delete rosteritems that match filter (only Mnesia)",
desc = "List/delete rosteritems that match filter",
longdesc = "Explanation of each argument:\n"
" - action: what to do with each rosteritem that "
"matches all the filtering options\n"
@@ -515,6 +515,8 @@ get_commands_spec() ->
" - users: the JIDs of the local user\n"
" - contacts: the JIDs of the contact in the roster\n"
"\n"
" *** Mnesia: \n"
"\n"
"Allowed values in the arguments:\n"
" ACTION = list | delete\n"
" SUBS = SUB[:SUB]* | any\n"
@@ -532,8 +534,26 @@ get_commands_spec() ->
"'example.org' and that the contact JID is either a "
"bare server name (without user part) or that has a "
"user part and the server part contains the word 'icq'"
":\n list none:from:to any *@example.org *:*@*icq*",
module = ?MODULE, function = process_rosteritems,
":\n list none:from:to any *@example.org *:*@*icq*"
"\n\n"
" *** SQL:\n"
"\n"
"Allowed values in the arguments:\n"
" ACTION = list | delete\n"
" SUBS = any | none | from | to | both\n"
" ASKS = any | none | out | in\n"
" USERS = JID\n"
" CONTACTS = JID\n"
" JID = characters valid in a JID, and can use the "
"globs: _ and %\n"
"\n"
"This example will list roster items with subscription "
"'to' that have any ask property, of "
"local users which JID is in the virtual host "
"'example.org' and that the contact JID's "
"server part contains the word 'icq'"
":\n list to any %@example.org %@%icq%",
module = mod_roster, function = process_rosteritems,
args = [{action, string}, {subs, string},
{asks, string}, {users, string},
{contacts, string}],
@@ -858,7 +878,7 @@ delete_old_users_vhost(Host, Days) ->
delete_old_users(Days, Users) ->
SecOlder = Days*24*60*60,
TimeStamp_now = p1_time_compat:system_time(seconds),
TimeStamp_now = erlang:system_time(second),
TimeStamp_oldest = TimeStamp_now - SecOlder,
F = fun({LUser, LServer}) ->
case catch delete_or_not(LUser, LServer, TimeStamp_oldest) of
@@ -1213,34 +1233,25 @@ update_vcard_els(Data, ContentList, Els1) ->
%%%
add_rosteritem(LocalUser, LocalServer, User, Server, Nick, Group, Subs) ->
case add_rosteritem(LocalUser, LocalServer, User, Server, Nick, Group, Subs, []) of
{atomic, ok} ->
push_roster_item(LocalUser, LocalServer, User, Server, {add, Nick, Subs, Group}),
ok;
_ ->
error
Jid = jid:make(LocalUser, LocalServer),
RosterItem = build_roster_item(User, Server, {add, Nick, Subs, Group}),
case mod_roster:set_item_and_notify_clients(Jid, RosterItem, true) of
ok -> ok;
_ -> error
end.
add_rosteritem(LU, LS, User, Server, Nick, Group, Subscription, Xattrs) ->
subscribe(LU, LS, User, Server, Nick, Group, Subscription, Xattrs).
subscribe(LU, LS, User, Server, Nick, Group, Subscription, _Xattrs) ->
ItemEl = build_roster_item(User, Server, {add, Nick, Subscription, Group}),
mod_roster:set_items(LU, LS, #roster_query{items = [ItemEl]}).
delete_rosteritem(LocalUser, LocalServer, User, Server) ->
case unsubscribe(LocalUser, LocalServer, User, Server) of
{atomic, ok} ->
push_roster_item(LocalUser, LocalServer, User, Server, remove),
ok;
_ ->
error
Jid = jid:make(LocalUser, LocalServer),
RosterItem = build_roster_item(User, Server, remove),
case mod_roster:set_item_and_notify_clients(Jid, RosterItem, true) of
ok -> ok;
_ -> error
end.
unsubscribe(LU, LS, User, Server) ->
ItemEl = build_roster_item(User, Server, remove),
mod_roster:set_items(LU, LS, #roster_query{items = [ItemEl]}).
%% -----------------------------
%% Get Roster
%% -----------------------------
@@ -1360,12 +1371,12 @@ get_last(User, Server) ->
[] ->
case mod_last:get_last_info(User, Server) of
not_found ->
{p1_time_compat:timestamp(), "NOT FOUND"};
{erlang:timestamp(), "NOT FOUND"};
{ok, Shift, Status1} ->
{{Shift div 1000000, Shift rem 1000000, 0}, Status1}
end;
_ ->
{p1_time_compat:timestamp(), "ONLINE"}
{erlang:timestamp(), "ONLINE"}
end,
{xmpp_util:encode_timestamp(Now), Status}.
@@ -1535,164 +1546,20 @@ stats(Name, Host) ->
end.
%%-----------------------------
%% Purge roster items
%%-----------------------------
process_rosteritems(ActionS, SubsS, AsksS, UsersS, ContactsS) ->
Action = case ActionS of
"list" -> list;
"delete" -> delete
end,
Subs = lists:foldl(
fun(any, _) -> [none, from, to, both];
(Sub, Subs) -> [Sub | Subs]
end,
[],
[list_to_atom(S) || S <- string:tokens(SubsS, ":")]
),
Asks = lists:foldl(
fun(any, _) -> [none, out, in];
(Ask, Asks) -> [Ask | Asks]
end,
[],
[list_to_atom(S) || S <- string:tokens(AsksS, ":")]
),
Users = lists:foldl(
fun("any", _) -> ["*", "*@*"];
(U, Us) -> [U | Us]
end,
[],
[S || S <- string:tokens(UsersS, ":")]
),
Contacts = lists:foldl(
fun("any", _) -> ["*", "*@*"];
(U, Us) -> [U | Us]
end,
[],
[S || S <- string:tokens(ContactsS, ":")]
),
rosteritem_purge({Action, Subs, Asks, Users, Contacts}).
%% @spec ({Action::atom(), Subs::[atom()], Asks::[atom()], User::string(), Contact::string()}) -> {atomic, ok}
rosteritem_purge(Options) ->
Num_rosteritems = mnesia:table_info(roster, size),
io:format("There are ~p roster items in total.~n", [Num_rosteritems]),
Key = mnesia:dirty_first(roster),
rip(Key, Options, {0, Num_rosteritems, 0, 0}, []).
rip('$end_of_table', _Options, Counters, Res) ->
print_progress_line(Counters),
Res;
rip(Key, Options, {Pr, NT, NV, ND}, Res) ->
Key_next = mnesia:dirty_next(roster, Key),
{Action, _, _, _, _} = Options,
{ND2, Res2} = case decide_rip(Key, Options) of
true ->
Jids = apply_action(Action, Key),
{ND+1, [Jids | Res]};
false ->
{ND, Res}
end,
NV2 = NV+1,
Pr2 = print_progress_line({Pr, NT, NV2, ND2}),
rip(Key_next, Options, {Pr2, NT, NV2, ND2}, Res2).
apply_action(list, Key) ->
{User, Server, JID} = Key,
{RUser, RServer, _} = JID,
Jid1string = <<User/binary, "@", Server/binary>>,
Jid2string = <<RUser/binary, "@", RServer/binary>>,
io:format("Matches: ~s ~s~n", [Jid1string, Jid2string]),
{Jid1string, Jid2string};
apply_action(delete, Key) ->
R = apply_action(list, Key),
mnesia:dirty_delete(roster, Key),
R.
print_progress_line({_Pr, 0, _NV, _ND}) ->
ok;
print_progress_line({Pr, NT, NV, ND}) ->
Pr2 = trunc((NV/NT)*100),
case Pr == Pr2 of
true ->
ok;
false ->
io:format("Progress ~p% - visited ~p - deleted ~p~n", [Pr2, NV, ND])
end,
Pr2.
decide_rip(Key, {_Action, Subs, Asks, User, Contact}) ->
case catch mnesia:dirty_read(roster, Key) of
[RI] ->
lists:member(RI#roster.subscription, Subs)
andalso lists:member(RI#roster.ask, Asks)
andalso decide_rip_jid(RI#roster.us, User)
andalso decide_rip_jid(RI#roster.jid, Contact);
_ ->
false
end.
%% Returns true if the server of the JID is included in the servers
decide_rip_jid({UName, UServer, _UResource}, Match_list) ->
decide_rip_jid({UName, UServer}, Match_list);
decide_rip_jid({UName, UServer}, Match_list) ->
lists:any(
fun(Match_string) ->
MJID = jid:decode(list_to_binary(Match_string)),
MName = MJID#jid.luser,
MServer = MJID#jid.lserver,
Is_server = is_glob_match(UServer, MServer),
case MName of
<<>> when UName == <<>> ->
Is_server;
<<>> ->
false;
_ ->
Is_server
andalso is_glob_match(UName, MName)
end
end,
Match_list).
user_action(User, Server, Fun, OK) ->
case ejabberd_auth:user_exists(User, Server) of
true ->
case catch Fun() of
case catch Fun() of
OK -> ok;
{error, Error} -> throw(Error);
{error, Error} -> throw(Error);
Error ->
?ERROR_MSG("Command returned: ~p", [Error]),
1
end;
false ->
throw({not_found, "unknown_user"})
1
end;
false ->
throw({not_found, "unknown_user"})
end.
%% Copied from ejabberd-2.0.0/src/acl.erl
is_regexp_match(String, RegExp) ->
case ejabberd_regexp:run(String, RegExp) of
nomatch ->
false;
match ->
true;
{error, ErrDesc} ->
io:format(
"Wrong regexp ~p in ACL: ~p",
[RegExp, ErrDesc]),
false
end.
is_glob_match(String, <<"!", Glob/binary>>) ->
not is_regexp_match(String, ejabberd_regexp:sh_to_awk(Glob));
is_glob_match(String, Glob) ->
is_regexp_match(String, ejabberd_regexp:sh_to_awk(Glob)).
num_prio(Priority) when is_integer(Priority) ->
Priority;
num_prio(_) ->
+31 -37
View File
@@ -468,18 +468,24 @@ announce_commands(From, To,
sid = SID,
xdata = XData,
action = Action} = Request) ->
ActionIsExecute = Action == execute orelse Action == complete,
if Action == cancel ->
%% User cancels request
#adhoc_command{status = canceled, lang = Lang, node = Node,
sid = SID};
XData == undefined, ActionIsExecute ->
XData == undefined andalso Action == execute ->
%% User requests form
Form = generate_adhoc_form(Lang, Node, To#jid.lserver),
#adhoc_command{status = executing, lang = Lang, node = Node,
sid = SID, xdata = Form};
XData /= undefined, ActionIsExecute ->
handle_adhoc_form(From, To, Request);
xmpp_util:make_adhoc_response(
#adhoc_command{status = executing, lang = Lang, node = Node,
sid = SID, xdata = Form});
XData /= undefined andalso (Action == execute orelse Action == complete) ->
case handle_adhoc_form(From, To, Request) of
ok ->
#adhoc_command{lang = Lang, node = Node, sid = SID,
status = completed};
{error, _} = Err ->
Err
end;
true ->
Txt = <<"Unexpected action">>,
{error, xmpp:err_bad_request(Txt, Lang)}
@@ -496,10 +502,10 @@ vvaluel(Val) ->
generate_adhoc_form(Lang, Node, ServerHost) ->
LNode = tokenize(Node),
{OldSubject, OldBody} = if (LNode == ?NS_ADMINL("edit-motd"))
{OldSubject, OldBody} = if (LNode == ?NS_ADMINL("edit-motd"))
or (LNode == ?NS_ADMINL("edit-motd-allhosts")) ->
get_stored_motd(ServerHost);
true ->
true ->
{<<>>, <<>>}
end,
Fs = if (LNode == ?NS_ADMINL("delete-motd"))
@@ -536,7 +542,7 @@ join_lines([], Acc) ->
handle_adhoc_form(From, #jid{lserver = LServer} = To,
#adhoc_command{lang = Lang, node = Node,
sid = SessionID, xdata = XData}) ->
xdata = XData}) ->
Confirm = case xmpp_util:get_xdata_values(<<"confirm">>, XData) of
[<<"true">>] -> true;
[<<"1">>] -> true;
@@ -544,8 +550,6 @@ handle_adhoc_form(From, #jid{lserver = LServer} = To,
end,
Subject = join_lines(xmpp_util:get_xdata_values(<<"subject">>, XData)),
Body = join_lines(xmpp_util:get_xdata_values(<<"body">>, XData)),
Response = #adhoc_command{lang = Lang, node = Node, sid = SessionID,
status = completed},
Packet = #message{from = From,
to = To,
type = headline,
@@ -555,17 +559,15 @@ handle_adhoc_form(From, #jid{lserver = LServer} = To,
case {Node, Body} of
{?NS_ADMIN_DELETE_MOTD, _} ->
if Confirm ->
gen_server:cast(Proc, {announce_motd_delete, Packet}),
Response;
gen_server:cast(Proc, {announce_motd_delete, Packet});
true ->
Response
ok
end;
{?NS_ADMIN_DELETE_MOTD_ALLHOSTS, _} ->
if Confirm ->
gen_server:cast(Proc, {announce_all_hosts_motd_delete, Packet}),
Response;
gen_server:cast(Proc, {announce_all_hosts_motd_delete, Packet});
true ->
Response
ok
end;
{_, <<>>} ->
%% An announce message with no body is definitely an operator error.
@@ -576,29 +578,21 @@ handle_adhoc_form(From, #jid{lserver = LServer} = To,
%% We don't use direct announce_* functions because it
%% leads to large delay in response and <iq/> queries processing
{?NS_ADMIN_ANNOUNCE, _} ->
gen_server:cast(Proc, {announce_online, Packet}),
Response;
{?NS_ADMIN_ANNOUNCE_ALLHOSTS, _} ->
gen_server:cast(Proc, {announce_all_hosts_online, Packet}),
Response;
gen_server:cast(Proc, {announce_online, Packet});
{?NS_ADMIN_ANNOUNCE_ALLHOSTS, _} ->
gen_server:cast(Proc, {announce_all_hosts_online, Packet});
{?NS_ADMIN_ANNOUNCE_ALL, _} ->
gen_server:cast(Proc, {announce_all, Packet}),
Response;
{?NS_ADMIN_ANNOUNCE_ALL_ALLHOSTS, _} ->
gen_server:cast(Proc, {announce_all_hosts_all, Packet}),
Response;
gen_server:cast(Proc, {announce_all, Packet});
{?NS_ADMIN_ANNOUNCE_ALL_ALLHOSTS, _} ->
gen_server:cast(Proc, {announce_all_hosts_all, Packet});
{?NS_ADMIN_SET_MOTD, _} ->
gen_server:cast(Proc, {announce_motd, Packet}),
Response;
{?NS_ADMIN_SET_MOTD_ALLHOSTS, _} ->
gen_server:cast(Proc, {announce_all_hosts_motd, Packet}),
Response;
gen_server:cast(Proc, {announce_motd, Packet});
{?NS_ADMIN_SET_MOTD_ALLHOSTS, _} ->
gen_server:cast(Proc, {announce_all_hosts_motd, Packet});
{?NS_ADMIN_EDIT_MOTD, _} ->
gen_server:cast(Proc, {announce_motd_update, Packet}),
Response;
{?NS_ADMIN_EDIT_MOTD_ALLHOSTS, _} ->
gen_server:cast(Proc, {announce_all_hosts_motd_update, Packet}),
Response;
gen_server:cast(Proc, {announce_motd_update, Packet});
{?NS_ADMIN_EDIT_MOTD_ALLHOSTS, _} ->
gen_server:cast(Proc, {announce_all_hosts_motd_update, Packet});
Junk ->
%% This can't happen, as we haven't registered any other
%% command nodes.
+2 -2
View File
@@ -35,7 +35,7 @@
-include("logger.hrl").
-record(bosh, {sid = <<"">> :: binary() | '_',
timestamp = p1_time_compat:timestamp() :: erlang:timestamp() | '_',
timestamp = erlang:timestamp() :: erlang:timestamp() | '_',
pid = self() :: pid() | '$1'}).
-record(state, {}).
@@ -60,7 +60,7 @@ use_cache() ->
false.
open_session(SID, Pid) ->
Session = #bosh{sid = SID, timestamp = p1_time_compat:timestamp(), pid = Pid},
Session = #bosh{sid = SID, timestamp = erlang:timestamp(), pid = Pid},
lists:foreach(
fun(Node) when Node == node() ->
gen_server:call(?MODULE, {write, Session});
+1 -1
View File
@@ -379,7 +379,7 @@ queue_new() ->
-spec queue_in(csi_key(), stanza(), csi_queue()) -> csi_queue().
queue_in(Key, Stanza, {Seq, Q}) ->
Seq1 = Seq + 1,
Time = {Seq1, p1_time_compat:timestamp()},
Time = {Seq1, erlang:timestamp()},
Q1 = maps:put(Key, {Time, Stanza}, Q),
{Seq1, Q1}.
+1 -1
View File
@@ -1161,7 +1161,7 @@ get_form(_Host, ?NS_ADMINL(<<"get-user-password">>),
get_form(_Host, ?NS_ADMINL(<<"change-user-password">>),
Lang) ->
{result,
#xdata{title = ?T(Lang, <<"Get User Password">>),
#xdata{title = ?T(Lang, <<"Change User Password">>),
type = form,
fields = [?HFIELD(),
#xdata_field{type = 'jid-single',
+9 -6
View File
@@ -1,7 +1,7 @@
%%%-------------------------------------------------------------------
%%% File : mod_fail2ban.erl
%%% Author : Evgeny Khramtsov <ekhramtsov@process-one.net>
%%% Purpose :
%%% Purpose :
%%% Created : 15 Aug 2014 by Evgeny Khramtsov <ekhramtsov@process-one.net>
%%%
%%%
@@ -51,9 +51,12 @@
%%%===================================================================
%%% API
%%%===================================================================
-spec c2s_auth_result(ejabberd_c2s:state(), boolean(), binary())
-spec c2s_auth_result(ejabberd_c2s:state(), true | {false, binary()}, binary())
-> ejabberd_c2s:state() | {stop, ejabberd_c2s:state()}.
c2s_auth_result(#{ip := {Addr, _}, lserver := LServer} = State, false, _User) ->
c2s_auth_result(#{sasl_mech := Mech} = State, {false, _}, _User)
when Mech == <<"EXTERNAL">> ->
State;
c2s_auth_result(#{ip := {Addr, _}, lserver := LServer} = State, {false, _}, _User) ->
case is_whitelisted(LServer, Addr) of
true ->
State;
@@ -62,7 +65,7 @@ c2s_auth_result(#{ip := {Addr, _}, lserver := LServer} = State, false, _User) ->
LServer, ?MODULE, c2s_auth_ban_lifetime),
MaxFailures = gen_mod:get_module_opt(
LServer, ?MODULE, c2s_max_auth_failures),
UnbanTS = p1_time_compat:system_time(seconds) + BanLifetime,
UnbanTS = erlang:system_time(second) + BanLifetime,
Attempts = case ets:lookup(failed_auth, Addr) of
[{Addr, N, _, _}] ->
ets:insert(failed_auth,
@@ -88,7 +91,7 @@ c2s_auth_result(#{ip := {Addr, _}} = State, true, _User) ->
c2s_stream_started(#{ip := {Addr, _}} = State, _) ->
case ets:lookup(failed_auth, Addr) of
[{Addr, N, TS, MaxFailures}] when N >= MaxFailures ->
case TS > p1_time_compat:system_time(seconds) of
case TS > erlang:system_time(second) of
true ->
log_and_disconnect(State, N, TS);
false ->
@@ -143,7 +146,7 @@ handle_cast(_Msg, State) ->
handle_info(clean, State) ->
?DEBUG("cleaning ~p ETS table", [failed_auth]),
Now = p1_time_compat:system_time(seconds),
Now = erlang:system_time(second),
ets:select_delete(
failed_auth,
ets:fun2ms(fun({_, _, UnbanTS, _}) -> UnbanTS =< Now end)),
+21 -12
View File
@@ -293,7 +293,7 @@ handle(Call, Auth, Args, Version) when is_atom(Call), is_list(Args) ->
{401, iolist_to_binary(Msg)};
throw:{error, account_unprivileged} ->
{403, 31, <<"Command need to be run with admin privilege.">>};
throw:{error, access_rules_unauthorized} ->
throw:{error, access_rules_unauthorized} ->
{403, 32, <<"AccessRules: Account does not have the right to perform the operation.">>};
throw:{invalid_parameter, Msg} ->
{400, iolist_to_binary(Msg)};
@@ -306,7 +306,10 @@ handle(Call, Auth, Args, Version) when is_atom(Call), is_list(Args) ->
throw:Msg when is_list(Msg); is_binary(Msg) ->
{400, iolist_to_binary(Msg)};
?EX_RULE(Class, Error, Stack) ->
?ERROR_MSG("REST API Error: ~p:~p ~p", [Class, Error, ?EX_STACK(Stack)]),
?ERROR_MSG("REST API Error: "
"~s(~p) -> ~p:~p ~p",
[Call, hide_sensitive_args(Args),
Class, Error, ?EX_STACK(Stack)]),
{500, <<"internal_error">>}
end;
{error, Msg} ->
@@ -319,7 +322,7 @@ handle(Call, Auth, Args, Version) when is_atom(Call), is_list(Args) ->
handle2(Call, Auth, Args, Version) when is_atom(Call), is_list(Args) ->
{ArgsF, _ResultF} = ejabberd_commands:get_command_format(Call, Auth, Version),
ArgsFormatted = format_args(Args, ArgsF),
ArgsFormatted = format_args(Call, Args, ArgsF),
case ejabberd_commands:execute_command2(Call, ArgsFormatted, Auth, Version) of
{error, Error} ->
throw(Error);
@@ -327,28 +330,32 @@ handle2(Call, Auth, Args, Version) when is_atom(Call), is_list(Args) ->
format_command_result(Call, Auth, Res, Version)
end.
get_elem_delete(A, L, F) ->
get_elem_delete(Call, A, L, F) ->
case proplists:get_all_values(A, L) of
[Value] -> {Value, proplists:delete(A, L)};
[_, _ | _] ->
%% Crash reporting the error
exit({duplicated_attribute, A, L});
?INFO_MSG("Command ~s call rejected, it has duplicate attribute ~w",
[Call, A]),
throw({invalid_parameter,
io_lib:format("Request have duplicate argument: ~w", [A])});
[] ->
case F of
{list, _} ->
{[], L};
_ ->
%% Report the error and then force a crash
exit({attribute_not_found, A, L})
?INFO_MSG("Command ~s call rejected, missing attribute ~w",
[Call, A]),
throw({invalid_parameter,
io_lib:format("Request have missing argument: ~w", [A])})
end
end.
format_args(Args, ArgsFormat) ->
format_args(Call, Args, ArgsFormat) ->
{ArgsRemaining, R} = lists:foldl(fun ({ArgName,
ArgFormat},
{Args1, Res}) ->
{ArgValue, Args2} =
get_elem_delete(ArgName,
get_elem_delete(Call, ArgName,
Args1, ArgFormat),
Formatted = format_arg(ArgValue,
ArgFormat),
@@ -358,9 +365,11 @@ format_args(Args, ArgsFormat) ->
case ArgsRemaining of
[] -> R;
L when is_list(L) ->
ExtraArgs = [N || {N, _} <- L],
?INFO_MSG("Command ~s call rejected, it has unknown arguments ~w",
[Call, ExtraArgs]),
throw({invalid_parameter,
io_lib:format("Request have unknown arguments: ~w",
[[N || {N, _} <- L]])})
io_lib:format("Request have unknown arguments: ~w", [ExtraArgs])})
end.
format_arg({Elements},
+68 -57
View File
@@ -111,7 +111,8 @@
external_secret :: binary()}).
-record(media_info,
{type :: atom(),
{path :: binary(),
type :: atom(),
height :: integer(),
width :: integer()}).
@@ -387,7 +388,7 @@ process(LocalPath, #request{method = Method, host = Host, ip = IP})
process(_LocalPath, #request{method = 'PUT', host = Host, ip = IP,
length = Length} = Request) ->
{Proc, Slot} = parse_http_request(Request),
case catch gen_server:call(Proc, {use_slot, Slot, Length}, ?CALL_TIMEOUT) of
try gen_server:call(Proc, {use_slot, Slot, Length}, ?CALL_TIMEOUT) of
{ok, Path, FileMode, DirMode, GetPrefix, Thumbnail, CustomHeaders} ->
?DEBUG("Storing file from ~s for ~s: ~s",
[encode_addr(IP), Host, Path]),
@@ -413,8 +414,14 @@ process(_LocalPath, #request{method = 'PUT', host = Host, ip = IP,
{error, invalid_slot} ->
?WARNING_MSG("Rejecting file ~s from ~s for ~s: Invalid slot",
[lists:last(Slot), encode_addr(IP), Host]),
http_response(403);
Error ->
http_response(403)
catch
exit:{noproc, _} ->
?WARNING_MSG("Cannot handle PUT request from ~s for ~s: "
"Upload not configured for this host",
[encode_addr(IP), Host]),
http_response(404);
_:Error ->
?ERROR_MSG("Cannot handle PUT request from ~s for ~s: ~p",
[encode_addr(IP), Host, Error]),
http_response(500)
@@ -423,7 +430,7 @@ process(_LocalPath, #request{method = Method, host = Host, ip = IP} = Request)
when Method == 'GET';
Method == 'HEAD' ->
{Proc, [_UserDir, _RandDir, FileName] = Slot} = parse_http_request(Request),
case catch gen_server:call(Proc, get_conf, ?CALL_TIMEOUT) of
try gen_server:call(Proc, get_conf, ?CALL_TIMEOUT) of
{ok, DocRoot, CustomHeaders} ->
Path = str:join([DocRoot | Slot], <<$/>>),
case file:open(Path, [read]) of
@@ -458,8 +465,14 @@ process(_LocalPath, #request{method = Method, host = Host, ip = IP} = Request)
?WARNING_MSG("Cannot serve ~s to ~s: ~s",
[Path, encode_addr(IP), format_error(Error)]),
http_response(500)
end;
Error ->
end
catch
exit:{noproc, _} ->
?WARNING_MSG("Cannot handle ~s request from ~s for ~s: "
"Upload not configured for this host",
[Method, encode_addr(IP), Host]),
http_response(404);
_:Error ->
?ERROR_MSG("Cannot handle ~s request from ~s for ~s: ~p",
[Method, encode_addr(IP), Host, Error]),
http_response(500)
@@ -469,11 +482,17 @@ process(_LocalPath, #request{method = 'OPTIONS', host = Host,
?DEBUG("Responding to OPTIONS request from ~s for ~s",
[encode_addr(IP), Host]),
{Proc, _Slot} = parse_http_request(Request),
case catch gen_server:call(Proc, get_conf, ?CALL_TIMEOUT) of
try gen_server:call(Proc, get_conf, ?CALL_TIMEOUT) of
{ok, _DocRoot, CustomHeaders} ->
AllowHeader = {<<"Allow">>, <<"OPTIONS, HEAD, GET, PUT">>},
http_response(200, [AllowHeader | CustomHeaders]);
Error ->
http_response(200, [AllowHeader | CustomHeaders])
catch
exit:{noproc, _} ->
?WARNING_MSG("Cannot handle OPTIONS request from ~s for ~s: "
"Upload not configured for this host",
[encode_addr(IP), Host]),
http_response(404);
_:Error ->
?ERROR_MSG("Cannot handle OPTIONS request from ~s for ~s: ~p",
[encode_addr(IP), Host, Error]),
http_response(500)
@@ -489,9 +508,12 @@ process(_LocalPath, #request{method = Method, host = Host, ip = IP}) ->
-spec get_proc_name(binary(), atom()) -> atom().
get_proc_name(ServerHost, ModuleName) ->
PutURL = gen_mod:get_module_opt(ServerHost, ?MODULE, put_url),
{ok, {_Scheme, _UserInfo, Host, _Port, Path, _Query}} =
%% Once we depend on OTP >= 20.0, we can use binaries with http_uri.
{ok, {_Scheme, _UserInfo, Host0, _Port, Path0, _Query}} =
http_uri:parse(binary_to_list(expand_host(PutURL, ServerHost))),
ProcPrefix = list_to_binary(string:strip(Host ++ Path, right, $/)),
Host = jid:nameprep(iolist_to_binary(Host0)),
Path = str:strip(iolist_to_binary(Path0), right, $/),
ProcPrefix = <<Host/binary, Path/binary>>,
gen_mod:get_module_proc(ProcPrefix, ModuleName).
-spec expand_home(binary()) -> binary().
@@ -743,7 +765,8 @@ iq_disco_info(Host, Lang, Name, AddInfo) ->
%% HTTP request handling.
-spec parse_http_request(#request{}) -> {atom(), slot()}.
parse_http_request(#request{host = Host, path = Path}) ->
parse_http_request(#request{host = Host0, path = Path}) ->
Host = jid:nameprep(Host0),
PrefixLength = length(Path) - 3,
{ProcURL, Slot} = if PrefixLength > 0 ->
Prefix = lists:sublist(Path, PrefixLength),
@@ -762,10 +785,10 @@ parse_http_request(#request{host = Host, path = Path}) ->
store_file(Path, Request, FileMode, DirMode, GetPrefix, Slot, Thumbnail) ->
case do_store_file(Path, Request, FileMode, DirMode) of
ok when Thumbnail ->
case identify(Path) of
{ok, MediaInfo} ->
case convert(Path, MediaInfo) of
{ok, OutPath, OutMediaInfo} ->
case read_image(Path) of
{ok, Data, MediaInfo} ->
case convert(Data, MediaInfo) of
{ok, #media_info{path = OutPath} = OutMediaInfo} ->
[UserDir, RandDir | _] = Slot,
FileName = filename:basename(OutPath),
URL = str:join([GetPrefix, UserDir,
@@ -810,8 +833,6 @@ do_store_file(Path, Request, FileMode, DirMode) ->
end
catch
_:{badmatch, {error, Error}} ->
{error, Error};
_:Error ->
{error, Error}
end.
@@ -870,30 +891,31 @@ format_error(Reason) ->
%%--------------------------------------------------------------------
%% Image manipulation stuff.
%%--------------------------------------------------------------------
-spec identify(binary()) -> {ok, media_info()} | pass.
identify(Path) ->
try
{ok, Fd} = file:open(Path, [read, raw]),
{ok, Data} = file:read(Fd, 1024),
case eimp:identify(Data) of
{ok, Info} ->
{ok, #media_info{
-spec read_image(binary()) -> {ok, binary(), media_info()} | pass.
read_image(Path) ->
case file:read_file(Path) of
{ok, Data} ->
case eimp:identify(Data) of
{ok, Info} ->
{ok, Data,
#media_info{
path = Path,
type = proplists:get_value(type, Info),
width = proplists:get_value(width, Info),
height = proplists:get_value(height, Info)}};
{error, Why} ->
?DEBUG("Cannot identify type of ~s: ~s",
[Path, eimp:format_error(Why)]),
pass
end
catch _:{badmatch, {error, Reason}} ->
{error, Why} ->
?DEBUG("Cannot identify type of ~s: ~s",
[Path, eimp:format_error(Why)]),
pass
end;
{error, Reason} ->
?DEBUG("Failed to read file ~s: ~s",
[Path, format_error(Reason)]),
pass
end.
-spec convert(binary(), media_info()) -> {ok, binary(), media_info()} | pass.
convert(Path, #media_info{type = T, width = W, height = H} = Info) ->
convert(InData, #media_info{path = Path, type = T, width = W, height = H} = Info) ->
if W * H >= 25000000 ->
?DEBUG("The image ~s is more than 25 Mpix", [Path]),
pass;
@@ -908,27 +930,20 @@ convert(Path, #media_info{type = T, width = W, height = H} = Info) ->
H > W -> {round(W*300/H), 300};
true -> {300, 300}
end,
OutInfo = #media_info{type = T, width = W1, height = H1},
case file:read_file(Path) of
{ok, Data} ->
case eimp:convert(Data, T, [{scale, {W1, H1}}]) of
{ok, OutData} ->
case file:write_file(OutPath, OutData) of
ok ->
{ok, OutPath, OutInfo};
{error, Why} ->
?ERROR_MSG("Failed to write to ~s: ~s",
[OutPath, format_error(Why)]),
pass
end;
OutInfo = #media_info{path = OutPath, type = T, width = W1, height = H1},
case eimp:convert(InData, T, [{scale, {W1, H1}}]) of
{ok, OutData} ->
case file:write_file(OutPath, OutData) of
ok ->
{ok, OutInfo};
{error, Why} ->
?ERROR_MSG("Failed to convert ~s to ~s: ~s",
[Path, OutPath, eimp:format_error(Why)]),
?ERROR_MSG("Failed to write to ~s: ~s",
[OutPath, format_error(Why)]),
pass
end;
{error, Why} ->
?ERROR_MSG("Failed to read file ~s: ~s",
[Path, format_error(Why)]),
?ERROR_MSG("Failed to convert ~s to ~s: ~s",
[Path, OutPath, eimp:format_error(Why)]),
pass
end
end.
@@ -962,9 +977,7 @@ remove_user(User, Server) ->
end,
ok.
-spec del_tree(file:filename_all()) -> ok | {error, term()}.
del_tree(Dir) when is_binary(Dir) ->
del_tree(binary_to_list(Dir));
-spec del_tree(file:filename_all()) -> ok | {error, file:posix()}.
del_tree(Dir) ->
try
{ok, Entries} = file:list_dir(Dir),
@@ -975,11 +988,9 @@ del_tree(Dir) ->
false ->
ok = file:delete(Path)
end
end, [Dir ++ "/" ++ Entry || Entry <- Entries]),
end, [filename:join(Dir, Entry) || Entry <- Entries]),
ok = file:del_dir(Dir)
catch
_:{badmatch, {error, Error}} ->
{error, Error};
_:Error ->
{error, Error}
end.
+3 -3
View File
@@ -117,7 +117,7 @@ get_node_uptime() ->
undefined ->
trunc(element(1, erlang:statistics(wall_clock)) / 1000);
Now ->
p1_time_compat:system_time(seconds) - Now
erlang:system_time(second) - Now
end.
%%%
@@ -209,7 +209,7 @@ get_last_iq(#iq{lang = Lang} = IQ, LUser, LServer) ->
Txt = <<"No info about last activity found">>,
xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang));
{ok, TimeStamp, Status} ->
TimeStamp2 = p1_time_compat:system_time(seconds),
TimeStamp2 = erlang:system_time(second),
Sec = TimeStamp2 - TimeStamp,
xmpp:make_iq_result(IQ, #last{seconds = Sec, status = Status})
end;
@@ -227,7 +227,7 @@ register_user(User, Server) ->
-spec on_presence_update(binary(), binary(), binary(), binary()) -> any().
on_presence_update(User, Server, _Resource, Status) ->
TimeStamp = p1_time_compat:system_time(seconds),
TimeStamp = erlang:system_time(second),
store_last_info(User, Server, TimeStamp, Status).
-spec store_last_info(binary(), binary(), non_neg_integer(), binary()) -> any().
+16 -5
View File
@@ -126,10 +126,11 @@ authenticate(#{stream_id := StreamID, server := Server,
case ejabberd_auth:check_password_with_authmodule(
U, U, JID#jid.lserver, P, D, DGen) of
{true, AuthModule} ->
State1 = ejabberd_c2s:handle_auth_success(
U, <<"legacy">>, AuthModule, State),
State2 = State1#{user := U},
open_session(State2, IQ, R);
State1 = State#{sasl_mech => <<"legacy">>},
State2 = ejabberd_c2s:handle_auth_success(
U, <<"legacy">>, AuthModule, State1),
State3 = State2#{user := U},
open_session(State3, IQ, R);
_ ->
Err = xmpp:make_error(IQ, xmpp:err_not_authorized()),
process_auth_failure(State, U, Err, 'not-authorized')
@@ -157,4 +158,14 @@ open_session(State, IQ, R) ->
-spec process_auth_failure(c2s_state(), binary(), iq(), atom()) -> c2s_state().
process_auth_failure(State, User, StanzaErr, Reason) ->
State1 = ejabberd_c2s:send(State, StanzaErr),
ejabberd_c2s:handle_auth_failure(User, <<"legacy">>, Reason, State1).
State2 = State1#{sasl_mech => <<"legacy">>},
Text = format_reason(Reason),
ejabberd_c2s:handle_auth_failure(User, <<"legacy">>, Text, State2).
-spec format_reason(atom()) -> binary().
format_reason('not-authorized') ->
<<"Invalid username or password">>;
format_reason('forbidden') ->
<<"Access denied by service policy">>;
format_reason('jid-malformed') ->
<<"Malformed XMPP address">>.
+161 -13
View File
@@ -42,7 +42,7 @@
get_room_config/4, set_room_option/3, offline_message/1, export/1,
mod_options/1, remove_mam_for_user_with_peer/3, remove_mam_for_user/2,
is_empty_for_user/2, is_empty_for_room/3, check_create_room/4,
process_iq/3, store_mam_message/7, make_id/0]).
process_iq/3, store_mam_message/7, make_id/0, wrap_as_mucsub/2, select/7]).
-include("xmpp.hrl").
-include("logger.hrl").
@@ -71,13 +71,22 @@
#rsm_set{} | undefined, chat | groupchat) ->
{[{binary(), non_neg_integer(), xmlel()}], boolean(), count()} |
{error, db_failure}.
-callback select(binary(), jid(), jid(), mam_query:result(),
#rsm_set{} | undefined, chat | groupchat,
all | only_count | only_messages) ->
{[{binary(), non_neg_integer(), xmlel()}], boolean(), count()} |
{error, db_failure}.
-callback use_cache(binary()) -> boolean().
-callback cache_nodes(binary()) -> [node()].
-callback remove_from_archive(binary(), binary(), jid() | none) -> ok | {error, any()}.
-callback is_empty_for_user(binary(), binary()) -> boolean().
-callback is_empty_for_room(binary(), binary(), binary()) -> boolean().
-callback select_with_mucsub(binary(), jid(), jid(), mam_query:result(),
#rsm_set{} | undefined, all | only_count | only_messages) ->
{[{binary(), non_neg_integer(), xmlel()}], boolean(), count()} |
{error, db_failure}.
-optional_callbacks([use_cache/1, cache_nodes/1]).
-optional_callbacks([use_cache/1, cache_nodes/1, select_with_mucsub/6, select/6, select/7]).
%%%===================================================================
%%% API
@@ -108,7 +117,7 @@ start(Host, Opts) ->
ejabberd_hooks:add(user_send_packet, Host, ?MODULE,
user_send_packet_strip_tag, 500),
ejabberd_hooks:add(offline_message_hook, Host, ?MODULE,
offline_message, 50),
offline_message, 49),
ejabberd_hooks:add(muc_filter_message, Host, ?MODULE,
muc_filter_message, 50),
ejabberd_hooks:add(muc_process_iq, Host, ?MODULE,
@@ -184,7 +193,7 @@ stop(Host) ->
ejabberd_hooks:delete(user_send_packet, Host, ?MODULE,
user_send_packet_strip_tag, 500),
ejabberd_hooks:delete(offline_message_hook, Host, ?MODULE,
offline_message, 50),
offline_message, 49),
ejabberd_hooks:delete(muc_filter_message, Host, ?MODULE,
muc_filter_message, 50),
ejabberd_hooks:delete(muc_process_iq, Host, ?MODULE,
@@ -440,7 +449,7 @@ muc_filter_message(Acc, _MUCState, _FromNick) ->
-spec make_id() -> binary().
make_id() ->
p1_time_compat:system_time(micro_seconds).
erlang:system_time(microsecond).
-spec get_stanza_id(stanza()) -> integer().
get_stanza_id(#message{meta = #{stanza_id := ID}}) ->
@@ -886,16 +895,20 @@ may_enter_room(From, MUCState) ->
store_msg(Pkt, LUser, LServer, Peer, Dir) ->
case get_prefs(LUser, LServer) of
{ok, Prefs} ->
case {should_archive_peer(LUser, LServer, Prefs, Peer), Pkt} of
{true, #message{meta = #{sm_copy := true}}} ->
UseMucArchive = gen_mod:get_module_opt(LServer, ?MODULE, user_mucsub_from_muc_archive),
StoredInMucMam = UseMucArchive andalso xmpp:get_meta(Pkt, in_muc_mam, false),
case {should_archive_peer(LUser, LServer, Prefs, Peer), Pkt, StoredInMucMam} of
{true, #message{meta = #{sm_copy := true}}, _} ->
ok; % Already stored.
{true, _} ->
{true, _, true} ->
ok; % Stored in muc archive.
{true, _, _} ->
case ejabberd_hooks:run_fold(store_mam_message, LServer, Pkt,
[LUser, LServer, Peer, <<"">>, chat, Dir]) of
#message{} -> ok;
_ -> pass
end;
{false, _} ->
{false, _, _} ->
pass
end;
{error, _} ->
@@ -1027,9 +1040,12 @@ select_and_send(LServer, Query, RSM, #iq{from = From, to = To} = IQ, MsgType) ->
xmpp:make_error(IQ, Err)
end.
select(LServer, JidRequestor, JidArchive, Query, RSM, MsgType) ->
select(LServer, JidRequestor, JidArchive, Query, RSM, MsgType, all).
select(_LServer, JidRequestor, JidArchive, Query, RSM,
{groupchat, _Role, #state{config = #config{mam = false},
history = History}} = MsgType) ->
history = History}} = MsgType, _Flags) ->
Start = proplists:get_value(start, Query),
End = proplists:get_value('end', Query),
#lqueue{queue = Q} = History,
@@ -1068,15 +1084,144 @@ select(_LServer, JidRequestor, JidArchive, Query, RSM,
_ ->
{Msgs, true, L}
end;
select(LServer, JidRequestor, JidArchive, Query, RSM, MsgType) ->
select(LServer, JidRequestor, JidArchive, Query, RSM, MsgType, Flags) ->
case might_expose_jid(Query, MsgType) of
true ->
{[], true, 0};
false ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:select(LServer, JidRequestor, JidArchive, Query, RSM, MsgType)
case {MsgType, gen_mod:get_module_opt(LServer, ?MODULE, user_mucsub_from_muc_archive)} of
{chat, true} ->
select_with_mucsub(LServer, JidRequestor, JidArchive, Query, RSM, Flags);
_ ->
db_select(LServer, JidRequestor, JidArchive, Query, RSM, MsgType, Flags)
end
end.
select_with_mucsub(LServer, JidRequestor, JidArchive, Query, RSM, Flags) ->
MucHosts = mod_muc_admin:find_hosts(LServer),
Mod = gen_mod:db_mod(LServer, ?MODULE),
case proplists:get_value(with, Query) of
#jid{lserver = WithLServer} = MucJid ->
case lists:member(WithLServer, MucHosts) of
true ->
select(LServer, JidRequestor, MucJid, Query, RSM,
{groupchat, member, #state{config = #config{mam = true}}});
_ ->
db_select(LServer, JidRequestor, JidArchive, Query, RSM, chat, Flags)
end;
_ ->
case erlang:function_exported(Mod, select_with_mucsub, 6) of
true ->
Mod:select_with_mucsub(LServer, JidRequestor, JidArchive, Query, RSM, Flags);
false ->
select_with_mucsub_fallback(LServer, JidRequestor, JidArchive, Query, RSM, Flags)
end
end.
select_with_mucsub_fallback(LServer, JidRequestor, JidArchive, Query, RSM, Flags) ->
case db_select(LServer, JidRequestor, JidArchive, Query, RSM, chat, Flags) of
{error, _} = Err ->
Err;
{Entries, All, Count} ->
{Dir, Max} = case RSM of
#rsm_set{max = M, before = V} when is_binary(V) ->
{desc, M};
#rsm_set{max = M} ->
{asc, M};
_ ->
{asc, undefined}
end,
SubRooms = case mod_muc_admin:find_hosts(LServer) of
[First|_] ->
case mod_muc:get_subscribed_rooms(First, JidRequestor) of
{ok, L} -> L;
{error, _} -> []
end;
_ ->
[]
end,
SubRoomJids = [Jid || {Jid, _} <- SubRooms],
{E2, A2, C2} =
lists:foldl(
fun(MucJid, {E0, A0, C0}) ->
case select(LServer, JidRequestor, MucJid, Query, RSM,
{groupchat, member, #state{config = #config{mam = true}}}) of
{error, _} ->
{E0, A0, C0};
{E, A, C} ->
{lists:keymerge(2, E0, wrap_as_mucsub(E, JidRequestor)),
A0 andalso A, C0 + C}
end
end, {Entries, All, Count}, SubRoomJids),
case {Dir, Max} of
{_, undefined} ->
{E2, A2, C2};
{desc, _} ->
Start = case length(E2) of
Len when Len < Max -> 1;
Len -> Len - Max + 1
end,
Sub = lists:sublist(E2, Start, Max),
{Sub, if Sub == E2 -> A2; true -> false end, C2};
_ ->
Sub = lists:sublist(E2, 1, Max),
{Sub, if Sub == E2 -> A2; true -> false end, C2}
end
end.
db_select(LServer, JidRequestor, JidArchive, Query, RSM, MsgType, Flags) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
case erlang:function_exported(Mod, select, 7) of
true ->
Mod:select(LServer, JidRequestor, JidArchive, Query, RSM, MsgType, Flags);
_ ->
Mod:select(LServer, JidRequestor, JidArchive, Query, RSM, MsgType)
end.
wrap_as_mucsub(Messages, #jid{lserver = LServer} = Requester) ->
ReqBare = jid:remove_resource(Requester),
ReqServer = jid:make(<<>>, LServer, <<>>),
[{T1, T2, wrap_as_mucsub(M, ReqBare, ReqServer)} || {T1, T2, M} <- Messages].
wrap_as_mucsub(Message, Requester, ReqServer) ->
case Message of
#forwarded{delay = #delay{stamp = Stamp, desc = Desc},
sub_els = [#message{from = From, sub_els = SubEls, subject = Subject} = Msg]} ->
{L1, SubEls2} = case lists:keytake(mam_archived, 1, xmpp:decode(SubEls)) of
{value, Arch, Rest} ->
{[Arch#mam_archived{by = Requester}], Rest};
_ ->
{[], SubEls}
end,
{Sid, L2, SubEls3} = case lists:keytake(stanza_id, 1, SubEls2) of
{value, #stanza_id{id = Sid0} = SID, Rest2} ->
{Sid0, [SID#stanza_id{by = Requester} | L1], Rest2};
_ ->
{p1_rand:get_string(), L1, SubEls2}
end,
Msg2 = Msg#message{to = Requester, sub_els = SubEls3},
Node = case Subject of
[] ->
?NS_MUCSUB_NODES_MESSAGES;
_ ->
?NS_MUCSUB_NODES_SUBJECT
end,
#forwarded{delay = #delay{stamp = Stamp, desc = Desc, from = ReqServer},
sub_els = [
#message{from = jid:remove_resource(From), to = Requester,
id = Sid,
sub_els = [#ps_event{
items = #ps_items{
node = Node,
items = [#ps_item{
id = Sid,
sub_els = [Msg2]
}]}} | L2]}]};
_ ->
Message
end.
msg_to_el(#archive_msg{timestamp = TS, packet = El, nick = Nick,
peer = Peer, id = ID},
MsgType, JidRequestor, #jid{lserver = LServer} = JidArchive) ->
@@ -1265,6 +1410,8 @@ mod_opt_type(request_activates_archiving) ->
fun (B) when is_boolean(B) -> B end;
mod_opt_type(clear_archive_on_room_destroy) ->
fun (B) when is_boolean(B) -> B end;
mod_opt_type(user_mucsub_from_muc_archive) ->
fun (B) when is_boolean(B) -> B end;
mod_opt_type(access_preferences) ->
fun acl:access_rules_validator/1.
@@ -1275,6 +1422,7 @@ mod_options(Host) ->
{compress_xml, false},
{clear_archive_on_room_destroy, true},
{access_preferences, all},
{user_mucsub_from_muc_archive, false},
{db_type, ejabberd_config:default_db(Host, ?MODULE)},
{use_cache, ejabberd_config:use_cache(Host)},
{cache_size, ejabberd_config:cache_size(Host)},
+115 -52
View File
@@ -30,14 +30,15 @@
%% API
-export([init/2, remove_user/2, remove_room/3, delete_old_messages/3,
extended_fields/0, store/8, write_prefs/4, get_prefs/2, select/6, export/1, remove_from_archive/3,
is_empty_for_user/2, is_empty_for_room/3]).
extended_fields/0, store/8, write_prefs/4, get_prefs/2, select/7, export/1, remove_from_archive/3,
is_empty_for_user/2, is_empty_for_room/3, select_with_mucsub/6]).
-include_lib("stdlib/include/ms_transform.hrl").
-include("xmpp.hrl").
-include("mod_mam.hrl").
-include("logger.hrl").
-include("ejabberd_sql_pt.hrl").
-include("mod_muc_room.hrl").
%%%===================================================================
%%% API
@@ -173,43 +174,97 @@ get_prefs(LUser, LServer) ->
end.
select(LServer, JidRequestor, #jid{luser = LUser} = JidArchive,
MAMQuery, RSM, MsgType) ->
MAMQuery, RSM, MsgType, Flags) ->
User = case MsgType of
chat -> LUser;
_ -> jid:encode(JidArchive)
end,
{Query, CountQuery} = make_sql_query(User, LServer, MAMQuery, RSM),
{Query, CountQuery} = make_sql_query(User, LServer, MAMQuery, RSM, none),
do_select_query(LServer, JidRequestor, JidArchive, RSM, MsgType, Query, CountQuery, Flags).
-spec select_with_mucsub(binary(), jid(), jid(), mam_query:result(),
#rsm_set{} | undefined, all | only_count | only_messages) ->
{[{binary(), non_neg_integer(), xmlel()}], boolean(), integer()} |
{error, db_failure}.
select_with_mucsub(LServer, JidRequestor, #jid{luser = LUser} = JidArchive,
MAMQuery, RSM, Flags) ->
Extra = case gen_mod:db_mod(LServer, mod_muc) of
mod_muc_sql ->
subscribers_table;
_ ->
SubRooms = case mod_muc_admin:find_hosts(LServer) of
[First|_] ->
case mod_muc:get_subscribed_rooms(First, JidRequestor) of
{ok, L} -> L;
{error, _} -> []
end;
_ ->
[]
end,
[jid:encode(Jid) || {Jid, _} <- SubRooms]
end,
{Query, CountQuery} = make_sql_query(LUser, LServer, MAMQuery, RSM, Extra),
do_select_query(LServer, JidRequestor, JidArchive, RSM, chat, Query, CountQuery, Flags).
do_select_query(LServer, JidRequestor, #jid{luser = LUser} = JidArchive, RSM,
MsgType, Query, CountQuery, Flags) ->
% TODO from XEP-0313 v0.2: "To conserve resources, a server MAY place a
% reasonable limit on how many stanzas may be pushed to a client in one
% request. If a query returns a number of stanzas greater than this limit
% and the client did not specify a limit using RSM then the server should
% return a policy-violation error to the client." We currently don't do this
% for v0.2 requests, but we do limit #rsm_in.max for v0.3 and newer.
case {ejabberd_sql:sql_query(LServer, Query),
ejabberd_sql:sql_query(LServer, CountQuery)} of
QRes = case Flags of
all ->
{ejabberd_sql:sql_query(LServer, Query), ejabberd_sql:sql_query(LServer, CountQuery)};
only_messages ->
{ejabberd_sql:sql_query(LServer, Query), {selected, ok, [[<<"0">>]]}};
only_count ->
{{selected, ok, []}, ejabberd_sql:sql_query(LServer, CountQuery)}
end,
case QRes of
{{selected, _, Res}, {selected, _, [[Count]]}} ->
{Max, Direction, _} = get_max_direction_id(RSM),
{Res1, IsComplete} =
if Max >= 0 andalso Max /= undefined andalso length(Res) > Max ->
if Direction == before ->
{lists:nthtail(1, Res), false};
true ->
{lists:sublist(Res, Max), false}
end;
true ->
{Res, true}
end,
if Max >= 0 andalso Max /= undefined andalso length(Res) > Max ->
if Direction == before ->
{lists:nthtail(1, Res), false};
true ->
{lists:sublist(Res, Max), false}
end;
true ->
{Res, true}
end,
MucState = #state{config = #config{anonymous = true}},
JidArchiveS = jid:encode(JidArchive),
{lists:flatmap(
fun([TS, XML, PeerBin, Kind, Nick]) ->
case make_archive_el(
jid:encode(JidArchive), TS, XML, PeerBin, Kind, Nick,
MsgType, JidRequestor, JidArchive) of
fun([TS, XML, PeerBin, Kind, Nick]) ->
case make_archive_el(JidArchiveS, TS, XML, PeerBin, Kind, Nick,
MsgType, JidRequestor, JidArchive) of
{ok, El} ->
[{TS, binary_to_integer(TS), El}];
{error, _} ->
[]
end;
([User, TS, XML, PeerBin, Kind, Nick]) when User == LUser ->
case make_archive_el(JidArchiveS, TS, XML, PeerBin, Kind, Nick,
MsgType, JidRequestor, JidArchive) of
{ok, El} ->
[{TS, binary_to_integer(TS), El}];
{error, _} ->
[]
end;
([User, TS, XML, PeerBin, Kind, Nick]) ->
case make_archive_el(User, TS, XML, PeerBin, Kind, Nick,
{groupchat, member, MucState}, JidRequestor,
jid:decode(User)) of
{ok, El} ->
mod_mam:wrap_as_mucsub([{TS, binary_to_integer(TS), El}],
JidRequestor);
{error, _} ->
[]
end
end, Res1), IsComplete, binary_to_integer(Count)};
end, Res1), IsComplete, binary_to_integer(Count)};
_ ->
{[], false, 0}
end.
@@ -293,7 +348,7 @@ usec_to_now(Int) ->
Sec = Secs rem 1000000,
{MSec, Sec, USec}.
make_sql_query(User, LServer, MAMQuery, RSM) ->
make_sql_query(User, LServer, MAMQuery, RSM, ExtraUsernames) ->
Start = proplists:get_value(start, MAMQuery),
End = proplists:get_value('end', MAMQuery),
With = proplists:get_value(with, MAMQuery),
@@ -364,22 +419,39 @@ make_sql_query(User, LServer, MAMQuery, RSM) ->
SUser = Escape(User),
SServer = Escape(LServer),
Query =
case ejabberd_sql:use_new_schema() of
true ->
[<<"SELECT ">>, TopClause,
<<" timestamp, xml, peer, kind, nick"
" FROM archive WHERE username='">>,
SUser, <<"' and server_host='">>,
SServer, <<"'">>, WithClause, WithTextClause,
StartClause, EndClause, PageClause];
false ->
[<<"SELECT ">>, TopClause,
<<" timestamp, xml, peer, kind, nick"
" FROM archive WHERE username='">>,
SUser, <<"'">>, WithClause, WithTextClause,
StartClause, EndClause, PageClause]
end,
HostMatch = case ejabberd_sql:use_new_schema() of
true ->
[<<" and server_host='", SServer/binary, "'">>];
_ ->
<<"">>
end,
{UserSel, UserWhere} = case ExtraUsernames of
Users when is_list(Users) ->
EscUsers = [<<"'", (Escape(U))/binary, "'">> || U <- [User | Users]],
{<<" username,">>,
[<<" username in (">>, str:join(EscUsers, <<",">>), <<")">>]};
subscribers_table ->
SJid = Escape(jid:encode({User, LServer, <<>>})),
RoomName = case ODBCType of
sqlite ->
<<"room || '@' || host">>;
_ ->
<<"concat(room, '@', host)">>
end,
{<<" username,">>,
[<<" (username = '">>, SUser, <<"'">>,
<<" or username in (select ">>, RoomName,
<<" from muc_room_subscribers where jid='">>, SJid, <<"'">>, HostMatch, <<"))">>]};
_ ->
{<<>>, [<<" username='">>, SUser, <<"'">>]}
end,
Query = [<<"SELECT ">>, TopClause, UserSel,
<<" timestamp, xml, peer, kind, nick"
" FROM archive WHERE">>, UserWhere, HostMatch,
WithClause, WithTextClause,
StartClause, EndClause, PageClause],
QueryPage =
case Direction of
@@ -387,26 +459,17 @@ make_sql_query(User, LServer, MAMQuery, RSM) ->
% ID can be empty because of
% XEP-0059: Result Set Management
% 2.5 Requesting the Last Page in a Result Set
[<<"SELECT timestamp, xml, peer, kind, nick FROM (">>, Query,
<<" ORDER BY timestamp DESC ">>,
[<<"SELECT">>, UserSel, <<" timestamp, xml, peer, kind, nick FROM (">>,
Query, <<" ORDER BY timestamp DESC ">>,
LimitClause, <<") AS t ORDER BY timestamp ASC;">>];
_ ->
[Query, <<" ORDER BY timestamp ASC ">>,
LimitClause, <<";">>]
end,
case ejabberd_sql:use_new_schema() of
true ->
{QueryPage,
[<<"SELECT COUNT(*) FROM archive WHERE username='">>,
SUser, <<"' and server_host='">>,
SServer, <<"'">>, WithClause, WithTextClause,
StartClause, EndClause, <<";">>]};
false ->
{QueryPage,
[<<"SELECT COUNT(*) FROM archive WHERE username='">>,
SUser, <<"'">>, WithClause, WithTextClause,
StartClause, EndClause, <<";">>]}
end.
{QueryPage,
[<<"SELECT COUNT(*) FROM archive WHERE ">>, UserWhere,
HostMatch, WithClause, WithTextClause,
StartClause, EndClause, <<";">>]}.
-spec get_max_direction_id(rsm_set() | undefined) ->
{integer() | undefined,
+1 -1
View File
@@ -138,7 +138,7 @@ send_metrics(Host, Probe, Peer, Port) ->
[_, FQDN] = binary:split(misc:atom_to_binary(node()), <<"@">>),
[Node|_] = binary:split(FQDN, <<".">>),
BaseId = <<Host/binary, "/", Node/binary, ".">>,
TS = integer_to_binary(p1_time_compat:system_time(seconds)),
TS = integer_to_binary(erlang:system_time(second)),
case get_socket(?SOCKET_REGISTER_RETRIES) of
{ok, Socket} ->
case Probe of
+2 -2
View File
@@ -87,7 +87,7 @@ set_channel(_LServer, Channel, Service, CreatorJID, Hidden, Key) ->
creator = jid:remove_resource(CreatorJID),
hidden = Hidden,
hmac_key = Key,
created_at = p1_time_compat:timestamp()}).
created_at = erlang:timestamp()}).
get_channels(_LServer, Service) ->
Ret = mnesia:dirty_index_read(mix_channel, Service, #mix_channel.service),
@@ -127,7 +127,7 @@ set_participant(_LServer, Channel, Service, JID, ID, Nick) ->
jid = jid:remove_resource(JID),
id = ID,
nick = Nick,
created_at = p1_time_compat:timestamp()}).
created_at = erlang:timestamp()}).
get_participant(_LServer, Channel, Service, JID) ->
{User, Domain, _} = jid:tolower(JID),
+10 -4
View File
@@ -26,7 +26,9 @@
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
%% ejabberd_listener API
-export([start_link/2, listen_opt_type/1, listen_options/0, accept/1]).
-export([start/3, start_link/3, listen_opt_type/1, listen_options/0, accept/1]).
%% ejabberd_http API
-export([socket_handoff/3]).
%% Legacy ejabberd_listener API
-export([become_controller/2, socket_type/0]).
%% API
@@ -71,12 +73,13 @@
%%%===================================================================
%%% API
%%%===================================================================
start({SockMod, Sock}, ListenOpts) ->
mod_mqtt_session:start(SockMod, Sock, ListenOpts);
start(SockMod, Sock, ListenOpts) ->
mod_mqtt_session:start(SockMod, Sock, ListenOpts).
start(Host, Opts) ->
gen_mod:start_child(?MODULE, Host, Opts).
start_link({SockMod, Sock}, ListenOpts) ->
start_link(SockMod, Sock, ListenOpts) ->
mod_mqtt_session:start_link(SockMod, Sock, ListenOpts).
stop(Host) ->
@@ -97,6 +100,9 @@ become_controller(Pid, _) ->
accept(Pid) ->
mod_mqtt_session:accept(Pid).
socket_handoff(LocalPath, Request, Opts) ->
mod_mqtt_ws:socket_handoff(LocalPath, Request, Opts).
open_session({U, S, R}) ->
Mod = gen_mod:ram_db_mod(S, ?MODULE),
Mod:open_session({U, S, R}).
+2 -2
View File
@@ -142,7 +142,7 @@ init() ->
end.
open_session(USR) ->
TS1 = p1_time_compat:unique_timestamp(),
TS1 = misc:unique_timestamp(),
P1 = self(),
F = fun() ->
case mnesia:read(mqtt_session, USR) of
@@ -197,7 +197,7 @@ lookup_session(USR) ->
end.
subscribe({U, S, R} = USR, TopicFilter, SubOpts, ID) ->
T1 = p1_time_compat:unique_timestamp(),
T1 = misc:unique_timestamp(),
P1 = self(),
Key = {TopicFilter, S, U, R},
F = fun() ->
+18 -10
View File
@@ -64,8 +64,8 @@
session_expiry_non_zero | unknown_topic_alias.
-type state() :: #state{}.
-type sockmod() :: gen_tcp | fast_tls.
-type socket() :: {sockmod(), inet:socket() | fast_tls:tls_socket()}.
-type sockmod() :: gen_tcp | fast_tls | mod_mqtt_ws.
-type socket() :: {sockmod(), inet:socket() | fast_tls:tls_socket() | mod_mqtt_ws:socket()}.
-type peername() :: {inet:ip_address(), inet:port_number()}.
-type seconds() :: non_neg_integer().
-type milli_seconds() :: non_neg_integer().
@@ -153,9 +153,10 @@ format_error(Reason) ->
%%%===================================================================
init([SockMod, Socket, ListenOpts]) ->
MaxSize = proplists:get_value(max_payload_size, ListenOpts, infinity),
SockMod1 = case proplists:get_bool(tls, ListenOpts) of
true -> fast_tls;
false -> SockMod
SockMod1 = case {SockMod, proplists:get_bool(tls, ListenOpts)} of
{gen_tcp, true} -> fast_tls;
{gen_tcp, false} -> gen_tcp;
{_, _} -> SockMod
end,
State1 = #state{socket = {SockMod1, Socket},
id = p1_rand:uniform(65535),
@@ -190,14 +191,14 @@ handle_call(Request, From, State) ->
?WARNING_MSG("Got unexpected call from ~p: ~p", [From, Request]),
noreply(State).
handle_cast(accept, #state{socket = {_, TCPSock} = Socket} = State) ->
case inet:peername(TCPSock) of
handle_cast(accept, #state{socket = {_, Sock} = Socket} = State) ->
case peername(State) of
{ok, IPPort} ->
State1 = State#state{peername = IPPort},
case starttls(Socket) of
{ok, Socket1} ->
State2 = State1#state{socket = Socket1},
handle_info({tcp, TCPSock, <<>>}, State2);
handle_info({tcp, Sock, <<>>}, State2);
{error, Why} ->
stop(State1, Why)
end;
@@ -863,6 +864,13 @@ activate({SockMod, Sock} = Socket) ->
end,
check_sock_result(Socket, Res).
-spec peername(state()) -> {ok, peername()} | {error, socket_error_reason()}.
peername(#state{socket = {SockMod, Sock}}) ->
case SockMod of
gen_tcp -> inet:peername(Sock);
_ -> SockMod:peername(Sock)
end.
-spec disconnect(state(), error_reason()) -> state().
disconnect(#state{socket = {SockMod, Sock}} = State, Err) ->
State1 = case Err of
@@ -1061,11 +1069,11 @@ topic_alias_maximum(Host) ->
%%%===================================================================
-spec current_time() -> milli_seconds().
current_time() ->
p1_time_compat:monotonic_time(milli_seconds).
erlang:monotonic_time(millisecond).
-spec unix_time() -> seconds().
unix_time() ->
p1_time_compat:system_time(seconds).
erlang:system_time(second).
-spec set_keep_alive(state(), seconds()) -> state().
set_keep_alive(State, 0) ->
+171
View File
@@ -0,0 +1,171 @@
%%%-------------------------------------------------------------------
%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
%%% @copyright (C) 2002-2019 ProcessOne, SARL. All Rights Reserved.
%%%
%%% Licensed under the Apache License, Version 2.0 (the "License");
%%% you may not use this file except in compliance with the License.
%%% You may obtain a copy of the License at
%%%
%%% http://www.apache.org/licenses/LICENSE-2.0
%%%
%%% Unless required by applicable law or agreed to in writing, software
%%% distributed under the License is distributed on an "AS IS" BASIS,
%%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%%% See the License for the specific language governing permissions and
%%% limitations under the License.
%%%
%%%-------------------------------------------------------------------
-module(mod_mqtt_ws).
-ifndef(GEN_SERVER).
-define(GEN_SERVER, gen_server).
-endif.
-behaviour(?GEN_SERVER).
%% API
-export([socket_handoff/3]).
-export([start/1, start_link/1]).
-export([peername/1, setopts/2, send/2, close/1]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3, format_status/2]).
-include("xmpp.hrl").
-include("ejabberd_http.hrl").
-include("logger.hrl").
-define(SEND_TIMEOUT, timer:seconds(15)).
-record(state, {socket :: socket(),
ws_pid :: pid(),
mqtt_session :: undefined | pid()}).
-type peername() :: {inet:ip_address(), inet:port_number()}.
-type socket() :: {http_ws, pid(), peername()}.
-export_type([socket/0]).
%%%===================================================================
%%% API
%%%===================================================================
socket_handoff(LocalPath, Request, Opts) ->
ejabberd_websocket:socket_handoff(
LocalPath, Request, Opts, ?MODULE, fun get_human_html_xmlel/0).
start({#ws{http_opts = Opts}, _} = WS) ->
?GEN_SERVER:start(?MODULE, [WS], ejabberd_config:fsm_limit_opts(Opts)).
start_link({#ws{http_opts = Opts}, _} = WS) ->
?GEN_SERVER:start_link(?MODULE, [WS], ejabberd_config:fsm_limit_opts(Opts)).
-spec peername(socket()) -> {ok, peername()}.
peername({http_ws, _, IP}) ->
{ok, IP}.
-spec setopts(socket(), list()) -> ok.
setopts(_WSock, _Opts) ->
ok.
-spec send(socket(), iodata()) -> ok | {error, timeout | einval}.
send({http_ws, Pid, _}, Data) ->
try ?GEN_SERVER:call(Pid, {send, Data}, ?SEND_TIMEOUT)
catch exit:{timeout, {?GEN_SERVER, _, _}} ->
{error, timeout};
exit:{_, {?GEN_SERVER, _, _}} ->
{error, einval}
end.
-spec close(socket()) -> ok.
close({http_ws, Pid, _}) ->
?GEN_SERVER:cast(Pid, close).
%%%===================================================================
%%% gen_server callbacks
%%%===================================================================
init([{#ws{ip = IP, http_opts = ListenOpts}, WsPid}]) ->
Socket = {http_ws, self(), IP},
case mod_mqtt_session:start(?MODULE, Socket, ListenOpts) of
{ok, Pid} ->
erlang:monitor(process, Pid),
erlang:monitor(process, WsPid),
mod_mqtt_session:accept(Pid),
State = #state{socket = Socket,
ws_pid = WsPid,
mqtt_session = Pid},
{ok, State};
{error, Reason} ->
{stop, Reason};
ignore ->
ignore
end.
handle_call({send, Data}, _From, #state{ws_pid = WsPid} = State) ->
WsPid ! {data, Data},
{reply, ok, State};
handle_call(Request, From, State) ->
?WARNING_MSG("Got unexpected call from ~p: ~p", [From, Request]),
{noreply, State}.
handle_cast(close, State) ->
{stop, normal, State#state{mqtt_session = undefined}};
handle_cast(Request, State) ->
?WARNING_MSG("Got unexpected cast: ~p", [Request]),
{noreply, State}.
handle_info(closed, State) ->
{stop, normal, State};
handle_info({received, Data}, State) ->
State#state.mqtt_session ! {tcp, State#state.socket, Data},
{noreply, State};
handle_info({'DOWN', _, process, Pid, _}, State)
when Pid == State#state.mqtt_session orelse Pid == State#state.ws_pid ->
{stop, normal, State};
handle_info(Info, State) ->
?WARNING_MSG("Got unexpected info: ~p", [Info]),
{noreply, State}.
terminate(_Reason, State) ->
if State#state.mqtt_session /= undefined ->
State#state.mqtt_session ! {tcp_closed, State#state.socket};
true ->
ok
end.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
format_status(_Opt, Status) ->
Status.
%%%===================================================================
%%% Internal functions
%%%===================================================================
-spec get_human_html_xmlel() -> xmlel().
get_human_html_xmlel() ->
Heading = <<"ejabberd mod_mqtt">>,
#xmlel{name = <<"html">>,
attrs =
[{<<"xmlns">>, <<"http://www.w3.org/1999/xhtml">>}],
children =
[#xmlel{name = <<"head">>, attrs = [],
children =
[#xmlel{name = <<"title">>, attrs = [],
children = [{xmlcdata, Heading}]}]},
#xmlel{name = <<"body">>, attrs = [],
children =
[#xmlel{name = <<"h1">>, attrs = [],
children = [{xmlcdata, Heading}]},
#xmlel{name = <<"p">>, attrs = [],
children =
[{xmlcdata, <<"An implementation of ">>},
#xmlel{name = <<"a">>,
attrs =
[{<<"href">>,
<<"http://tools.ietf.org/html/rfc6455">>}],
children =
[{xmlcdata,
<<"WebSocket protocol">>}]}]},
#xmlel{name = <<"p">>, attrs = [],
children =
[{xmlcdata,
<<"This web page is only informative. To "
"use WebSocket connection you need an MQTT "
"client that supports it.">>}]}]}]}.
+127 -68
View File
@@ -66,7 +66,7 @@
count_online_rooms_by_user/3,
get_online_rooms_by_user/3,
can_use_nick/4,
check_create_room/4]).
get_subscribed_rooms/2]).
-export([init/1, handle_call/3, handle_cast/2,
handle_info/2, terminate/2, code_change/3,
@@ -107,20 +107,19 @@
-callback unregister_online_user(binary(), ljid(), binary(), binary()) -> any().
-callback count_online_rooms_by_user(binary(), binary(), binary()) -> non_neg_integer().
-callback get_online_rooms_by_user(binary(), binary(), binary()) -> [{binary(), binary()}].
-callback get_subscribed_rooms(binary(), binary(), jid()) -> [{ljid(), [binary()]}] | [].
-callback get_subscribed_rooms(binary(), binary(), jid()) ->
{ok, [{jid(), [binary()]}]} | {error, db_failure}.
-optional_callbacks([get_subscribed_rooms/3]).
%%====================================================================
%% API
%%====================================================================
start(Host, Opts) ->
ejabberd_hooks:add(check_create_room, Host, ?MODULE,
check_create_room, 50),
gen_mod:start_child(?MODULE, Host, Opts).
stop(Host) ->
Rooms = shutdown_rooms(Host),
ejabberd_hooks:delete(check_create_room, Host, ?MODULE,
check_create_room, 50),
gen_mod:stop_child(?MODULE, Host),
{wait, Rooms}.
@@ -438,18 +437,14 @@ do_route1(_Host, _ServerHost, _Access, _HistorySize, _RoomShaper,
ejabberd_router:route_error(Packet, Err);
do_route1(Host, ServerHost, Access, HistorySize, RoomShaper,
From, To, Packet, DefRoomOpts, QueueType) ->
{_AccessRoute, AccessCreate, _AccessAdmin, _AccessPersistent, _AccessMam} = Access,
{Room, _, Nick} = jid:tolower(To),
RMod = gen_mod:ram_db_mod(ServerHost, ?MODULE),
case RMod:find_online_room(ServerHost, Room, Host) of
error ->
case is_create_request(Packet) of
true ->
case check_user_can_create_room(
ServerHost, AccessCreate, From, Room) and
ejabberd_hooks:run_fold(check_create_room,
ServerHost, true,
[ServerHost, Room, Host]) of
case check_create_room(
ServerHost, Host, Room, From, Access) of
true ->
{ok, Pid} = start_new_room(
Host, ServerHost, Access,
@@ -584,7 +579,7 @@ process_muc_unique(#iq{type = set, lang = Lang} = IQ) ->
xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
process_muc_unique(#iq{from = From, type = get,
sub_els = [#muc_unique{}]} = IQ) ->
Name = str:sha(term_to_binary([From, p1_time_compat:timestamp(),
Name = str:sha(term_to_binary([From, erlang:timestamp(),
p1_rand:get_string()])),
xmpp:make_iq_result(IQ, #muc_unique{name = Name}).
@@ -592,12 +587,19 @@ process_muc_unique(#iq{from = From, type = get,
process_mucsub(#iq{type = set, lang = Lang} = IQ) ->
Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
process_mucsub(#iq{type = get, from = From, to = To,
process_mucsub(#iq{type = get, from = From, to = To, lang = Lang,
sub_els = [#muc_subscriptions{}]} = IQ) ->
Host = To#jid.lserver,
ServerHost = ejabberd_router:host_of_route(Host),
Subs = get_subscribed_rooms(ServerHost, Host, From),
xmpp:make_iq_result(IQ, #muc_subscriptions{list = Subs});
case get_subscribed_rooms(ServerHost, Host, From) of
{ok, Subs} ->
List = [#muc_subscription{jid = JID, events = Nodes}
|| {JID, Nodes} <- Subs],
xmpp:make_iq_result(IQ, #muc_subscriptions{list = List});
{error, _} ->
Txt = <<"Database failure">>,
xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang))
end;
process_mucsub(#iq{lang = Lang} = IQ) ->
Txt = <<"No module is handling this query">>,
xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang)).
@@ -611,20 +613,38 @@ is_create_request(#iq{type = T} = IQ) when T == get; T == set ->
is_create_request(_) ->
false.
check_user_can_create_room(ServerHost, AccessCreate,
From, _RoomID) ->
-spec check_create_room(binary(), binary(), binary(), jid(), tuple())
-> boolean().
check_create_room(ServerHost, Host, Room, From, Access) ->
{_AccessRoute, AccessCreate, AccessAdmin,
_AccessPersistent, _AccessMam} = Access,
case acl:match_rule(ServerHost, AccessCreate, From) of
allow -> true;
_ -> false
allow ->
case gen_mod:get_module_opt(ServerHost, ?MODULE, max_room_id) of
Max when byte_size(Room) =< Max ->
Regexp = gen_mod:get_module_opt(
ServerHost, ?MODULE, regexp_room_id),
case re:run(Room, Regexp, [unicode, {capture, none}]) of
match ->
case acl:match_rule(
ServerHost, AccessAdmin, From) of
allow ->
true;
_ ->
ejabberd_hooks:run_fold(
check_create_room, ServerHost, true,
[ServerHost, Room, Host])
end;
_ ->
false
end;
_ ->
false
end;
_ ->
false
end.
check_create_room(Acc, ServerHost, RoomID, _Host) ->
Max = gen_mod:get_module_opt(ServerHost, ?MODULE, max_room_id),
Regexp = gen_mod:get_module_opt(ServerHost, ?MODULE, regexp_room_id),
Acc and
(byte_size(RoomID) =< Max) and
(re:run(RoomID, Regexp, [unicode, {capture, none}]) == match).
get_rooms(ServerHost, Host) ->
LServer = jid:nameprep(ServerHost),
Mod = gen_mod:db_mod(LServer, ?MODULE),
@@ -634,31 +654,46 @@ load_permanent_rooms(Host, ServerHost, Access,
HistorySize, RoomShaper, QueueType) ->
RMod = gen_mod:ram_db_mod(ServerHost, ?MODULE),
lists:foreach(
fun(R) ->
{Room, Host} = R#muc_room.name_host,
case RMod:find_online_room(ServerHost, Room, Host) of
error ->
{ok, Pid} = mod_muc_room:start(Host,
ServerHost, Access, Room,
HistorySize, RoomShaper,
R#muc_room.opts, QueueType),
RMod:register_online_room(ServerHost, Room, Host, Pid);
{ok, _} ->
ok
end
end,
get_rooms(ServerHost, Host)).
fun(R) ->
{Room, Host} = R#muc_room.name_host,
case proplists:get_bool(persistent, R#muc_room.opts) of
true ->
case RMod:find_online_room(ServerHost, Room, Host) of
error ->
{ok, Pid} = mod_muc_room:start(Host,
ServerHost, Access, Room,
HistorySize, RoomShaper,
R#muc_room.opts, QueueType),
RMod:register_online_room(ServerHost, Room, Host, Pid);
{ok, _} ->
ok
end;
_ ->
forget_room(ServerHost, Host, Room)
end
end, get_rooms(ServerHost, Host)).
start_new_room(Host, ServerHost, Access, Room,
HistorySize, RoomShaper, From,
Nick, DefRoomOpts, QueueType) ->
case restore_room(ServerHost, Host, Room) of
Opts = case restore_room(ServerHost, Host, Room) of
error ->
error;
Opts0 ->
case proplists:get_bool(persistent, Opts0) of
true ->
Opts0;
_ ->
error
end
end,
case Opts of
error ->
?DEBUG("MUC: open new room '~s'~n", [Room]),
mod_muc_room:start(Host, ServerHost, Access, Room,
HistorySize, RoomShaper,
From, Nick, DefRoomOpts, QueueType);
Opts ->
_ ->
?DEBUG("MUC: restore room '~s'~n", [Room]),
mod_muc_room:start(Host, ServerHost, Access, Room,
HistorySize, RoomShaper, Opts, QueueType)
@@ -705,38 +740,62 @@ iq_disco_items(_ServerHost, _Host, _From, Lang, _MaxRoomsDiscoItems, _Node, _RSM
-spec get_room_disco_item({binary(), binary(), pid()},
term()) -> {ok, disco_item()} |
{error, timeout | notfound}.
get_room_disco_item({Name, Host, Pid}, Query) ->
RoomJID = jid:make(Name, Host),
try p1_fsm:sync_send_all_state_event(Pid, Query, 100) of
{item, Desc} ->
{ok, #disco_item{jid = RoomJID, name = Desc}};
false ->
{error, notfound}
catch _:{timeout, {p1_fsm, _, _}} ->
{error, timeout};
_:{_, {p1_fsm, _, _}} ->
{error, notfound}
get_room_disco_item({Name, Host, Pid},
{get_disco_item, Filter, JID, Lang}) ->
RoomJID = jid:make(Name, Host),
Timeout = 100,
Time = erlang:monotonic_time(millisecond),
Query1 = {get_disco_item, Filter, JID, Lang, Time+Timeout},
try p1_fsm:sync_send_all_state_event(Pid, Query1, Timeout) of
{item, Desc} ->
{ok, #disco_item{jid = RoomJID, name = Desc}};
false ->
{error, notfound}
catch _:{timeout, {p1_fsm, _, _}} ->
{error, timeout};
_:{_, {p1_fsm, _, _}} ->
{error, notfound}
end.
-spec get_subscribed_rooms(binary(), jid()) -> {ok, [{jid(), [binary()]}]} | {error, any()}.
get_subscribed_rooms(Host, User) ->
ServerHost = ejabberd_router:host_of_route(Host),
get_subscribed_rooms(ServerHost, Host, User).
-record(subscriber, {jid :: jid(),
nick = <<>> :: binary(),
nodes = [] :: [binary()]}).
-spec get_subscribed_rooms(binary(), binary(), jid()) ->
{ok, [{jid(), [binary()]}]} | {error, any()}.
get_subscribed_rooms(ServerHost, Host, From) ->
LServer = jid:nameprep(ServerHost),
Mod = gen_mod:db_mod(LServer, ?MODULE),
BareFrom = jid:remove_resource(From),
case Mod:get_subscribed_rooms(LServer, Host, BareFrom) of
not_implemented ->
case erlang:function_exported(Mod, get_subscribed_rooms, 3) of
false ->
Rooms = get_online_rooms(ServerHost, Host),
lists:flatmap(
fun({Name, _, Pid}) ->
case p1_fsm:sync_send_all_state_event(Pid, {is_subscribed, BareFrom}) of
{true, Nodes} ->
[#muc_subscription{jid = jid:make(Name, Host), events = Nodes}];
false -> []
end;
(_) ->
[]
end, Rooms);
V ->
[#muc_subscription{jid = Jid, events = Nodes} || {Jid, Nodes} <- V]
{ok, lists:flatmap(
fun({Name, _, Pid}) when Pid == self() ->
USR = jid:split(BareFrom),
case erlang:get(muc_subscribers) of
#{USR := #subscriber{nodes = Nodes}} ->
[{jid:make(Name, Host), Nodes}];
_ ->
[]
end;
({Name, _, Pid}) ->
case p1_fsm:sync_send_all_state_event(
Pid, {is_subscribed, BareFrom}) of
{true, Nodes} ->
[{jid:make(Name, Host), Nodes}];
false -> []
end;
(_) ->
[]
end, Rooms)};
true ->
Mod:get_subscribed_rooms(LServer, Host, BareFrom)
end.
get_nick(ServerHost, Host, From) ->
+16 -10
View File
@@ -28,7 +28,7 @@
-behaviour(gen_mod).
-export([start/2, stop/1, reload/3, depends/2,
-export([start/2, stop/1, reload/3, depends/2,
muc_online_rooms/1, muc_online_rooms_by_regex/2,
muc_register_nick/3, muc_unregister_nick/2,
create_room_with_opts/4, create_room/3, destroy_room/2,
@@ -41,7 +41,7 @@
set_room_affiliation/4, get_room_affiliations/2, get_room_affiliation/3,
web_menu_main/2, web_page_main/2, web_menu_host/3,
subscribe_room/4, unsubscribe_room/2, get_subscribers/2,
web_page_host/3, mod_options/1, get_commands_spec/0]).
web_page_host/3, mod_options/1, get_commands_spec/0, find_hosts/1]).
-include("logger.hrl").
-include("xmpp.hrl").
@@ -100,18 +100,18 @@ get_commands_spec() ->
desc = "List existing rooms ('global' to get all vhosts) by regex",
policy = admin,
module = ?MODULE, function = muc_online_rooms_by_regex,
args_desc = ["Server domain where the MUC service is, or 'global' for all",
args_desc = ["Server domain where the MUC service is, or 'global' for all",
"Regex pattern for room name"],
args_example = ["example.com", "^prefix"],
result_desc = "List of rooms with summary",
result_example = [{"room1@muc.example.com", "true", 10},
result_example = [{"room1@muc.example.com", "true", 10},
{"room2@muc.example.com", "false", 10}],
args = [{host, binary}, {regex, binary}],
result = {rooms, {list, {room, {tuple,
[{jid, string},
{public, string},
{participants, integer}
]}}}}},
]}}}}},
#ejabberd_commands{name = muc_register_nick, tags = [muc],
desc = "Register a nick to a User JID in the MUC service of a server",
module = ?MODULE, function = muc_register_nick,
@@ -590,9 +590,16 @@ prepare_room_info(Room_info) ->
misc:atom_to_binary(Public),
misc:atom_to_binary(Persistent),
misc:atom_to_binary(Logging),
misc:atom_to_binary(Just_created),
justcreated_to_binary(Just_created),
Title].
justcreated_to_binary(J) when is_integer(J) ->
JNow = misc:usec_to_now(J),
{{Year, Month, Day}, {Hour, Minute, Second}} = calendar:now_to_local_time(JNow),
str:format("~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w",
[Year, Month, Day, Hour, Minute, Second]);
justcreated_to_binary(J) when is_atom(J) ->
misc:atom_to_binary(J).
%%----------------------------
%% Create/Delete Room
@@ -814,13 +821,12 @@ decide_room(unused, {_Room_name, _Host, Room_pid}, ServerHost, Last_allowed) ->
History = (S#state.history)#lqueue.queue,
Ts_now = calendar:universal_time(),
HistorySize = gen_mod:get_module_opt(ServerHost, mod_muc, history_size),
JustCreated = S#state.just_created,
{Has_hist, Last} = case p1_queue:is_empty(History) of
true when (HistorySize == 0) or (JustCreated == true) ->
true when (HistorySize == 0) or (Just_created == true) ->
{false, 0};
true ->
Ts_diff = (misc:now_to_usec(now())
- S#state.just_created) div 1000000,
Ts_diff = (erlang:system_time(microsecond)
- Just_created) div 1000000,
{false, Ts_diff};
false ->
Last_message = get_queue_last(History),
+3 -3
View File
@@ -309,7 +309,7 @@ add_message_to_log(Nick1, Message, RoomJID, Opts,
Room = get_room_info(RoomJID, Opts),
Nick = htmlize(Nick1, FileFormat),
Nick2 = htmlize_nick(Nick1, FileFormat),
Now = p1_time_compat:timestamp(),
Now = erlang:timestamp(),
TimeStamp = case Timezone of
local -> calendar:now_to_local_time(Now);
universal -> calendar:now_to_universal_time(Now)
@@ -625,7 +625,7 @@ put_header_script(F) ->
put_room_config(_F, _RoomConfig, _Lang, plaintext) ->
ok;
put_room_config(F, RoomConfig, Lang, _FileFormat) ->
{_, Now2, _} = p1_time_compat:timestamp(),
{_, Now2, _} = erlang:timestamp(),
fw(F, <<"<div class=\"rc\">">>),
fw(F,
<<"<div class=\"rct\" onclick=\"sh('a~p');return "
@@ -642,7 +642,7 @@ put_room_occupants(_F, _RoomOccupants, _Lang,
ok;
put_room_occupants(F, RoomOccupants, Lang,
_FileFormat) ->
{_, Now2, _} = p1_time_compat:timestamp(),
{_, Now2, _} = erlang:timestamp(),
%% htmlize
%% The default behaviour is to ignore the nofollow spam prevention on links
%% (NoFollow=false)
+1 -5
View File
@@ -33,8 +33,7 @@
-export([register_online_room/4, unregister_online_room/4, find_online_room/3,
get_online_rooms/3, count_online_rooms/2, rsm_supported/0,
register_online_user/4, unregister_online_user/4,
count_online_rooms_by_user/3, get_online_rooms_by_user/3,
get_subscribed_rooms/3]).
count_online_rooms_by_user/3, get_online_rooms_by_user/3]).
-export([set_affiliation/6, set_affiliations/4, get_affiliation/5,
get_affiliations/3, search_affiliation/4]).
%% gen_server callbacks
@@ -401,6 +400,3 @@ transform(#muc_registered{us_host = {{U, S}, H}, nick = Nick} = R) ->
R#muc_registered{us_host = {{iolist_to_binary(U), iolist_to_binary(S)},
iolist_to_binary(H)},
nick = iolist_to_binary(Nick)}.
get_subscribed_rooms(_, _, _) ->
not_implemented.
+1 -5
View File
@@ -33,8 +33,7 @@
-export([register_online_room/4, unregister_online_room/4, find_online_room/3,
get_online_rooms/3, count_online_rooms/2, rsm_supported/0,
register_online_user/4, unregister_online_user/4,
count_online_rooms_by_user/3, get_online_rooms_by_user/3,
get_subscribed_rooms/3]).
count_online_rooms_by_user/3, get_online_rooms_by_user/3]).
-export([set_affiliation/6, set_affiliations/4, get_affiliation/5,
get_affiliations/3, search_affiliation/4]).
@@ -184,9 +183,6 @@ import(_LServer, <<"muc_registered">>,
ejabberd_riak:put(R, muc_registered_schema(),
[{'2i', [{<<"nick_host">>, {Nick, RoomHost}}]}]).
get_subscribed_rooms(_, _, _) ->
not_implemented.
%%%===================================================================
%%% Internal functions
%%%===================================================================
+137 -62
View File
@@ -162,7 +162,7 @@ normal_state({route, <<"">>,
is_user_allowed_message_nonparticipant(From, StateData) of
true when Type == groupchat ->
Activity = get_user_activity(From, StateData),
Now = p1_time_compat:system_time(micro_seconds),
Now = erlang:system_time(microsecond),
MinMessageInterval = trunc(gen_mod:get_module_opt(
StateData#state.server_host,
mod_muc, min_message_interval)
@@ -322,7 +322,8 @@ normal_state({route, <<"">>,
end,
case NewStateData of
stop ->
{stop, normal, StateData};
Conf = StateData#state.config,
{stop, normal, StateData#state{config = Conf#config{persistent = false}}};
_ when NewStateData#state.just_created ->
close_room_if_temporary_and_empty(NewStateData);
_ ->
@@ -344,7 +345,7 @@ normal_state({route, <<"">>, #iq{} = IQ}, StateData) ->
end;
normal_state({route, Nick, #presence{from = From} = Packet}, StateData) ->
Activity = get_user_activity(From, StateData),
Now = p1_time_compat:system_time(micro_seconds),
Now = erlang:system_time(microsecond),
MinPresenceInterval =
trunc(gen_mod:get_module_opt(StateData#state.server_host,
mod_muc, min_presence_interval)
@@ -498,7 +499,8 @@ handle_event({destroy, Reason}, _StateName,
?INFO_MSG("Destroyed MUC room ~s with reason: ~p",
[jid:encode(StateData#state.jid), Reason]),
add_to_log(room_existence, destroyed, StateData),
{stop, shutdown, StateData};
Conf = StateData#state.config,
{stop, shutdown, StateData#state{config = Conf#config{persistent = false}}};
handle_event(destroy, StateName, StateData) ->
?INFO_MSG("Destroyed MUC room ~s",
[jid:encode(StateData#state.jid)]),
@@ -510,7 +512,7 @@ handle_event({set_affiliations, Affiliations},
handle_event(_Event, StateName, StateData) ->
{next_state, StateName, StateData}.
handle_sync_event({get_disco_item, Filter, JID, Lang}, _From, StateName, StateData) ->
handle_sync_event({get_disco_item, Filter, JID, Lang, Time}, _From, StateName, StateData) ->
Len = maps:size(StateData#state.nicks),
Reply = case (Filter == all) or (Filter == Len) or ((Filter /= 0) and (Len /= 0)) of
true ->
@@ -519,10 +521,17 @@ handle_sync_event({get_disco_item, Filter, JID, Lang}, _From, StateName, StateDa
false ->
false
end,
{reply, Reply, StateName, StateData};
%% This clause is only for backwards compatibility
CurrentTime = erlang:monotonic_time(millisecond),
if CurrentTime < Time ->
{reply, Reply, StateName, StateData};
true ->
{next_state, StateName, StateData}
end;
%% These two clauses are only for backward compatibility with nodes running old code
handle_sync_event({get_disco_item, JID, Lang}, From, StateName, StateData) ->
handle_sync_event({get_disco_item, any, JID, Lang}, From, StateName, StateData);
handle_sync_event({get_disco_item, Filter, JID, Lang}, From, StateName, StateData) ->
handle_sync_event({get_disco_item, Filter, JID, Lang, infinity}, From, StateName, StateData);
handle_sync_event(get_config, _From, StateName,
StateData) ->
{reply, {ok, StateData#state.config}, StateName,
@@ -536,6 +545,7 @@ handle_sync_event({change_config, Config}, _From,
{reply, {ok, NSD#state.config}, StateName, NSD};
handle_sync_event({change_state, NewStateData}, _From,
StateName, _StateData) ->
erlang:put(muc_subscribers, NewStateData#state.subscribers),
{reply, {ok, NewStateData}, StateName, NewStateData};
handle_sync_event({process_item_change, Item, UJID}, _From, StateName, StateData) ->
case process_item_change(Item, StateData, UJID) of
@@ -698,10 +708,10 @@ handle_info(config_reloaded, StateName, StateData) ->
handle_info(_Info, StateName, StateData) ->
{next_state, StateName, StateData}.
terminate(Reason, _StateName, StateData) ->
terminate(Reason, _StateName,
#state{server_host = LServer, host = Host, room = Room} = StateData) ->
try
?INFO_MSG("Stopping MUC room ~s@~s",
[StateData#state.room, StateData#state.host]),
?INFO_MSG("Stopping MUC room ~s@~s", [Room, Host]),
ReasonT = case Reason of
shutdown ->
<<"You are being removed from the room "
@@ -728,12 +738,16 @@ terminate(Reason, _StateName, StateData) ->
tab_remove_online_user(LJID, StateData)
end, [], get_users_and_subscribers(StateData)),
add_to_log(room_existence, stopped, StateData),
mod_muc:room_destroyed(StateData#state.host, StateData#state.room, self(),
StateData#state.server_host)
case (StateData#state.config)#config.persistent of
false ->
ejabberd_hooks:run(room_destroyed, LServer, [LServer, Room, Host]);
_ ->
ok
end,
mod_muc:room_destroyed(Host, Room, self(), LServer)
catch ?EX_RULE(E, R, St) ->
mod_muc:room_destroyed(StateData#state.host, StateData#state.room, self(),
StateData#state.server_host),
?ERROR_MSG("Got exception on room termination: ~p", [{E, {R, ?EX_STACK(St)}}])
mod_muc:room_destroyed(Host, Room, self(), LServer),
?ERROR_MSG("Got exception on room termination: ~p", [{E, {R, ?EX_STACK(St)}}])
end,
ok.
@@ -903,7 +917,7 @@ process_voice_request(From, Pkt, StateData) ->
true ->
MinInterval = (StateData#state.config)#config.voice_request_min_interval,
BareFrom = jid:remove_resource(jid:tolower(From)),
NowPriority = -p1_time_compat:system_time(micro_seconds),
NowPriority = -erlang:system_time(microsecond),
CleanPriority = NowPriority + MinInterval * 1000000,
Times = clean_treap(StateData#state.last_voice_request_time,
CleanPriority),
@@ -1134,6 +1148,7 @@ close_room_if_temporary_and_empty(StateData1) ->
"and empty",
[jid:encode(StateData1#state.jid)]),
add_to_log(room_existence, destroyed, StateData1),
maybe_forget_room(StateData1),
{stop, normal, StateData1};
_ -> {next_state, normal_state, StateData1}
end.
@@ -1572,7 +1587,7 @@ store_user_activity(JID, UserActivity, StateData) ->
mod_muc, min_presence_interval)
* 1000),
Key = jid:tolower(JID),
Now = p1_time_compat:system_time(micro_seconds),
Now = erlang:system_time(microsecond),
Activity1 = clean_treap(StateData#state.activity,
{1, -Now}),
Activity = case treap:lookup(Key, Activity1) of
@@ -1688,8 +1703,14 @@ update_online_user(JID, #user{nick = Nick} = User, StateData) ->
end,
NewStateData.
set_subscriber(JID, Nick, Nodes, StateData) ->
BareJID = jid:remove_resource(JID),
set_subscriber(JID, Nick, Nodes,
#state{room = Room, host = Host, server_host = ServerHost} = StateData) ->
BareJID = case JID of
#jid{} -> jid:remove_resource(JID);
_ ->
?ERROR_MSG("Invalid subscriber JID in set_subscriber ~p", [JID]),
jid:remove_resource(jid:make(JID))
end,
LBareJID = jid:tolower(BareJID),
Subscribers = maps:put(LBareJID,
#subscriber{jid = BareJID,
@@ -1702,7 +1723,8 @@ set_subscriber(JID, Nick, Nodes, StateData) ->
store_room(NewStateData, [{add_subscription, BareJID, Nick, Nodes}]),
case not maps:is_key(LBareJID, StateData#state.subscribers) of
true ->
send_subscriptions_change_notifications(BareJID, Nick, subscribe, NewStateData);
send_subscriptions_change_notifications(BareJID, Nick, subscribe, NewStateData),
ejabberd_hooks:run(muc_subscribed, ServerHost, [ServerHost, Room, Host, BareJID]);
_ ->
ok
end,
@@ -1960,7 +1982,7 @@ add_new_user(From, Nick, Packet, StateData) ->
ResultState =
case NewStateData#state.just_created of
true ->
NewStateData#state{just_created = misc:now_to_usec(now())};
NewStateData#state{just_created = erlang:system_time(microsecond)};
_ ->
Robots = maps:remove(From, StateData#state.robots),
NewStateData#state{robots = Robots}
@@ -2103,7 +2125,7 @@ extract_password(#iq{} = IQ) ->
get_history(Nick, Packet, #state{history = History}) ->
case xmpp:get_subtag(Packet, #muc{}) of
#muc{history = #muc_history{} = MUCHistory} ->
Now = p1_time_compat:timestamp(),
Now = erlang:timestamp(),
Q = History#lqueue.queue,
filter_history(Q, Now, Nick, MUCHistory);
_ ->
@@ -2518,7 +2540,7 @@ add_message_to_history(FromNick, FromJID, Packet, StateData) ->
add_to_log(text, {FromNick, Packet}, StateData),
case check_subject(Packet) of
[] ->
TimeStamp = p1_time_compat:timestamp(),
TimeStamp = erlang:timestamp(),
AddrPacket = case (StateData#state.config)#config.anonymous of
true -> Packet;
false ->
@@ -3473,13 +3495,12 @@ change_config(Config, StateData) ->
store_room(StateData1),
StateData1;
{true, false} ->
Affiliations = get_affiliations(StateData),
mod_muc:forget_room(StateData1#state.server_host,
StateData1#state.host,
StateData1#state.room),
StateData1#state{affiliations = Affiliations};
{false, false} ->
StateData1
Affiliations = get_affiliations(StateData),
maybe_forget_room(StateData),
StateData1#state{affiliations = Affiliations};
_ ->
maybe_forget_room(StateData),
StateData1
end,
case {(StateData#state.config)#config.members_only,
Config#config.members_only} of
@@ -3674,14 +3695,20 @@ set_opts([{Opt, Val} | Opts], StateData) ->
{Subscribers, Nicks} =
lists:foldl(
fun({JID, Nick, Nodes}, {SubAcc, NickAcc}) ->
BareJID = jid:remove_resource(JID),
{maps:put(
jid:tolower(BareJID),
#subscriber{jid = BareJID,
nick = Nick,
nodes = Nodes},
SubAcc),
maps:put(Nick, [jid:tolower(BareJID)], NickAcc)}
BareJID = case JID of
#jid{} -> jid:remove_resource(JID);
_ ->
?ERROR_MSG("Invalid subscriber JID in set_opts ~p", [JID]),
jid:remove_resource(jid:make(JID))
end,
LBareJID = jid:tolower(BareJID),
{maps:put(
LBareJID,
#subscriber{jid = BareJID,
nick = Nick,
nodes = Nodes},
SubAcc),
maps:put(Nick, [LBareJID], NickAcc)}
end, {#{}, #{}}, Val),
StateData#state{subscribers = Subscribers,
subscriber_nicks = Nicks};
@@ -3803,14 +3830,27 @@ destroy_room(DEl, StateData) ->
Info#user.jid, Packet,
?NS_MUCSUB_NODES_CONFIG, StateData)
end, ok, get_users_and_subscribers(StateData)),
case (StateData#state.config)#config.persistent of
true ->
mod_muc:forget_room(StateData#state.server_host,
StateData#state.host, StateData#state.room);
false -> ok
end,
maybe_forget_room(StateData),
{result, undefined, stop}.
maybe_forget_room(StateData) ->
Forget = case (StateData#state.config)#config.persistent of
true ->
true;
_ ->
Mod = gen_mod:db_mod(StateData#state.server_host, mod_muc),
erlang:function_exported(Mod, get_subscribed_rooms, 3)
end,
case Forget of
true ->
mod_muc:forget_room(StateData#state.server_host,
StateData#state.host,
StateData#state.room),
StateData;
_ ->
StateData
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Disco
@@ -4045,8 +4085,9 @@ process_iq_mucsub(From, #iq{type = set, lang = Lang,
{error, xmpp:err_forbidden(Txt, Lang)}
end;
process_iq_mucsub(From, #iq{type = set, sub_els = [#muc_unsubscribe{}]},
StateData) ->
LBareJID = jid:tolower(jid:remove_resource(From)),
#state{room = Room, host = Host, server_host = ServerHost} = StateData) ->
BareJID = jid:remove_resource(From),
LBareJID = jid:tolower(BareJID),
try maps:get(LBareJID, StateData#state.subscribers) of
#subscriber{nick = Nick} ->
Nicks = maps:remove(Nick, StateData#state.subscriber_nicks),
@@ -4054,7 +4095,8 @@ process_iq_mucsub(From, #iq{type = set, sub_els = [#muc_unsubscribe{}]},
NewStateData = StateData#state{subscribers = Subscribers,
subscriber_nicks = Nicks},
store_room(NewStateData, [{del_subscription, LBareJID}]),
send_subscriptions_change_notifications(LBareJID, Nick, unsubscribe, StateData),
send_subscriptions_change_notifications(BareJID, Nick, unsubscribe, StateData),
ejabberd_hooks:run(muc_unsubscribed, ServerHost, [ServerHost, Room, Host, BareJID]),
NewStateData2 = case close_room_if_temporary_and_empty(NewStateData) of
{stop, normal, _} -> stop;
{next_state, normal_state, SD} -> SD
@@ -4068,13 +4110,23 @@ process_iq_mucsub(From, #iq{type = get, lang = Lang,
StateData) ->
FAffiliation = get_affiliation(From, StateData),
FRole = get_role(From, StateData),
if FRole == moderator; FAffiliation == owner; FAffiliation == admin ->
IsModerator = FRole == moderator orelse FAffiliation == owner orelse
FAffiliation == admin,
case IsModerator orelse is_subscriber(From, StateData) of
true ->
ShowJid = IsModerator orelse
(StateData#state.config)#config.anonymous == false,
Subs = maps:fold(
fun(_, #subscriber{jid = J, nodes = Nodes}, Acc) ->
[#muc_subscription{jid = J, events = Nodes}|Acc]
fun(_, #subscriber{jid = J, nick = N, nodes = Nodes}, Acc) ->
case ShowJid of
true ->
[#muc_subscription{jid = J, events = Nodes}|Acc];
_ ->
[#muc_subscription{nick = N, events = Nodes}|Acc]
end
end, [], StateData#state.subscribers),
{result, #muc_subscriptions{list = Subs}, StateData};
true ->
_ ->
Txt = <<"Moderator privileges required">>,
{error, xmpp:err_forbidden(Txt, Lang)}
end;
@@ -4328,7 +4380,21 @@ element_size(El) ->
store_room(StateData) ->
store_room(StateData, []).
store_room(StateData, ChangesHints) ->
if (StateData#state.config)#config.persistent ->
% Let store persistent rooms or on those backends that have get_subscribed_rooms
erlang:put(muc_subscribers, StateData#state.subscribers),
ShouldStore = case (StateData#state.config)#config.persistent of
true ->
true;
_ ->
case ChangesHints of
[] ->
false;
_ ->
Mod = gen_mod:db_mod(StateData#state.server_host, mod_muc),
erlang:function_exported(Mod, get_subscribed_rooms, 3)
end
end,
if ShouldStore ->
mod_muc:store_room(StateData#state.server_host,
StateData#state.host, StateData#state.room,
make_opts(StateData),
@@ -4385,9 +4451,17 @@ send_wrapped(From, To, Packet, Node, State) ->
#subscriber{nodes = Nodes, jid = JID} ->
case lists:member(Node, Nodes) of
true ->
NewPacket = wrap(From, JID, Packet, Node),
MamEnabled = (State#state.config)#config.mam,
Id = case xmpp:get_subtag(Packet, #stanza_id{}) of
#stanza_id{id = Id2} ->
Id2;
_ ->
p1_rand:get_string()
end,
NewPacket = wrap(From, JID, Packet, Node, Id),
NewPacket2 = xmpp:put_meta(NewPacket, in_muc_mam, MamEnabled),
ejabberd_router:route(
xmpp:set_from_to(NewPacket, State#state.jid, JID));
xmpp:set_from_to(NewPacket2, State#state.jid, JID));
false ->
ok
end
@@ -4420,16 +4494,17 @@ send_wrapped(From, To, Packet, Node, State) ->
ejabberd_router:route(xmpp:set_from_to(Packet, From, To))
end.
-spec wrap(jid(), jid(), stanza(), binary()) -> message().
wrap(From, To, Packet, Node) ->
-spec wrap(jid(), jid(), stanza(), binary(), binary()) -> message().
wrap(From, To, Packet, Node, Id) ->
El = xmpp:set_from_to(Packet, From, To),
#message{
sub_els = [#ps_event{
items = #ps_items{
node = Node,
items = [#ps_item{
id = p1_rand:get_string(),
sub_els = [El]}]}}]}.
id = Id,
sub_els = [#ps_event{
items = #ps_items{
node = Node,
items = [#ps_item{
id = Id,
sub_els = [El]}]}}]}.
-spec send_wrapped_multiple(jid(), map(), stanza(), binary(), state()) -> ok.
send_wrapped_multiple(From, Users, Packet, Node, State) ->
+7 -6
View File
@@ -409,14 +409,15 @@ import(_, _, _) ->
get_subscribed_rooms(LServer, Host, Jid) ->
JidS = jid:encode(Jid),
case catch ejabberd_sql:sql_query(
LServer,
?SQL("select @(room)s, @(nodes)s from muc_room_subscribers where jid=%(JidS)s"
" and host=%(Host)s")) of
case ejabberd_sql:sql_query(
LServer,
?SQL("select @(room)s, @(nodes)s from muc_room_subscribers "
"where jid=%(JidS)s and host=%(Host)s")) of
{selected, Subs} ->
[{jid:make(Room, Host, <<>>), ejabberd_sql:decode_term(Nodes)} || {Room, Nodes} <- Subs];
{ok, [{jid:make(Room, Host), ejabberd_sql:decode_term(Nodes)}
|| {Room, Nodes} <- Subs]};
_Error ->
[]
{error, db_failure}
end.
%%%===================================================================
+369 -105
View File
@@ -82,6 +82,8 @@
%% default value for the maximum number of user messages
-define(MAX_USER_MESSAGES, infinity).
-define(EMPTY_SPOOL_CACHE, offline_empty_cache).
-type c2s_state() :: ejabberd_c2s:state().
-callback init(binary(), gen_mod:opts()) -> any().
@@ -109,6 +111,7 @@ depends(_Host, _Opts) ->
start(Host, Opts) ->
Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
Mod:init(Host, Opts),
init_cache(Opts),
ejabberd_hooks:add(offline_message_hook, Host, ?MODULE,
store_packet, 50),
ejabberd_hooks:add(c2s_self_presence, Host, ?MODULE, c2s_self_presence, 50),
@@ -158,24 +161,59 @@ stop(Host) ->
reload(Host, NewOpts, OldOpts) ->
NewMod = gen_mod:db_mod(Host, NewOpts, ?MODULE),
OldMod = gen_mod:db_mod(Host, OldOpts, ?MODULE),
init_cache(NewOpts),
if NewMod /= OldMod ->
NewMod:init(Host, NewOpts);
true ->
ok
end.
init_cache(Opts) ->
case gen_mod:get_opt(use_mam_for_storage, Opts) of
true ->
MaxSize = gen_mod:get_opt(cache_size, Opts),
LifeTime = case gen_mod:get_opt(cache_life_time, Opts) of
infinity -> infinity;
I -> timer:seconds(I)
end,
COpts = [{max_size, MaxSize}, {cache_missed, false}, {life_time, LifeTime}],
ets_cache:new(?EMPTY_SPOOL_CACHE, COpts);
false ->
ets_cache:delete(?EMPTY_SPOOL_CACHE)
end.
-spec store_offline_msg(#offline_msg{}) -> ok | {error, full | any()}.
store_offline_msg(#offline_msg{us = {User, Server}} = Msg) ->
Mod = gen_mod:db_mod(Server, ?MODULE),
case get_max_user_messages(User, Server) of
infinity ->
Mod:store_message(Msg);
Limit ->
Num = count_offline_messages(User, Server),
if Num < Limit ->
store_offline_msg(#offline_msg{us = {User, Server}, packet = Pkt} = Msg) ->
UseMam = use_mam_for_user(User, Server),
case UseMam andalso xmpp:get_meta(Pkt, mam_archived, false) of
true ->
Mod = gen_mod:db_mod(Server, ?MODULE),
ets_cache:lookup(?EMPTY_SPOOL_CACHE, {User, Server},
fun() ->
case count_messages_in_db(User, Server) of
0 ->
case Mod:store_message(Msg) of
ok ->
{cache, ok};
Err ->
{nocache, Err}
end;
_ ->
{cache, ok}
end
end);
false ->
Mod = gen_mod:db_mod(Server, ?MODULE),
case get_max_user_messages(User, Server) of
infinity ->
Mod:store_message(Msg);
true ->
{error, full}
Limit ->
Num = count_messages_in_db(User, Server),
if Num < Limit ->
Mod:store_message(Msg);
true ->
{error, full}
end
end
end.
@@ -298,34 +336,44 @@ handle_offline_query(#iq{lang = Lang} = IQ) ->
-spec handle_offline_items_view(jid(), [offline_item()]) -> boolean().
handle_offline_items_view(JID, Items) ->
{U, S, R} = jid:tolower(JID),
lists:foldl(
fun(#offline_item{node = Node, action = view}, Acc) ->
case fetch_msg_by_node(JID, Node) of
{ok, OfflineMsg} ->
case offline_msg_to_route(S, OfflineMsg) of
{route, El} ->
NewEl = set_offline_tag(El, Node),
case ejabberd_sm:get_session_pid(U, S, R) of
Pid when is_pid(Pid) ->
Pid ! {route, NewEl};
none ->
ok
end,
Acc or true;
error ->
Acc or false
end;
error ->
Acc or false
end
end, false, Items).
case use_mam_for_user(U, S) of
true ->
false;
_ ->
lists:foldl(
fun(#offline_item{node = Node, action = view}, Acc) ->
case fetch_msg_by_node(JID, Node) of
{ok, OfflineMsg} ->
case offline_msg_to_route(S, OfflineMsg) of
{route, El} ->
NewEl = set_offline_tag(El, Node),
case ejabberd_sm:get_session_pid(U, S, R) of
Pid when is_pid(Pid) ->
Pid ! {route, NewEl};
none ->
ok
end,
Acc or true;
error ->
Acc or false
end;
error ->
Acc or false
end
end, false, Items) end.
-spec handle_offline_items_remove(jid(), [offline_item()]) -> boolean().
handle_offline_items_remove(JID, Items) ->
lists:foldl(
fun(#offline_item{node = Node, action = remove}, Acc) ->
Acc or remove_msg_by_node(JID, Node)
end, false, Items).
{U, S, _R} = jid:tolower(JID),
case use_mam_for_user(U, S) of
true ->
false;
_ ->
lists:foldl(
fun(#offline_item{node = Node, action = remove}, Acc) ->
Acc or remove_msg_by_node(JID, Node)
end, false, Items)
end.
-spec set_offline_tag(message(), binary()) -> message().
set_offline_tag(Msg, Node) ->
@@ -334,11 +382,11 @@ set_offline_tag(Msg, Node) ->
-spec handle_offline_fetch(jid()) -> ok.
handle_offline_fetch(#jid{luser = U, lserver = S} = JID) ->
ejabberd_sm:route(JID, {resend_offline, false}),
lists:foreach(
fun({Node, El}) ->
El1 = set_offline_tag(El, Node),
ejabberd_router:route(El1)
end, read_messages(U, S)).
lists:foreach(
fun({Node, El}) ->
El1 = set_offline_tag(El, Node),
ejabberd_router:route(El1)
end, read_messages(U, S)).
-spec fetch_msg_by_node(jid(), binary()) -> error | {ok, #offline_msg{}}.
fetch_msg_by_node(To, Seq) ->
@@ -370,31 +418,38 @@ need_to_store(_LServer, #message{type = error}) -> false;
need_to_store(LServer, #message{type = Type} = Packet) ->
case xmpp:has_subtag(Packet, #offline{}) of
false ->
case check_store_hint(Packet) of
store ->
true;
no_store ->
false;
none ->
Store = case Type of
groupchat ->
gen_mod:get_module_opt(
LServer, ?MODULE, store_groupchat);
headline ->
false;
_ ->
true
end,
case {Store, gen_mod:get_module_opt(
LServer, ?MODULE, store_empty_body)} of
{false, _} ->
false;
{_, true} ->
case misc:unwrap_mucsub_message(Packet) of
#message{type = groupchat} = Msg ->
need_to_store(LServer, Msg#message{type = chat});
#message{} = Msg ->
need_to_store(LServer, Msg);
_ ->
case check_store_hint(Packet) of
store ->
true;
{_, false} ->
Packet#message.body /= [];
{_, unless_chat_state} ->
not misc:is_standalone_chat_state(Packet)
no_store ->
false;
none ->
Store = case Type of
groupchat ->
gen_mod:get_module_opt(
LServer, ?MODULE, store_groupchat);
headline ->
false;
_ ->
true
end,
case {Store, gen_mod:get_module_opt(
LServer, ?MODULE, store_empty_body)} of
{false, _} ->
false;
{_, true} ->
true;
{_, false} ->
Packet#message.body /= [];
{_, unless_chat_state} ->
not misc:is_standalone_chat_state(Packet)
end
end
end;
true ->
@@ -413,7 +468,7 @@ store_packet({_Action, #message{from = From, to = To} = Packet} = Acc) ->
drop ->
Acc;
NewPacket ->
TimeStamp = p1_time_compat:timestamp(),
TimeStamp = erlang:timestamp(),
Expire = find_x_expire(TimeStamp, NewPacket),
OffMsg = #offline_msg{us = {LUser, LServer},
timestamp = TimeStamp,
@@ -508,15 +563,28 @@ c2s_self_presence(Acc) ->
-spec route_offline_messages(c2s_state()) -> ok.
route_offline_messages(#{jid := #jid{luser = LUser, lserver = LServer}} = State) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
case Mod:pop_messages(LUser, LServer) of
{ok, OffMsgs} ->
lists:foreach(
fun(OffMsg) ->
route_offline_message(State, OffMsg)
end, OffMsgs);
_ ->
ok
end.
Msgs = case Mod:pop_messages(LUser, LServer) of
{ok, OffMsgs} ->
case use_mam_for_user(LUser, LServer) of
true ->
ets_cache:delete(?EMPTY_SPOOL_CACHE, {LUser, LServer},
ejabberd_cluster:get_nodes()),
lists:map(
fun({_, #message{from = From, to = To} = Msg}) ->
#offline_msg{from = From, to = To,
us = {LUser, LServer},
packet = Msg}
end, read_mam_messages(LUser, LServer, OffMsgs));
_ ->
OffMsgs
end;
_ ->
[]
end,
lists:foreach(
fun(OffMsg) ->
route_offline_message(State, OffMsg)
end, Msgs).
-spec route_offline_message(c2s_state(), #offline_msg{}) -> ok.
route_offline_message(#{lserver := LServer} = State,
@@ -538,7 +606,7 @@ route_offline_message(#{lserver := LServer} = State,
-spec is_message_expired(erlang:timestamp() | never, message()) -> boolean().
is_message_expired(Expire, Msg) ->
TS = p1_time_compat:timestamp(),
TS = erlang:timestamp(),
Expire1 = case Expire of
undefined -> find_x_expire(TS, Msg);
_ -> Expire
@@ -576,19 +644,42 @@ remove_user(User, Server) ->
%% Helper functions:
-spec check_if_message_should_be_bounced(message()) -> boolean().
check_if_message_should_be_bounced(Packet) ->
case Packet of
#message{type = groupchat, to = #jid{lserver = LServer}} ->
gen_mod:get_module_opt(LServer, ?MODULE, bounce_groupchat);
#message{to = #jid{lserver = LServer}} ->
case misc:is_mucsub_message(Packet) of
true ->
gen_mod:get_module_opt(LServer, ?MODULE, bounce_groupchat);
_ ->
true
end;
_ ->
true
end.
%% Warn senders that their messages have been discarded:
-spec discard_warn_sender(message(), full | any()) -> ok.
discard_warn_sender(Packet, full) ->
ErrText = <<"Your contact offline message queue is "
"full. The message has been discarded.">>,
Lang = xmpp:get_lang(Packet),
Err = xmpp:err_resource_constraint(ErrText, Lang),
ejabberd_router:route_error(Packet, Err);
discard_warn_sender(Packet, _) ->
ErrText = <<"Database failure">>,
Lang = xmpp:get_lang(Packet),
Err = xmpp:err_internal_server_error(ErrText, Lang),
ejabberd_router:route_error(Packet, Err).
discard_warn_sender(Packet, Reason) ->
case check_if_message_should_be_bounced(Packet) of
true ->
Lang = xmpp:get_lang(Packet),
Err = case Reason of
full ->
ErrText = <<"Your contact offline message queue is "
"full. The message has been discarded.">>,
xmpp:err_resource_constraint(ErrText, Lang);
_ ->
ErrText = <<"Database failure">>,
xmpp:err_internal_server_error(ErrText, Lang)
end,
ejabberd_router:route_error(Packet, Err);
_ ->
ok
end.
webadmin_page(_, Host,
#request{us = _US, path = [<<"user">>, U, <<"queue">>],
@@ -618,25 +709,172 @@ offline_msg_to_route(LServer, #offline_msg{from = From, to = To} = R) ->
-spec read_messages(binary(), binary()) -> [{binary(), message()}].
read_messages(LUser, LServer) ->
Res = read_db_messages(LUser, LServer),
case use_mam_for_user(LUser, LServer) of
true ->
read_mam_messages(LUser, LServer, Res);
_ ->
Res
end.
-spec read_db_messages(binary(), binary()) -> [{binary(), message()}].
read_db_messages(LUser, LServer) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
CodecOpts = ejabberd_config:codec_options(LServer),
lists:flatmap(
fun({Seq, From, To, TS, El}) ->
Node = integer_to_binary(Seq),
try xmpp:decode(El, ?NS_CLIENT, CodecOpts) of
Pkt ->
Node = integer_to_binary(Seq),
Pkt1 = add_delay_info(Pkt, LServer, TS),
Pkt2 = xmpp:set_from_to(Pkt1, From, To),
[{Node, Pkt2}]
catch _:{xmpp_codec, Why} ->
?ERROR_MSG("failed to decode packet ~p "
"of user ~s: ~s",
[El, jid:encode(To),
xmpp:format_error(Why)]),
[]
end
end, Mod:read_message_headers(LUser, LServer)).
fun({Seq, From, To, TS, El}) ->
Node = integer_to_binary(Seq),
try xmpp:decode(El, ?NS_CLIENT, CodecOpts) of
Pkt ->
Node = integer_to_binary(Seq),
Pkt1 = add_delay_info(Pkt, LServer, TS),
Pkt2 = xmpp:set_from_to(Pkt1, From, To),
[{Node, Pkt2}]
catch _:{xmpp_codec, Why} ->
?ERROR_MSG("failed to decode packet ~p "
"of user ~s: ~s",
[El, jid:encode(To),
xmpp:format_error(Why)]),
[]
end
end, Mod:read_message_headers(LUser, LServer)).
-spec parse_marker_messages(binary(), [#offline_msg{} | {any(), message()}]) ->
{integer() | none, [message()]}.
parse_marker_messages(LServer, ReadMsgs) ->
{Timestamp, ExtraMsgs} = lists:foldl(
fun({_Node, #message{id = <<"ActivityMarker">>,
body = [], type = error} = Msg}, {T, E}) ->
case xmpp:get_subtag(Msg, #delay{}) of
#delay{stamp = Time} ->
if T == none orelse T > Time ->
{Time, E};
true ->
{T, E}
end
end;
(#offline_msg{from = From, to = To, timestamp = TS, packet = Pkt},
{T, E}) ->
try xmpp:decode(Pkt) of
#message{id = <<"ActivityMarker">>,
body = [], type = error} = Msg ->
TS2 = case TS of
undefined ->
case xmpp:get_subtag(Msg, #delay{}) of
#delay{stamp = TS0} ->
TS0;
_ ->
erlang:timestamp()
end;
_ ->
TS
end,
if T == none orelse T > TS2 ->
{TS2, E};
true ->
{T, E}
end;
Decoded ->
Pkt1 = add_delay_info(Decoded, LServer, TS),
{T, [xmpp:set_from_to(Pkt1, From, To) | E]}
catch _:{xmpp_codec, _Why} ->
{T, E}
end;
({_Node, Msg}, {T, E}) ->
{T, [Msg | E]}
end, {none, []}, ReadMsgs),
Start = case {Timestamp, ExtraMsgs} of
{none, [First|_]} ->
case xmpp:get_subtag(First, #delay{}) of
#delay{stamp = {Mega, Sec, Micro}} ->
{Mega, Sec, Micro+1};
_ ->
none
end;
{none, _} ->
none;
_ ->
Timestamp
end,
{Start, ExtraMsgs}.
-spec read_mam_messages(binary(), binary(), [#offline_msg{} | {any(), message()}]) ->
[{integer(), message()}].
read_mam_messages(LUser, LServer, ReadMsgs) ->
{Start, ExtraMsgs} = parse_marker_messages(LServer, ReadMsgs),
AllMsgs = case Start of
none ->
ExtraMsgs;
_ ->
MaxOfflineMsgs = case get_max_user_messages(LUser, LServer) of
Number when is_integer(Number) -> Number - length(ExtraMsgs);
infinity -> undefined;
_ -> 100 - length(ExtraMsgs)
end,
JID = jid:make(LUser, LServer, <<>>),
{MamMsgs, _, _} = mod_mam:select(LServer, JID, JID,
[{start, Start}],
#rsm_set{max = MaxOfflineMsgs,
before = <<"9999999999999999">>},
chat, only_messages),
MamMsgs2 = lists:map(
fun({_, _, #forwarded{sub_els = [MM | _], delay = #delay{stamp = MMT}}}) ->
add_delay_info(MM, LServer, MMT)
end, MamMsgs),
ExtraMsgs ++ MamMsgs2
end,
AllMsgs2 = lists:sort(
fun(A, B) ->
DA = case xmpp:get_subtag(A, #stanza_id{}) of
#stanza_id{id = IDA} ->
IDA;
_ -> case xmpp:get_subtag(A, #delay{}) of
#delay{stamp = STA} ->
integer_to_binary(misc:now_to_usec(STA));
_ ->
<<"unknown">>
end
end,
DB = case xmpp:get_subtag(B, #stanza_id{}) of
#stanza_id{id = IDB} ->
IDB;
_ -> case xmpp:get_subtag(B, #delay{}) of
#delay{stamp = STB} ->
integer_to_binary(misc:now_to_usec(STB));
_ ->
<<"unknown">>
end
end,
DA < DB
end, AllMsgs),
{AllMsgs3, _} = lists:mapfoldl(
fun(Msg, Counter) ->
{{Counter, Msg}, Counter + 1}
end, 1, AllMsgs2),
AllMsgs3.
-spec count_mam_messages(binary(), binary(), [#offline_msg{} | {any(), message()}]) ->
integer().
count_mam_messages(LUser, LServer, ReadMsgs) ->
{Start, ExtraMsgs} = parse_marker_messages(LServer, ReadMsgs),
case Start of
none ->
length(ExtraMsgs);
_ ->
MaxOfflineMsgs = case get_max_user_messages(LUser, LServer) of
Number when is_integer(Number) -> Number - length(ExtraMsgs);
infinity -> undefined;
_ -> 100 - length(ExtraMsgs)
end,
JID = jid:make(LUser, LServer, <<>>),
{_, _, Count} = mod_mam:select(LServer, JID, JID,
[{start, Start}],
#rsm_set{max = MaxOfflineMsgs,
before = <<"9999999999999999">>},
chat, only_count),
Count + length(ExtraMsgs)
end.
format_user_queue(Hdrs) ->
lists:map(
@@ -800,6 +1038,16 @@ webadmin_user_parse_query(Acc, _Action, _User, _Server,
count_offline_messages(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
case use_mam_for_user(User, Server) of
true ->
Res = read_db_messages(LUser, LServer),
count_mam_messages(LUser, LServer, Res);
_ ->
count_messages_in_db(LUser, LServer)
end.
-spec count_messages_in_db(binary(), binary()) -> non_neg_integer().
count_messages_in_db(LUser, LServer) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:count_messages(LUser, LServer).
@@ -807,7 +1055,7 @@ count_offline_messages(User, Server) ->
undefined | erlang:timestamp()) -> message().
add_delay_info(Packet, LServer, TS) ->
NewTS = case TS of
undefined -> p1_time_compat:timestamp();
undefined -> erlang:timestamp();
_ -> TS
end,
Packet1 = xmpp:put_meta(Packet, from_offline, true),
@@ -840,7 +1088,7 @@ import(LServer, {sql, _}, DBType, <<"spool">>,
#delay{stamp = {MegaSecs, Secs, _}} ->
{MegaSecs, Secs, 0};
false ->
p1_time_compat:timestamp()
erlang:timestamp()
end,
US = {LUser, LServer},
Expire = find_x_expire(TS, Msg),
@@ -850,18 +1098,34 @@ import(LServer, {sql, _}, DBType, <<"spool">>,
Mod = gen_mod:db_mod(DBType, ?MODULE),
Mod:import(OffMsg).
use_mam_for_user(_User, Server) ->
gen_mod:get_module_opt(Server, ?MODULE, use_mam_for_storage).
mod_opt_type(access_max_user_messages) ->
fun acl:shaper_rules_validator/1;
mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
mod_opt_type(store_groupchat) ->
fun(V) when is_boolean(V) -> V end;
mod_opt_type(bounce_groupchat) ->
fun(V) when is_boolean(V) -> V end;
mod_opt_type(use_mam_for_storage) ->
fun(V) when is_boolean(V) -> V end;
mod_opt_type(store_empty_body) ->
fun (V) when is_boolean(V) -> V;
(unless_chat_state) -> unless_chat_state
end;
mod_opt_type(O) when O == cache_life_time; O == cache_size ->
fun (I) when is_integer(I), I > 0 -> I;
(infinity) -> infinity
end.
mod_options(Host) ->
[{db_type, ejabberd_config:default_db(Host, ?MODULE)},
{access_max_user_messages, max_user_offline_messages},
{store_empty_body, unless_chat_state},
{store_groupchat, false}].
{use_mam_for_storage, false},
{bounce_groupchat, false},
{store_groupchat, false},
{cache_size, ejabberd_config:cache_size(Host)},
{cache_life_time, ejabberd_config:cache_life_time(Host)}].
+2 -2
View File
@@ -63,7 +63,7 @@ pop_messages(LUser, LServer) ->
end.
remove_expired_messages(_LServer) ->
TimeStamp = p1_time_compat:timestamp(),
TimeStamp = erlang:timestamp(),
F = fun () ->
mnesia:write_lock_table(offline_msg),
mnesia:foldl(fun (Rec, _Acc) ->
@@ -81,7 +81,7 @@ remove_expired_messages(_LServer) ->
mnesia:transaction(F).
remove_old_messages(Days, _LServer) ->
S = p1_time_compat:system_time(seconds) - 60 * 60 * 24 * Days,
S = erlang:system_time(second) - 60 * 60 * 24 * Days,
MegaSecs1 = S div 1000000,
Secs1 = S rem 1000000,
TimeStamp = {MegaSecs1, Secs1, 0},
+1 -1
View File
@@ -94,7 +94,7 @@ remove_old_messages(Days, LServer) ->
ejabberd_sql:sql_query_t(
?SQL("DELETE FROM spool"
" WHERE created_at <"
" NOW() - INTERVAL '%(Days)d DAY'"));
" NOW() - %(Days)d * INTERVAL '1 DAY'"));
(_, _) ->
ejabberd_sql:sql_query_t(
?SQL("DELETE FROM spool"
+5 -4
View File
@@ -131,10 +131,9 @@ handle_info({iq_reply, #iq{type = error}, JID}, State) ->
handle_info({iq_reply, #iq{}, _JID}, State) ->
{noreply, State};
handle_info({iq_reply, timeout, JID}, State) ->
Timers = del_timer(JID, State#state.timers),
ejabberd_hooks:run(user_ping_timeout, State#state.host,
[JID]),
case State#state.timeout_action of
Timers = case State#state.timeout_action of
kill ->
#jid{user = User, server = Server,
resource = Resource} =
@@ -143,8 +142,10 @@ handle_info({iq_reply, timeout, JID}, State) ->
of
Pid when is_pid(Pid) -> ejabberd_c2s:close(Pid, ping_timeout);
_ -> ok
end;
_ -> ok
end,
del_timer(JID, State#state.timers);
_ ->
State#state.timers
end,
{noreply, State#state{timers = Timers}};
handle_info({timeout, _TRef, {ping, JID}}, State) ->
+1 -1
View File
@@ -80,7 +80,7 @@ check_packet(Acc, _, _, _) ->
update(Server, JID, Dir) ->
StormCount = gen_mod:get_module_opt(Server, ?MODULE, count),
TimeInterval = gen_mod:get_module_opt(Server, ?MODULE, interval),
TimeStamp = p1_time_compat:system_time(seconds),
TimeStamp = erlang:system_time(second),
case read(Dir) of
undefined ->
write(Dir,
+1 -1
View File
@@ -92,7 +92,7 @@ set_lists(#privacy{us = {LUser, LServer},
lists:foreach(
fun({Name, List}) ->
add_privacy_list(LUser, LServer, Name),
{selected, [<<"id">>], [[I]]} =
{selected, [{I}]} =
get_privacy_list_id_t(LUser, LServer, Name),
RItems = lists:map(fun item_to_raw/1, List),
set_privacy_list(I, RItems),
+4 -5
View File
@@ -38,7 +38,7 @@
wait_for_request/2, wait_for_activation/2,
stream_established/2]).
-export([start/2, stop/1, start_link/2, start_link/3, activate/2,
-export([start/3, stop/1, start_link/3, activate/2,
relay/3, accept/1, listen_opt_type/1,
listen_options/0]).
@@ -65,20 +65,19 @@ code_change(_OldVsn, StateName, StateData, _Extra) ->
%%-------------------------------
start({gen_tcp, Socket}, Opts1) ->
start(gen_tcp, Socket, Opts1) ->
{[{server_host, Host}], Opts} = lists:partition(
fun({server_host, _}) -> true;
(_) -> false
end, Opts1),
p1_fsm:start(?MODULE, [Socket, Host, Opts], []).
start_link({gen_tcp, Socket}, Opts1) ->
start_link(gen_tcp, Socket, Opts1) ->
{[{server_host, Host}], Opts} = lists:partition(
fun({server_host, _}) -> true;
(_) -> false
end, Opts1),
start_link(Socket, Host, Opts).
start_link(Socket, Host, Opts);
start_link(Socket, Host, Opts) ->
p1_fsm:start_link(?MODULE, [Socket, Host, Opts], []).
+4 -4
View File
@@ -1805,7 +1805,7 @@ publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload, PubOpts, Access
(DeliverPayloads or PersistItems) and (PayloadCount > 1) ->
{error, extended_error(xmpp:err_bad_request(),
err_invalid_payload())};
(not (DeliverPayloads or PersistItems)) and (PayloadCount > 0) ->
(not DeliverPayloads) and (PayloadCount > 0) ->
{error, extended_error(xmpp:err_bad_request(),
err_item_forbidden())};
true ->
@@ -2571,7 +2571,7 @@ sub_option_can_deliver(nodes, _, {subscription_type, items}) -> false;
sub_option_can_deliver(_, _, {subscription_depth, all}) -> true;
sub_option_can_deliver(_, Depth, {subscription_depth, D}) -> Depth =< D;
sub_option_can_deliver(_, _, {deliver, false}) -> false;
sub_option_can_deliver(_, _, {expire, When}) -> p1_time_compat:timestamp() < When;
sub_option_can_deliver(_, _, {expire, When}) -> erlang:timestamp() < When;
sub_option_can_deliver(_, _, _) -> true.
-spec presence_can_deliver(ljid(), boolean()) -> boolean().
@@ -3371,7 +3371,7 @@ set_cached_item({_, ServerHost, _}, Nidx, ItemId, Publisher, Payload) ->
set_cached_item(Host, Nidx, ItemId, Publisher, Payload) ->
case is_last_item_cache_enabled(Host) of
true ->
Stamp = {p1_time_compat:timestamp(), jid:tolower(jid:remove_resource(Publisher))},
Stamp = {erlang:timestamp(), jid:tolower(jid:remove_resource(Publisher))},
Item = #pubsub_last_item{nodeid = {Host, Nidx},
itemid = ItemId,
creation = Stamp,
@@ -3748,7 +3748,7 @@ err_unsupported_access_model() ->
-spec uniqid() -> mod_pubsub:itemId().
uniqid() ->
{T1, T2, T3} = p1_time_compat:timestamp(),
{T1, T2, T3} = erlang:timestamp(),
(str:format("~.16B~.16B~.16B", [T1, T2, T3])).
-spec add_message_type(message(), message_type()) -> message().
+8 -3
View File
@@ -163,7 +163,7 @@ get_commands_spec() ->
-spec delete_old_sessions(non_neg_integer()) -> ok | any().
delete_old_sessions(Days) ->
CurrentTime = p1_time_compat:system_time(micro_seconds),
CurrentTime = erlang:system_time(microsecond),
Diff = Days * 24 * 60 * 60 * 1000000,
TimeStamp = misc:usec_to_now(CurrentTime - Diff),
DBTypes = lists:usort(
@@ -682,8 +682,13 @@ get_body_text(#message{body = Body} = Msg) ->
end.
-spec body_is_encrypted(message()) -> boolean().
body_is_encrypted(#message{sub_els = SubEls}) ->
lists:keyfind(<<"encrypted">>, #xmlel.name, SubEls) /= false.
body_is_encrypted(#message{sub_els = MsgEls}) ->
case lists:keyfind(<<"encrypted">>, #xmlel.name, MsgEls) of
#xmlel{children = EncEls} ->
lists:keyfind(<<"payload">>, #xmlel.name, EncEls) /= false;
false ->
false
end.
-spec inspect_error(iq()) -> {atom(), binary()}.
inspect_error(IQ) ->
+133 -101
View File
@@ -34,13 +34,15 @@
-behaviour(gen_mod).
-export([start/2, stop/1, reload/3, stream_feature_register/2,
c2s_unauthenticated_packet/2, try_register/5,
c2s_unauthenticated_packet/2, try_register/4,
process_iq/1, send_registration_notifications/3,
transform_options/1, transform_module_options/1,
mod_opt_type/1, mod_options/1, opt_type/1, depends/2]).
mod_opt_type/1, mod_options/1, opt_type/1, depends/2,
format_error/1]).
-include("logger.hrl").
-include("xmpp.hrl").
-include("translate.hrl").
start(Host, _Opts) ->
gen_iq_handler:add_iq_handler(ejabberd_local, Host,
@@ -271,110 +273,131 @@ try_register_or_set_password(User, Server, Password,
xmpp:make_error(IQ, xmpp:err_not_allowed())
end.
%% @doc Try to change password and return IQ response
try_set_password(User, Server, Password, #iq{lang = Lang, meta = M} = IQ) ->
try_set_password(User, Server, Password) ->
case is_strong_password(Server, Password) of
true ->
case ejabberd_auth:set_password(User, Server, Password) of
ok ->
?INFO_MSG("~s has changed password from ~s",
[jid:encode({User, Server, <<"">>}),
ejabberd_config:may_hide_data(
misc:ip_to_list(maps:get(ip, M, {0,0,0,0})))]),
xmpp:make_iq_result(IQ);
{error, empty_password} ->
Txt = <<"Empty password">>,
xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang));
{error, not_allowed} ->
Txt = <<"Changing password is not allowed">>,
xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
{error, invalid_jid} ->
xmpp:make_error(IQ, xmpp:err_jid_malformed());
{error, invalid_password} ->
Txt = <<"Incorrect password">>,
xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
Err ->
?ERROR_MSG("failed to register user ~s@~s: ~p",
[User, Server, Err]),
xmpp:make_error(IQ, xmpp:err_internal_server_error())
end;
error_preparing_password ->
ErrText = <<"The password contains unacceptable characters">>,
xmpp:make_error(IQ, xmpp:err_not_acceptable(ErrText, Lang));
false ->
ErrText = <<"The password is too weak">>,
xmpp:make_error(IQ, xmpp:err_not_acceptable(ErrText, Lang))
true ->
ejabberd_auth:set_password(User, Server, Password);
error_preparing_password ->
{error, invalid_password};
false ->
{error, weak_password}
end.
try_set_password(User, Server, Password, #iq{lang = Lang, meta = M} = IQ) ->
case try_set_password(User, Server, Password) of
ok ->
?INFO_MSG("~s has changed password from ~s",
[jid:encode({User, Server, <<"">>}),
ejabberd_config:may_hide_data(
misc:ip_to_list(maps:get(ip, M, {0,0,0,0})))]),
xmpp:make_iq_result(IQ);
{error, not_allowed} ->
Txt = ?T("Changing password is not allowed"),
xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
{error, invalid_jid = Why} ->
xmpp:make_error(IQ, xmpp:err_jid_malformed(format_error(Why), Lang));
{error, invalid_password = Why} ->
xmpp:make_error(IQ, xmpp:err_not_allowed(format_error(Why), Lang));
{error, weak_password = Why} ->
xmpp:make_error(IQ, xmpp:err_not_acceptable(format_error(Why), Lang));
{error, empty_password = Why} ->
xmpp:make_error(IQ, xmpp:err_bad_request(format_error(Why), Lang));
{error, db_failure = Why} ->
xmpp:make_error(IQ, xmpp:err_internal_server_error(format_error(Why), Lang));
{error, Why} ->
?ERROR_MSG("Failed to change password for user ~s@~s: ~s",
[User, Server, format_error(Why)]),
xmpp:make_error(IQ, xmpp:err_internal_server_error(format_error(Why), Lang))
end.
try_register(User, Server, Password, SourceRaw) ->
case jid:is_nodename(User) of
false ->
{error, invalid_jid};
true ->
case check_access(User, Server, SourceRaw) of
deny ->
{error, eaccess};
allow ->
Source = may_remove_resource(SourceRaw),
case check_timeout(Source) of
true ->
case is_strong_password(Server, Password) of
true ->
case ejabberd_auth:try_register(
User, Server, Password) of
ok ->
ok;
{error, _} = Err ->
remove_timeout(Source),
Err
end;
false ->
remove_timeout(Source),
{error, weak_password};
error_preparing_password ->
remove_timeout(Source),
{error, invalid_password}
end;
false ->
{error, wait}
end
end
end.
try_register(User, Server, Password, SourceRaw, Lang) ->
case jid:is_nodename(User) of
false -> {error, xmpp:err_bad_request(<<"Malformed username">>, Lang)};
_ ->
JID = jid:make(User, Server),
Access = gen_mod:get_module_opt(Server, ?MODULE, access),
IPAccess = get_ip_access(Server),
case {acl:match_rule(Server, Access, JID),
check_ip_access(SourceRaw, IPAccess)}
of
{deny, _} -> {error, xmpp:err_forbidden(<<"Access denied by service policy">>, Lang)};
{_, deny} -> {error, xmpp:err_forbidden(<<"Access denied by service policy">>, Lang)};
{allow, allow} ->
Source = may_remove_resource(SourceRaw),
case check_timeout(Source) of
true ->
case is_strong_password(Server, Password) of
true ->
case ejabberd_auth:try_register(User, Server,
Password)
of
ok ->
?INFO_MSG("The account ~s was registered "
"from IP address ~s",
[jid:encode({User, Server, <<"">>}),
ejabberd_config:may_hide_data(
ip_to_string(Source))]),
send_welcome_message(JID),
send_registration_notifications(
?MODULE, JID, Source),
ok;
Error ->
remove_timeout(Source),
case Error of
{error, exists} ->
Txt = <<"User already exists">>,
{error, xmpp:err_conflict(Txt, Lang)};
{error, invalid_jid} ->
{error, xmpp:err_jid_malformed()};
{error, invalid_password} ->
Txt = <<"Incorrect password">>,
{error, xmpp:err_not_allowed(Txt, Lang)};
{error, not_allowed} ->
{error, xmpp:err_not_allowed()};
{error, _} ->
?ERROR_MSG("failed to register user "
"~s@~s: ~p",
[User, Server, Error]),
{error, xmpp:err_internal_server_error()}
end
end;
error_preparing_password ->
remove_timeout(Source),
ErrText = <<"The password contains unacceptable characters">>,
{error, xmpp:err_not_acceptable(ErrText, Lang)};
false ->
remove_timeout(Source),
ErrText = <<"The password is too weak">>,
{error, xmpp:err_not_acceptable(ErrText, Lang)}
end;
false ->
ErrText =
<<"Users are not allowed to register accounts "
"so quickly">>,
{error, xmpp:err_resource_constraint(ErrText, Lang)}
end
end
case try_register(User, Server, Password, SourceRaw) of
ok ->
JID = jid:make(User, Server),
Source = may_remove_resource(SourceRaw),
?INFO_MSG("The account ~s was registered from IP address ~s",
[jid:encode({User, Server, <<"">>}),
ejabberd_config:may_hide_data(ip_to_string(Source))]),
send_welcome_message(JID),
send_registration_notifications(?MODULE, JID, Source);
{error, invalid_jid = Why} ->
{error, xmpp:err_jid_malformed(format_error(Why), Lang)};
{error, eaccess = Why} ->
{error, xmpp:err_forbidden(format_error(Why), Lang)};
{error, wait = Why} ->
{error, xmpp:err_resource_constraint(format_error(Why), Lang)};
{error, weak_password = Why} ->
{error, xmpp:err_not_acceptable(format_error(Why), Lang)};
{error, invalid_password = Why} ->
{error, xmpp:err_not_acceptable(format_error(Why), Lang)};
{error, not_allowed = Why} ->
{error, xmpp:err_not_allowed(format_error(Why), Lang)};
{error, exists = Why} ->
{error, xmpp:err_conflict(format_error(Why), Lang)};
{error, db_failure = Why} ->
{error, xmpp:err_internal_server_error(format_error(Why), Lang)};
{error, Why} ->
?ERROR_MSG("Failed to register user ~s@~s: ~s",
[User, Server, format_error(Why)]),
{error, xmpp:err_internal_server_error(format_error(Why), Lang)}
end.
format_error(invalid_jid) ->
?T("Malformed username");
format_error(eaccess) ->
?T("Access denied by service policy");
format_error(wait) ->
?T("Users are not allowed to register accounts so quickly");
format_error(weak_password) ->
?T("The password is too weak");
format_error(invalid_password) ->
?T("The password contains unacceptable characters");
format_error(empty_password) ->
?T("Empty password");
format_error(not_allowed) ->
?T("Not allowed");
format_error(exists) ->
?T("User already exists");
format_error(db_failure) ->
?T("Database failure");
format_error(Unexpected) ->
list_to_binary(io_lib:format(?T("Unexpected error condition: ~p"), [Unexpected])).
send_welcome_message(JID) ->
Host = JID#jid.lserver,
case gen_mod:get_module_opt(Host, ?MODULE, welcome_message) of
@@ -422,7 +445,7 @@ check_timeout(undefined) -> true;
check_timeout(Source) ->
Timeout = ejabberd_config:get_option(registration_timeout, 600),
if is_integer(Timeout) ->
Priority = -p1_time_compat:system_time(seconds),
Priority = -erlang:system_time(second),
CleanPriority = Priority + Timeout,
F = fun () ->
Treap = case mnesia:read(mod_register_ip, treap, write)
@@ -597,6 +620,15 @@ check_ip_access(undefined, _IPAccess) ->
check_ip_access(IPAddress, IPAccess) ->
acl:match_rule(global, IPAccess, IPAddress).
check_access(User, Server, Source) ->
JID = jid:make(User, Server),
Access = gen_mod:get_module_opt(Server, ?MODULE, access),
IPAccess = get_ip_access(Server),
case acl:match_rule(Server, Access, JID) of
allow -> check_ip_access(Source, IPAccess);
deny -> deny
end.
mod_opt_type(access) -> fun acl:access_rules_validator/1;
mod_opt_type(access_from) -> fun acl:access_rules_validator/1;
mod_opt_type(access_remove) -> fun acl:access_rules_validator/1;
+40 -23
View File
@@ -50,7 +50,8 @@
webadmin_user/4, get_versioning_feature/2,
roster_versioning_enabled/1, roster_version/2,
mod_opt_type/1, mod_options/1, set_roster/1, del_roster/3,
depends/2]).
process_rosteritems/5,
depends/2, set_item_and_notify_clients/3]).
-include("logger.hrl").
-include("xmpp.hrl").
@@ -137,7 +138,8 @@ reload(Host, NewOpts, OldOpts) ->
NewMod:init(Host, NewOpts);
true ->
ok
end.
end,
init_cache(NewMod, Host, NewOpts).
depends(_Host, _Opts) ->
[].
@@ -251,7 +253,7 @@ write_roster_version_t(LUser, LServer) ->
write_roster_version(LUser, LServer, true).
write_roster_version(LUser, LServer, InTransaction) ->
Ver = str:sha(term_to_binary(p1_time_compat:unique_integer())),
Ver = str:sha(term_to_binary(erlang:unique_integer())),
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:write_roster_version(LUser, LServer, InTransaction, Ver),
if InTransaction -> ok;
@@ -439,25 +441,37 @@ decode_item(Item, R, Managed) ->
process_iq_set(#iq{from = _From, to = To,
sub_els = [#roster_query{items = [QueryItem]}]} = IQ) ->
case set_item_and_notify_clients(To, QueryItem, false) of
ok ->
xmpp:make_iq_result(IQ);
E ->
?ERROR_MSG("roster set failed:~nIQ = ~s~nError = ~p",
[xmpp:pp(IQ), E]),
xmpp:make_error(IQ, xmpp:err_internal_server_error())
end.
-spec set_item_and_notify_clients(jid(), #roster_item{}, boolean()) -> ok | error.
set_item_and_notify_clients(To, #roster_item{jid = PeerJID} = RosterItem,
OverrideSubscription) ->
#jid{luser = LUser, lserver = LServer} = To,
LJID = jid:tolower(QueryItem#roster_item.jid),
PeerLJID = jid:tolower(PeerJID),
F = fun () ->
Item = get_roster_item(LUser, LServer, LJID),
Item2 = decode_item(QueryItem, Item, false),
Item3 = ejabberd_hooks:run_fold(roster_process_item,
LServer, Item2,
[LServer]),
case Item3#roster.subscription of
remove -> del_roster_t(LUser, LServer, LJID);
_ -> update_roster_t(LUser, LServer, LJID, Item3)
end,
case roster_version_on_db(LServer) of
true -> write_roster_version_t(LUser, LServer);
false -> ok
end,
{Item, Item3}
Item = get_roster_item(LUser, LServer, PeerLJID),
Item2 = decode_item(RosterItem, Item, OverrideSubscription),
Item3 = ejabberd_hooks:run_fold(roster_process_item,
LServer, Item2,
[LServer]),
case Item3#roster.subscription of
remove -> del_roster_t(LUser, LServer, PeerLJID);
_ -> update_roster_t(LUser, LServer, PeerLJID, Item3)
end,
case transaction(LUser, LServer, [LJID], F) of
case roster_version_on_db(LServer) of
true -> write_roster_version_t(LUser, LServer);
false -> ok
end,
{Item, Item3}
end,
case transaction(LUser, LServer, [PeerLJID], F) of
{atomic, {OldItem, Item}} ->
push_item(To, OldItem, Item),
case Item#roster.subscription of
@@ -466,11 +480,9 @@ process_iq_set(#iq{from = _From, to = To,
_ ->
ok
end,
xmpp:make_iq_result(IQ);
ok;
E ->
?ERROR_MSG("roster set failed:~nIQ = ~s~nError = ~p",
[xmpp:pp(IQ), E]),
xmpp:make_error(IQ, xmpp:err_internal_server_error())
E
end.
push_item(To, OldItem, NewItem) ->
@@ -891,6 +903,11 @@ is_subscribed(From, #jid{luser = LUser, lserver = LServer}) ->
(Sub /= none) orelse (Ask == subscribe)
orelse (Ask == out) orelse (Ask == both).
process_rosteritems(ActionS, SubsS, AsksS, UsersS, ContactsS) ->
LServer = ejabberd_config:get_myname(),
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:process_rosteritems(ActionS, SubsS, AsksS, UsersS, ContactsS).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
webadmin_page(_, Host,
+138
View File
@@ -31,11 +31,13 @@
get_roster/2, get_roster_item/3, roster_subscribe/4,
remove_user/2, update_roster/4, del_roster/3, transaction/2,
read_subscription_and_groups/3, import/3, create_roster/1,
process_rosteritems/5,
use_cache/2]).
-export([need_transform/1, transform/1]).
-include("mod_roster.hrl").
-include("logger.hrl").
-include("xmpp.hrl").
%%%===================================================================
%%% API
@@ -154,6 +156,142 @@ transform(#roster_version{us = {U, S}, version = Ver} = R) ->
R#roster_version{us = {iolist_to_binary(U), iolist_to_binary(S)},
version = iolist_to_binary(Ver)}.
%%%===================================================================
process_rosteritems(ActionS, SubsS, AsksS, UsersS, ContactsS) ->
Action = case ActionS of
"list" -> list;
"delete" -> delete
end,
Subs = lists:foldl(
fun(any, _) -> [none, from, to, both];
(Sub, Subs) -> [Sub | Subs]
end,
[],
[list_to_atom(S) || S <- string:tokens(SubsS, ":")]
),
Asks = lists:foldl(
fun(any, _) -> [none, out, in];
(Ask, Asks) -> [Ask | Asks]
end,
[],
[list_to_atom(S) || S <- string:tokens(AsksS, ":")]
),
Users = lists:foldl(
fun("any", _) -> ["*", "*@*"];
(U, Us) -> [U | Us]
end,
[],
[S || S <- string:tokens(UsersS, ":")]
),
Contacts = lists:foldl(
fun("any", _) -> ["*", "*@*"];
(U, Us) -> [U | Us]
end,
[],
[S || S <- string:tokens(ContactsS, ":")]
),
rosteritem_purge({Action, Subs, Asks, Users, Contacts}).
%% @spec ({Action::atom(), Subs::[atom()], Asks::[atom()], User::string(), Contact::string()}) -> {atomic, ok}
rosteritem_purge(Options) ->
Num_rosteritems = mnesia:table_info(roster, size),
io:format("There are ~p roster items in total.~n", [Num_rosteritems]),
Key = mnesia:dirty_first(roster),
rip(Key, Options, {0, Num_rosteritems, 0, 0}, []).
rip('$end_of_table', _Options, Counters, Res) ->
print_progress_line(Counters),
Res;
rip(Key, Options, {Pr, NT, NV, ND}, Res) ->
Key_next = mnesia:dirty_next(roster, Key),
{Action, _, _, _, _} = Options,
{ND2, Res2} = case decide_rip(Key, Options) of
true ->
Jids = apply_action(Action, Key),
{ND+1, [Jids | Res]};
false ->
{ND, Res}
end,
NV2 = NV+1,
Pr2 = print_progress_line({Pr, NT, NV2, ND2}),
rip(Key_next, Options, {Pr2, NT, NV2, ND2}, Res2).
apply_action(list, Key) ->
{User, Server, JID} = Key,
{RUser, RServer, _} = JID,
Jid1string = <<User/binary, "@", Server/binary>>,
Jid2string = <<RUser/binary, "@", RServer/binary>>,
io:format("Matches: ~s ~s~n", [Jid1string, Jid2string]),
{Jid1string, Jid2string};
apply_action(delete, Key) ->
R = apply_action(list, Key),
mnesia:dirty_delete(roster, Key),
R.
print_progress_line({_Pr, 0, _NV, _ND}) ->
ok;
print_progress_line({Pr, NT, NV, ND}) ->
Pr2 = trunc((NV/NT)*100),
case Pr == Pr2 of
true ->
ok;
false ->
io:format("Progress ~p% - visited ~p - deleted ~p~n", [Pr2, NV, ND])
end,
Pr2.
decide_rip(Key, {_Action, Subs, Asks, User, Contact}) ->
case catch mnesia:dirty_read(roster, Key) of
[RI] ->
lists:member(RI#roster.subscription, Subs)
andalso lists:member(RI#roster.ask, Asks)
andalso decide_rip_jid(RI#roster.us, User)
andalso decide_rip_jid(RI#roster.jid, Contact);
_ ->
false
end.
%% Returns true if the server of the JID is included in the servers
decide_rip_jid({UName, UServer, _UResource}, Match_list) ->
decide_rip_jid({UName, UServer}, Match_list);
decide_rip_jid({UName, UServer}, Match_list) ->
lists:any(
fun(Match_string) ->
MJID = jid:decode(list_to_binary(Match_string)),
MName = MJID#jid.luser,
MServer = MJID#jid.lserver,
Is_server = is_glob_match(UServer, MServer),
case MName of
<<>> when UName == <<>> ->
Is_server;
<<>> ->
false;
_ ->
Is_server
andalso is_glob_match(UName, MName)
end
end,
Match_list).
%% Copied from ejabberd-2.0.0/src/acl.erl
is_regexp_match(String, RegExp) ->
case ejabberd_regexp:run(String, RegExp) of
nomatch ->
false;
match ->
true;
{error, ErrDesc} ->
io:format(
"Wrong regexp ~p in ACL: ~p",
[RegExp, ErrDesc]),
false
end.
is_glob_match(String, <<"!", Glob/binary>>) ->
not is_regexp_match(String, ejabberd_regexp:sh_to_awk(Glob));
is_glob_match(String, Glob) ->
is_regexp_match(String, ejabberd_regexp:sh_to_awk(Glob)).
%%%===================================================================
%%% Internal functions
%%%===================================================================
+38
View File
@@ -33,11 +33,13 @@
get_roster/2, get_roster_item/3, roster_subscribe/4,
read_subscription_and_groups/3, remove_user/2,
update_roster/4, del_roster/3, transaction/2,
process_rosteritems/5,
import/3, export/1, raw_to_record/2]).
-include("mod_roster.hrl").
-include("ejabberd_sql_pt.hrl").
-include("logger.hrl").
-include("jid.hrl").
%%%===================================================================
%%% API
@@ -375,3 +377,39 @@ format_row_error(User, Server, Why) ->
{ask, Ask} -> ["Malformed 'ask' field with value '", Ask, "'"]
end,
" detected for ", User, "@", Server, " in table 'rosterusers'"].
process_rosteritems(ActionS, SubsS, AsksS, UsersS, ContactsS) ->
process_rosteritems_sql(ActionS, list_to_atom(SubsS), list_to_atom(AsksS),
list_to_binary(UsersS), list_to_binary(ContactsS)).
process_rosteritems_sql(ActionS, Subscription, Ask, SLocalJID, SJID) ->
[LUser, LServer] = binary:split(SLocalJID, <<"@">>),
SSubscription = case Subscription of
any -> <<"_">>;
both -> <<"B">>;
to -> <<"T">>;
from -> <<"F">>;
none -> <<"N">>
end,
SAsk = case Ask of
any -> <<"_">>;
subscribe -> <<"S">>;
unsubscribe -> <<"U">>;
both -> <<"B">>;
out -> <<"O">>;
in -> <<"I">>;
none -> <<"N">>
end,
{selected, List} = ejabberd_sql:sql_query(
LServer,
?SQL("select @(username)s, @(jid)s from rosterusers "
"where username LIKE %(LUser)s"
" and %(LServer)H"
" and jid LIKE %(SJID)s"
" and subscription LIKE %(SSubscription)s"
" and ask LIKE %(SAsk)s")),
case ActionS of
"delete" -> [mod_roster:del_roster(User, LServer, jid:decode(Contact)) || {User, Contact} <- List];
"list" -> ok
end,
List.
+2 -2
View File
@@ -302,7 +302,7 @@ add_record_route_and_set_uri(URI, LServer, #sip{hdrs = Hdrs} = Req) ->
case need_record_route(LServer) of
true ->
RR_URI = get_configured_record_route(LServer),
TS = (integer_to_binary(p1_time_compat:system_time(seconds))),
TS = (integer_to_binary(erlang:system_time(second))),
Sign = make_sign(TS, Hdrs),
User = <<TS/binary, $-, Sign/binary>>,
NewRR_URI = RR_URI#uri{user = User},
@@ -339,7 +339,7 @@ is_signed_by_me(TS_Sign, Hdrs) ->
try
[TSBin, Sign] = str:tokens(TS_Sign, <<"-">>),
TS = (binary_to_integer(TSBin)),
NowTS = p1_time_compat:system_time(seconds),
NowTS = erlang:system_time(second),
true = (NowTS - TS) =< ?SIGN_LIFETIME,
Sign == make_sign(TSBin, Hdrs)
catch _:_ ->
+2 -2
View File
@@ -50,7 +50,7 @@
socket = #sip_socket{} :: #sip_socket{},
call_id = <<"">> :: binary(),
cseq = 0 :: non_neg_integer(),
timestamp = p1_time_compat:timestamp() :: erlang:timestamp(),
timestamp = erlang:timestamp() :: erlang:timestamp(),
contact :: {binary(), #uri{}, [{binary(), binary()}]},
flow_tref :: reference() | undefined,
reg_tref = make_ref() :: reference(),
@@ -243,7 +243,7 @@ register_session(US, SIPSocket, CallID, CSeq, IsOutboundSupported,
socket = SIPSocket,
call_id = CallID,
cseq = CSeq,
timestamp = p1_time_compat:timestamp(),
timestamp = erlang:timestamp(),
contact = Contact,
expires = Expires}
end, ContactsWithExpires),
+26 -14
View File
@@ -523,7 +523,7 @@ mgmt_queue_add(#{mgmt_stanzas_out := NumStanzasOut,
4294967295 -> 0;
Num -> Num + 1
end,
Queue1 = p1_queue:in({NewNum, p1_time_compat:timestamp(), Pkt}, Queue),
Queue1 = p1_queue:in({NewNum, erlang:timestamp(), Pkt}, Queue),
State1 = State#{mgmt_queue => Queue1, mgmt_stanzas_out => NewNum},
check_queue_length(State1).
@@ -591,22 +591,25 @@ route_unacked_stanzas(#{mgmt_state := MgmtState,
end,
?DEBUG("Re-routing ~B unacknowledged stanza(s) to ~s",
[p1_queue:len(Queue), jid:encode(JID)]),
p1_queue:foreach(
fun({_, _Time, #presence{from = From}}) ->
?DEBUG("Dropping presence stanza from ~s", [jid:encode(From)]);
({_, _Time, #iq{} = El}) ->
p1_queue:foldl(
fun({_, _Time, #presence{from = From}}, Acc) ->
?DEBUG("Dropping presence stanza from ~s", [jid:encode(From)]),
Acc;
({_, _Time, #iq{} = El}, Acc) ->
Txt = <<"User session terminated">>,
ejabberd_router:route_error(
El, xmpp:err_service_unavailable(Txt, Lang));
({_, _Time, #message{from = From, meta = #{carbon_copy := true}}}) ->
El, xmpp:err_service_unavailable(Txt, Lang)),
Acc;
({_, _Time, #message{from = From, meta = #{carbon_copy := true}}}, Acc) ->
%% XEP-0280 says: "When a receiving server attempts to deliver a
%% forked message, and that message bounces with an error for
%% any reason, the receiving server MUST NOT forward that error
%% back to the original sender." Resending such a stanza could
%% easily lead to unexpected results as well.
?DEBUG("Dropping forwarded message stanza from ~s",
[jid:encode(From)]);
({_, Time, #message{} = Msg}) ->
[jid:encode(From)]),
Acc;
({_, Time, #message{} = Msg}, Acc) ->
case ejabberd_hooks:run_fold(message_is_archived,
LServer, false,
[State, Msg]) of
@@ -615,17 +618,26 @@ route_unacked_stanzas(#{mgmt_state := MgmtState,
[jid:encode(xmpp:get_from(Msg))]);
false when ResendOnTimeout ->
NewEl = add_resent_delay_info(State, Msg, Time),
ejabberd_router:route(NewEl);
NewEl2 = case Acc of
first_resend ->
xmpp:put_meta(NewEl, first_from_queue, true);
_ ->
NewEl
end,
ejabberd_router:route(NewEl2),
false;
false ->
Txt = <<"User session terminated">>,
ejabberd_router:route_error(
Msg, xmpp:err_service_unavailable(Txt, Lang))
Msg, xmpp:err_service_unavailable(Txt, Lang)),
Acc
end;
({_, _Time, El}) ->
({_, _Time, El}, Acc) ->
%% Raw element of type 'error' resulting from a validation error
%% We cannot pass it to the router, it will generate an error
?DEBUG("Do not route raw element from ack queue: ~p", [El])
end, Queue);
?DEBUG("Do not route raw element from ack queue: ~p", [El]),
Acc
end, first_resend, Queue);
route_unacked_stanzas(_State) ->
ok.
+1 -1
View File
@@ -54,7 +54,7 @@ process_local_iq(#iq{type = set, lang = Lang} = IQ) ->
Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
process_local_iq(#iq{type = get} = IQ) ->
Now = p1_time_compat:timestamp(),
Now = erlang:timestamp(),
Now_universal = calendar:now_to_universal_time(Now),
Now_local = calendar:universal_time_to_local_time(Now_universal),
Seconds_diff =
+14 -6
View File
@@ -429,18 +429,26 @@ mk_search_form(JID, ServerHost, Lang) ->
Fs = [mk_tfield(Label, Var, Lang) || {Label, Var} <- SearchFields],
X = #xdata{type = form,
title = Title,
instructions =
[translate:translate(
Lang,
<<"Fill in the form to search for any matching "
"Jabber User (Add * to the end of field "
"to match substring)">>)],
instructions = [make_instructions(Mod, Lang)],
fields = Fs},
#search{instructions =
translate:translate(
Lang, <<"You need an x:data capable client to search">>),
xdata = X}.
make_instructions(Mod, Lang) ->
Fill = translate:translate(
Lang,
<<"Fill in the form to search for any matching "
"Jabber User">>),
Add = translate:translate(
Lang,
<<" (Add * to the end of field to match substring)">>),
case Mod of
mod_vcard_mnesia -> Fill;
_ -> str:concat(Fill, Add)
end.
-spec search_result(binary(), jid(), binary(), [xdata_field()]) -> xdata().
search_result(Lang, JID, ServerHost, XFields) ->
Mod = gen_mod:db_mod(ServerHost, ?MODULE),

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