Compare commits
160 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 571a786b9b | |||
| 4eaba13189 | |||
| 8b301fc93e | |||
| a06bdb1721 | |||
| b90fe4c5c9 | |||
| 50f93023f5 | |||
| 226c09f031 | |||
| dc126b86bb | |||
| 44e1af25e5 | |||
| 5b62a05205 | |||
| 5642338a73 | |||
| 3f7a850ae8 | |||
| 729c8b0d24 | |||
| 4424f40186 | |||
| 17f9ffb7e7 | |||
| 2b523030cf | |||
| 63e9b82a46 | |||
| 937f07a4cc | |||
| 2db547b557 | |||
| 0ed638c7fb | |||
| 7c16e29984 | |||
| 7a03a125aa | |||
| 2aa181658a | |||
| a4c3ea0dfb | |||
| 3c95764d1a | |||
| e996579dd1 | |||
| 122cb4b959 | |||
| 1452023c93 | |||
| 3d8711f708 | |||
| 4b6f1195c6 | |||
| e427358e08 | |||
| 2fff4d1ea6 | |||
| 945c58d3db | |||
| a04ea19f03 | |||
| a6f7d7ce23 | |||
| 4dc8549738 | |||
| 7d23cd2899 | |||
| 8207ea18bf | |||
| 5b863c25ae | |||
| 83b790c7c9 | |||
| 3d434cfcef | |||
| 4e7bf9207e | |||
| ecce318304 | |||
| b18f53c5ce | |||
| 09d67a20d3 | |||
| 94f7bbc239 | |||
| be14caddf4 | |||
| 7a8de9cfcf | |||
| 6b0f7f2a24 | |||
| 7a107c02a5 | |||
| cd2d62bffd | |||
| f7bc969729 | |||
| 1ec3525ed6 | |||
| 492da2baac | |||
| 25f7ce0cb6 | |||
| 0d2720d7ab | |||
| ef1a75a628 | |||
| 7eb5a0877b | |||
| 2562f89005 | |||
| eac7a77b6a | |||
| 63c12d18cc | |||
| b83d30fd07 | |||
| b071c4906f | |||
| c7d04a82a2 | |||
| 268750e3b7 | |||
| 11e963aa78 | |||
| 4af99f7b03 | |||
| c56209a27d | |||
| 39bbc7cad8 | |||
| d32a0ce566 | |||
| 1db22c9656 | |||
| a0f48cf52f | |||
| eff70951c5 | |||
| c550d36581 | |||
| aaf674160b | |||
| faf9b20ac0 | |||
| 17ff62d4af | |||
| b716b835c4 | |||
| 830a2f209a | |||
| 05d088b104 | |||
| b76f90fe39 | |||
| bcfe50f817 | |||
| 17444ba84e | |||
| 7ab7390b9c | |||
| a0c8c70c9c | |||
| 5819733de6 | |||
| cc5829bc33 | |||
| 8b501f5fe6 | |||
| fc043dd8cf | |||
| cbf3fec2c8 | |||
| edba1aebb5 | |||
| d2ea905926 | |||
| feb4c7f5e9 | |||
| 5faae61bef | |||
| cc892ddc01 | |||
| e623678747 | |||
| 7c45b52c86 | |||
| 0789a145fc | |||
| 7f14826564 | |||
| 875b2daff1 | |||
| 4e2c95fe58 | |||
| 83653c0338 | |||
| 3706e35b86 | |||
| c96a925fde | |||
| 009b9a1fd0 | |||
| 6dac0a602e | |||
| 8761e6e0e0 | |||
| c5a06e9d06 | |||
| 2e007f1607 | |||
| 9f3ccd604e | |||
| 909a505d65 | |||
| 3013f1b9bc | |||
| 17b9dc6035 | |||
| e66f594901 | |||
| 4e591a73c5 | |||
| 623a9ec3ba | |||
| 65a6532cd9 | |||
| 33c10867e3 | |||
| d085fff14b | |||
| cbac8a604a | |||
| d96ab48c6b | |||
| ed2abe471a | |||
| 7eef966a04 | |||
| 5c69122bbe | |||
| 9b040f65a3 | |||
| 24b9b69783 | |||
| 0c78e01088 | |||
| a7310ffea1 | |||
| 8e05fd1d24 | |||
| 063869603a | |||
| ee2b441b0f | |||
| 1f2b8adc28 | |||
| 51e7ccc16d | |||
| cf733b0913 | |||
| 6545d55473 | |||
| 6129720838 | |||
| 291c05715b | |||
| 4a920dca5a | |||
| 5077d39600 | |||
| 3b16afeda7 | |||
| 89db022da4 | |||
| 0715e62a41 | |||
| 7a622c3392 | |||
| 629e568294 | |||
| 333b010d54 | |||
| 1af2b2cfc7 | |||
| 328553ea3f | |||
| 946baa972d | |||
| e921b43754 | |||
| b5fa3b0e2b | |||
| a4222fe9b3 | |||
| 652858c7fe | |||
| 93cebbf4a3 | |||
| 598e00e80f | |||
| 2f46aebca2 | |||
| 9bfe5bb618 | |||
| 7511da0f26 | |||
| 456e87e8b2 | |||
| 538f35d05a | |||
| 77ac0584ed |
+2
-1
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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() | '_',
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()}).
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
);
|
||||
|
||||
@@ -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))
|
||||
);
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
@@ -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.
|
||||
|
||||
@@ -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) ->
|
||||
|
||||
@@ -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
@@ -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) ->
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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
@@ -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
@@ -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 "
|
||||
|
||||
@@ -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 ->
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) ->
|
||||
|
||||
@@ -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) ->
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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}),
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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].
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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.
|
||||
|
||||
@@ -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});
|
||||
|
||||
@@ -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}.
|
||||
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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}).
|
||||
|
||||
@@ -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
@@ -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) ->
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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)}].
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -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
@@ -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) ->
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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,
|
||||
|
||||
@@ -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
|
||||
%%%===================================================================
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 _:_ ->
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
Reference in New Issue
Block a user