aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKonstantinos Kallas <konstantinos.kallas@hotmail.com>2017-08-19 13:42:05 +0300
committerKonstantinos Kallas <konstantinos.kallas@hotmail.com>2017-08-19 13:42:05 +0300
commitf581e391ac71106090c8a79ba915b614cf833587 (patch)
treef80098cdbc46fcebbd039ab69c0000798b8f23ca
parentMerge remove_account_option branch (diff)
parentApply cosmetic changes to previous commit (diff)
Merge remote-tracking branch 'upstream/master'
-rw-r--r--.travis.yml1
-rw-r--r--Dockerfile2
-rw-r--r--README5
-rw-r--r--configure.ac4
-rw-r--r--ejabberd.yml.example2
-rwxr-xr-xejabberdctl.template4
-rw-r--r--mix.exs2
-rw-r--r--mix.lock20
-rw-r--r--rebar.config3
-rw-r--r--src/ejabberd_auth.erl4
-rw-r--r--src/ejabberd_auth_mnesia.erl2
-rw-r--r--src/ejabberd_bosh.erl38
-rw-r--r--src/ejabberd_c2s.erl27
-rw-r--r--src/ejabberd_captcha.erl27
-rw-r--r--src/ejabberd_config.erl5
-rw-r--r--src/ejabberd_http_ws.erl16
-rw-r--r--src/ejabberd_logger.erl3
-rw-r--r--src/ejabberd_receiver.erl4
-rw-r--r--src/ejabberd_regexp.erl2
-rw-r--r--src/ejabberd_s2s.erl18
-rw-r--r--src/ejabberd_s2s_out.erl2
-rw-r--r--src/ejabberd_sm.erl29
-rw-r--r--src/ejabberd_sql.erl30
-rw-r--r--src/ejd2sql.erl5
-rw-r--r--src/eldap.erl30
-rw-r--r--src/ext_mod.erl31
-rw-r--r--src/gen_mod.erl17
-rw-r--r--src/misc.erl36
-rw-r--r--src/mod_echo.erl41
-rw-r--r--src/mod_http_api.erl12
-rw-r--r--src/mod_http_fileserver.erl16
-rw-r--r--src/mod_http_upload.erl22
-rw-r--r--src/mod_irc.erl62
-rw-r--r--src/mod_irc_connection.erl10
-rw-r--r--src/mod_mam.erl19
-rw-r--r--src/mod_mix.erl103
-rw-r--r--src/mod_muc.erl88
-rw-r--r--src/mod_muc_admin.erl20
-rw-r--r--src/mod_muc_log.erl2
-rw-r--r--src/mod_muc_mnesia.erl7
-rw-r--r--src/mod_muc_riak.erl2
-rw-r--r--src/mod_muc_room.erl14
-rw-r--r--src/mod_multicast.erl14
-rw-r--r--src/mod_proxy65.erl4
-rw-r--r--src/mod_proxy65_service.erl95
-rw-r--r--src/mod_proxy65_stream.erl10
-rw-r--r--src/mod_pubsub.erl564
-rw-r--r--src/mod_push.erl596
-rw-r--r--src/mod_push_keepalive.erl236
-rw-r--r--src/mod_push_mnesia.erl204
-rw-r--r--src/mod_roster.erl10
-rw-r--r--src/mod_sip_proxy.erl7
-rw-r--r--src/mod_stream_mgmt.erl63
-rw-r--r--src/mod_vcard.erl82
-rw-r--r--src/mod_vcard_ldap.erl7
-rw-r--r--src/node_pep.erl2
-rw-r--r--src/node_pep_sql.erl2
-rw-r--r--src/prosody2ejabberd.erl118
-rw-r--r--src/pubsub_db_sql.erl94
-rw-r--r--src/randoms.erl15
-rw-r--r--test/ejabberd_SUITE.erl12
-rw-r--r--test/ejabberd_SUITE_data/ejabberd.yml16
-rwxr-xr-xtest/ejabberd_SUITE_data/extauth.py26
-rw-r--r--test/muc_tests.erl29
-rw-r--r--test/push_tests.erl234
65 files changed, 2414 insertions, 813 deletions
diff --git a/.travis.yml b/.travis.yml
index 2b1c92190..6e1533a61 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -4,6 +4,7 @@ otp_release:
- 17.5
- 18.3
- 19.2
+ - 20.0
services:
- riak
diff --git a/Dockerfile b/Dockerfile
index 6de6f5783..e5f3d78f3 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,7 +1,7 @@
FROM debian:jessie-slim
MAINTAINER Rafael Römhild <rafael@roemhild.de>
-ENV EJABBERD_BRANCH=17.04 \
+ENV EJABBERD_BRANCH=17.08 \
EJABBERD_USER=ejabberd \
EJABBERD_HTTPS=true \
EJABBERD_STARTTLS=true \
diff --git a/README b/README
index 67e062267..87b684f14 100644
--- a/README
+++ b/README
@@ -116,11 +116,14 @@ To compile ejabberd you need:
needed on systems with GNU Libc.
- ImageMagick's Convert program. Optional. For CAPTCHA challenges.
+If your system splits packages in libraries and development headers, you must
+install the development packages also.
### 1. Compile and install on *nix systems
To compile ejabberd, execute the following commands. The first one is only
-necessary if your source tree didn't come with a `configure` script.
+necessary if your source tree didn't come with a `configure` script (In this
+case you need autoconf installed).
./autogen.sh
./configure
diff --git a/configure.ac b/configure.ac
index 884db5d4e..edf54722c 100644
--- a/configure.ac
+++ b/configure.ac
@@ -3,8 +3,8 @@
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="6.1 (Erlang/OTP 17.1)"
-REQUIRE_ERLANG_MAX="9.0.0 (No Max)"
+REQUIRE_ERLANG_MIN="6.4 (Erlang/OTP 17.5)"
+REQUIRE_ERLANG_MAX="100.0.0 (No Max)"
AC_CONFIG_MACRO_DIR([m4])
diff --git a/ejabberd.yml.example b/ejabberd.yml.example
index ee3dda24c..72eb0b4b1 100644
--- a/ejabberd.yml.example
+++ b/ejabberd.yml.example
@@ -738,6 +738,8 @@ modules:
- "flat"
- "hometree"
- "pep" # pep requires mod_caps
+ mod_push: {}
+ mod_push_keepalive: {}
## mod_register:
##
## Protect In-Band account registrations with CAPTCHA.
diff --git a/ejabberdctl.template b/ejabberdctl.template
index edf9bea0d..9d47334a2 100755
--- a/ejabberdctl.template
+++ b/ejabberdctl.template
@@ -69,9 +69,7 @@ done
# define erl parameters
ERLANG_OPTS="+K $POLL -smp $SMP +P $ERL_PROCESSES $ERL_OPTIONS"
if [ "$FIREWALL_WINDOW" != "" ] ; then
- ERLANG_OPTS="$ERLANG_OPTS -kernel \
- inet_dist_listen_min ${FIREWALL_WINDOW%-*} \
- inet_dist_listen_max ${FIREWALL_WINDOW#*-}"
+ ERLANG_OPTS="$ERLANG_OPTS -kernel inet_dist_listen_min ${FIREWALL_WINDOW%-*} inet_dist_listen_max ${FIREWALL_WINDOW#*-}"
fi
if [ "$INET_DIST_INTERFACE" != "" ] ; then
INET_DIST_INTERFACE2=$("$ERL" -noshell -eval 'case inet:parse_address("'$INET_DIST_INTERFACE'") of {ok,IP} -> io:format("~p",[IP]); _ -> ok end.' -s erlang halt)
diff --git a/mix.exs b/mix.exs
index 025552f45..24b74f87c 100644
--- a/mix.exs
+++ b/mix.exs
@@ -3,7 +3,7 @@ defmodule Ejabberd.Mixfile do
def project do
[app: :ejabberd,
- version: "17.6.0",
+ version: "17.8.0",
description: description(),
elixir: "~> 1.4",
elixirc_paths: ["lib"],
diff --git a/mix.lock b/mix.lock
index 40eda1930..4b67809e5 100644
--- a/mix.lock
+++ b/mix.lock
@@ -1,18 +1,22 @@
-%{"cache_tab": {:hex, :cache_tab, "1.0.8", "eac8923f0f20c35e630317790c4d4c2629c5bc792753fa48eb5391bd39c80245", [:rebar3], [{:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}]},
- "distillery": {:hex, :distillery, "1.4.0", "d633cd322c8efa0428082b00b7f902daf8caa166d45f9022bbc19a896d2e1e56", [:mix], []},
- "earmark": {:hex, :earmark, "1.2.2", "f718159d6b65068e8daeef709ccddae5f7fdc770707d82e7d126f584cd925b74", [:mix], []},
- "esip": {:hex, :esip, "1.0.12", "e0505afe74bb362b0ea486e2a64b3c1934b1eb541a7b3e990b23045e4bdc07d4", [:rebar3], [{:fast_tls, "1.0.12", [hex: :fast_tls, optional: false]}, {:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}, {:stun, "1.0.11", [hex: :stun, optional: false]}]},
- "ex_doc": {:hex, :ex_doc, "0.16.1", "b4b8a23602b4ce0e9a5a960a81260d1f7b29635b9652c67e95b0c2f7ccee5e81", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, optional: false]}]},
+%{"cache_tab": {:hex, :cache_tab, "1.0.10", "dd6aba8951ba15cab4ad483d997f8eefdb0cb00225971d0629c730d107a2bed6", [:rebar3], [{:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}]},
+ "distillery": {:hex, :distillery, "1.4.1", "546d851bf27ae8fe0727e10e4fc4e146ad836eecee138263a60431e688044ed3", [:mix], []},
+ "earmark": {:hex, :earmark, "1.2.3", "206eb2e2ac1a794aa5256f3982de7a76bf4579ff91cb28d0e17ea2c9491e46a4", [:mix], []},
+ "eredis": {:hex, :eredis, "1.0.8", "ab4fda1c4ba7fbe6c19c26c249dc13da916d762502c4b4fa2df401a8d51c5364", [:rebar], []},
+ "esip": {:hex, :esip, "1.0.15", "82c8b0178618c10b1ac9690841d94025c982d63f8cd6c8f8bf920cf33e301658", [:rebar3], [{:fast_tls, "1.0.15", [hex: :fast_tls, optional: false]}, {:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}, {:stun, "1.0.14", [hex: :stun, optional: false]}]},
+ "ex_doc": {:hex, :ex_doc, "0.16.2", "3b3e210ebcd85a7c76b4e73f85c5640c011d2a0b2f06dcdf5acdb2ae904e5084", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, optional: false]}]},
"ezlib": {:hex, :ezlib, "1.0.2", "22004ecf553a7d831404394d5642712e2aede90522e22bd6ccc089ca410ee098", [:rebar3], []},
- "fast_tls": {:hex, :fast_tls, "1.0.12", "861b591f23103142782c5b72de8898673a37acd78646c50dbda978e1e1c5b463", [:rebar3], [{:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}]},
+ "fast_tls": {:hex, :fast_tls, "1.0.15", "96546e6a8b8384fbbcddf435c4c42cf2c0a3dc1858c3c9c2e62a74ae1ddd526a", [:rebar3], [{:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}]},
"fast_xml": {:hex, :fast_xml, "1.1.23", "1e7b311d3353806ee832d7630fef57713987cea40a7020669cf057d537de4721", [:rebar3], [{:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}]},
"fast_yaml": {:hex, :fast_yaml, "1.0.10", "ce5d52b77cb21968c8b73aa29b39f56a4ffd7e1e11f853d5597e7277858f155e", [:rebar3], [{:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}]},
"goldrush": {:hex, :goldrush, "0.1.9", "f06e5d5f1277da5c413e84d5a2924174182fb108dabb39d5ec548b27424cd106", [:rebar3], []},
"iconv": {:hex, :iconv, "1.0.5", "ae871aa11c854695db37e48fd5e5583b02e106126fbdf21bb53448f5a47c092b", [:rebar3], [{:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}]},
"jiffy": {:hex, :jiffy, "0.14.11", "919a87d491c5a6b5e3bbc27fafedc3a0761ca0b4c405394f121f582fd4e3f0e5", [:rebar3], []},
"lager": {:hex, :lager, "3.4.2", "150b9a17b23ae6d3265cc10dc360747621cf217b7a22b8cddf03b2909dbf7aa5", [:rebar3], [{:goldrush, "0.1.9", [hex: :goldrush, optional: false]}]},
+ "p1_mysql": {:hex, :p1_mysql, "1.0.3", "e2cc26f2e8d17c3885a9c2fee3ff64fcac5915896f50ab6f6aa9b0da1eed341c", [:rebar3], []},
"p1_oauth2": {:hex, :p1_oauth2, "0.6.1", "4e021250cc198c538b097393671a41e7cebf463c248980320e038fe0316eb56b", [:rebar3], []},
+ "p1_pgsql": {:hex, :p1_pgsql, "1.1.3", "ce94c83e9605c88d5f541b8f4b49edff3dc2bbacd1b6409c4cad0fbf7bef2ac4", [:rebar3], []},
"p1_utils": {:hex, :p1_utils, "1.0.9", "c33c230efbeb4dcc02911161e3cb1a93231a92df15e3fc97de655a9271a26d9f", [:rebar3], []},
+ "sqlite3": {:hex, :sqlite3, "1.1.5", "794738b6d07b6d36ec6d42492cb9d629bad9cf3761617b8b8d728e765db19840", [:rebar3], []},
"stringprep": {:hex, :stringprep, "1.0.9", "9182ba39931cd1db528b8883cad0d63530abe2bf21835d26cec2f9af8bc00be0", [:rebar3], [{:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}]},
- "stun": {:hex, :stun, "1.0.11", "386cb3e3543e17a6351028a43e047c2172225d035c826a72fcb67672da9874e5", [:rebar3], [{:fast_tls, "1.0.12", [hex: :fast_tls, optional: false]}, {:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}]},
- "xmpp": {:hex, :xmpp, "1.1.11", "8c49964d0d48b81080d2c5700fcf6cc19950ae9dc60a71bd3ff3d4620336d052", [:rebar3], [{:fast_xml, "1.1.23", [hex: :fast_xml, optional: false]}, {:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}, {:stringprep, "1.0.9", [hex: :stringprep, optional: false]}]}}
+ "stun": {:hex, :stun, "1.0.14", "6dc2080c25a72f7087301dc7333c1ea7d27ea4d88efaa379fc2b5924f3b17006", [:rebar3], [{:fast_tls, "1.0.15", [hex: :fast_tls, optional: false]}, {:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}]},
+ "xmpp": {:hex, :xmpp, "1.1.14", "e186f5208e7a448a4af784a8d2cb87cefe99dd49b24623e25d38115b23a50e12", [:rebar3], [{:fast_xml, "1.1.23", [hex: :fast_xml, optional: false]}, {:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}, {:stringprep, "1.0.9", [hex: :stringprep, optional: false]}]}}
diff --git a/rebar.config b/rebar.config
index d24236cee..0383fb084 100644
--- a/rebar.config
+++ b/rebar.config
@@ -77,7 +77,7 @@
ezlib,
iconv]}}.
-{erl_first_files, ["src/ejabberd_config.erl", "src/gen_mod.erl", "src/mod_muc_room.erl"]}.
+{erl_first_files, ["src/ejabberd_config.erl", "src/gen_mod.erl", "src/mod_muc_room.erl", "src/mod_push.erl"]}.
{erl_opts, [nowarn_deprecated_function,
{i, "include"},
@@ -93,6 +93,7 @@
{if_var_true, elixir, {d, 'ELIXIR_ENABLED'}},
{if_var_true, erlang_deprecated_types, {d, 'ERL_DEPRECATED_TYPES'}},
{if_have_fun, {crypto, strong_rand_bytes, 1}, {d, 'STRONG_RAND_BYTES'}},
+ {if_have_fun, {rand, uniform, 1}, {d, 'RAND_UNIFORM'}},
{if_have_fun, {gb_sets, iterator_from, 2}, {d, 'GB_SETS_ITERATOR_FROM'}},
{if_have_fun, {public_key, short_name_hash, 1}, {d, 'SHORT_NAME_HASH'}},
%% {if_have_fun, {public_key, generate_key, 1}, {d, 'GENERATE_RSA_KEY'}},
diff --git a/src/ejabberd_auth.erl b/src/ejabberd_auth.erl
index 251e36ff7..b34925ff0 100644
--- a/src/ejabberd_auth.erl
+++ b/src/ejabberd_auth.erl
@@ -261,12 +261,12 @@ try_register(User, Server, Password) ->
ok;
(Mod, _) ->
db_try_register(
- User, Server, Password, Mod)
+ LUser, LServer, Password, Mod)
end, {error, not_allowed},
auth_modules(LServer)) of
ok ->
ejabberd_hooks:run(
- register_user, Server, [User, Server]);
+ register_user, LServer, [LUser, LServer]);
{error, _} = Err ->
Err
end;
diff --git a/src/ejabberd_auth_mnesia.erl b/src/ejabberd_auth_mnesia.erl
index 9c2152578..690152674 100644
--- a/src/ejabberd_auth_mnesia.erl
+++ b/src/ejabberd_auth_mnesia.erl
@@ -208,6 +208,8 @@ remove_user(User, Server) ->
{error, db_failure}
end.
+need_transform(#reg_users_counter{}) ->
+ false;
need_transform(#passwd{us = {U, S}, password = Pass}) ->
if is_binary(Pass) ->
case store_type(S) of
diff --git a/src/ejabberd_bosh.erl b/src/ejabberd_bosh.erl
index 0755067e7..1df6681ff 100644
--- a/src/ejabberd_bosh.erl
+++ b/src/ejabberd_bosh.erl
@@ -27,9 +27,7 @@
-protocol({xep, 124, '1.11'}).
-protocol({xep, 206, '1.4'}).
--define(GEN_FSM, p1_fsm).
-
--behaviour(?GEN_FSM).
+-behaviour(p1_fsm).
%% API
-export([start/2, start/3, start_link/3]).
@@ -137,18 +135,18 @@ start(#body{attrs = Attrs} = Body, IP, SID) ->
end.
start(StateName, State) ->
- (?GEN_FSM):start_link(?MODULE, [StateName, State],
+ p1_fsm:start_link(?MODULE, [StateName, State],
?FSMOPTS).
start_link(Body, IP, SID) ->
- (?GEN_FSM):start_link(?MODULE, [Body, IP, SID],
+ p1_fsm:start_link(?MODULE, [Body, IP, SID],
?FSMOPTS).
send({http_bind, FsmRef, IP}, Packet) ->
send_xml({http_bind, FsmRef, IP}, Packet).
send_xml({http_bind, FsmRef, _IP}, Packet) ->
- case catch (?GEN_FSM):sync_send_all_state_event(FsmRef,
+ case catch p1_fsm:sync_send_all_state_event(FsmRef,
{send_xml, Packet},
?SEND_TIMEOUT)
of
@@ -160,12 +158,12 @@ send_xml({http_bind, FsmRef, _IP}, Packet) ->
setopts({http_bind, FsmRef, _IP}, Opts) ->
case lists:member({active, once}, Opts) of
true ->
- (?GEN_FSM):send_all_state_event(FsmRef,
+ p1_fsm:send_all_state_event(FsmRef,
{activate, self()});
_ ->
case lists:member({active, false}, Opts) of
true ->
- case catch (?GEN_FSM):sync_send_all_state_event(FsmRef,
+ case catch p1_fsm:sync_send_all_state_event(FsmRef,
deactivate_socket)
of
{'EXIT', _} -> {error, einval};
@@ -181,7 +179,7 @@ custom_receiver({http_bind, FsmRef, _IP}) ->
{receiver, ?MODULE, FsmRef}.
become_controller(FsmRef, C2SPid) ->
- (?GEN_FSM):send_all_state_event(FsmRef,
+ p1_fsm:send_all_state_event(FsmRef,
{become_controller, C2SPid}).
change_controller({http_bind, FsmRef, _IP}, C2SPid) ->
@@ -190,14 +188,14 @@ change_controller({http_bind, FsmRef, _IP}, C2SPid) ->
reset_stream({http_bind, _FsmRef, _IP}) -> ok.
change_shaper({http_bind, FsmRef, _IP}, Shaper) ->
- (?GEN_FSM):send_all_state_event(FsmRef,
+ p1_fsm:send_all_state_event(FsmRef,
{change_shaper, Shaper}).
monitor({http_bind, FsmRef, _IP}) ->
erlang:monitor(process, FsmRef).
close({http_bind, FsmRef, _IP}) ->
- catch (?GEN_FSM):sync_send_all_state_event(FsmRef,
+ catch p1_fsm:sync_send_all_state_event(FsmRef,
close).
sockname(_Socket) -> {ok, {{0, 0, 0, 0}, 0}}.
@@ -269,7 +267,7 @@ process_request(Data, IP, Type) ->
end.
process_request(Pid, Req, _IP, Type) ->
- case catch (?GEN_FSM):sync_send_event(Pid, Req,
+ case catch p1_fsm:sync_send_event(Pid, Req,
infinity)
of
#body{} = Resp -> bosh_response(Resp, Type);
@@ -571,7 +569,7 @@ handle_sync_event({send_xml, El}, _From, StateName,
of
{{value, {TRef, From, Body}}, Q} ->
cancel_timer(TRef),
- (?GEN_FSM):send_event(self(), {Body, From}),
+ p1_fsm:send_event(self(), {Body, From}),
State1#state{shaped_receivers = Q};
_ -> State1
end,
@@ -598,7 +596,7 @@ handle_info({timeout, TRef, shaper_timeout}, StateName,
State) ->
case p1_queue:out(State#state.shaped_receivers) of
{{value, {TRef, From, Req}}, Q} ->
- (?GEN_FSM):send_event(self(), {Req, From}),
+ p1_fsm:send_event(self(), {Req, From}),
{next_state, StateName,
State#state{shaped_receivers = Q}};
{{value, _}, _} ->
@@ -630,7 +628,7 @@ terminate(_Reason, _StateName, State) ->
mod_bosh:close_session(State#state.sid),
case State#state.c2s_pid of
C2SPid when is_pid(C2SPid) ->
- (?GEN_FSM):send_event(C2SPid, closed);
+ p1_fsm:send_event(C2SPid, closed);
_ -> ok
end,
bounce_receivers(State, closed),
@@ -644,7 +642,7 @@ print_state(State) -> State.
route_els(#state{el_ibuf = Buf, c2s_pid = C2SPid} = State) ->
NewBuf = p1_queue:dropwhile(
fun(El) ->
- ?GEN_FSM:send_event(C2SPid, El),
+ p1_fsm:send_event(C2SPid, El),
true
end, Buf),
State#state{el_ibuf = NewBuf}.
@@ -653,7 +651,7 @@ route_els(State, Els) ->
case State#state.c2s_pid of
C2SPid when is_pid(C2SPid) ->
lists:foreach(fun (El) ->
- (?GEN_FSM):send_event(C2SPid, El)
+ p1_fsm:send_event(C2SPid, El)
end,
Els),
State;
@@ -676,7 +674,7 @@ reply(State, Body, RID, From) ->
case catch gb_trees:take_smallest(Receivers) of
{NextRID, {From1, Req}, Receivers1}
when NextRID == RID + 1 ->
- (?GEN_FSM):send_event(self(), {Req, From1}),
+ p1_fsm:send_event(self(), {Req, From1}),
State2#state{receivers = Receivers1};
_ -> State2#state{receivers = Receivers}
end.
@@ -715,7 +713,7 @@ do_reply(State, From, Body, RID) ->
?DEBUG("send reply:~n** RequestID: ~p~n** Reply: "
"~p~n** To: ~p~n** State: ~p",
[RID, Body, From, State]),
- (?GEN_FSM):reply(From, Body),
+ p1_fsm:reply(From, Body),
Responses = gb_trees:delete_any(RID,
State#state.responses),
Responses1 = case gb_trees:size(Responses) of
@@ -1053,7 +1051,7 @@ buf_out(Buf, I, Els) ->
end.
cancel_timer(TRef) when is_reference(TRef) ->
- (?GEN_FSM):cancel_timer(TRef);
+ p1_fsm:cancel_timer(TRef);
cancel_timer(_) -> false.
restart_timer(TRef, Timeout, Msg) ->
diff --git a/src/ejabberd_c2s.erl b/src/ejabberd_c2s.erl
index 159cb4054..fe60f344e 100644
--- a/src/ejabberd_c2s.erl
+++ b/src/ejabberd_c2s.erl
@@ -47,7 +47,7 @@
process_terminated/2, process_info/2]).
%% API
-export([get_presence/1, set_presence/2, resend_presence/1, resend_presence/2,
- open_session/1, call/3, send/2, close/1, close/2, stop/1,
+ open_session/1, call/3, cast/2, send/2, close/1, close/2, stop/1,
reply/2, copy_state/2, set_timeout/2, route/2,
host_up/1, host_down/1]).
@@ -90,6 +90,10 @@ socket_type() ->
call(Ref, Msg, Timeout) ->
xmpp_stream_in:call(Ref, Msg, Timeout).
+-spec cast(pid(), term()) -> ok.
+cast(Ref, Msg) ->
+ xmpp_stream_in:cast(Ref, Msg).
+
reply(Ref, Reply) ->
xmpp_stream_in:reply(Ref, Reply).
@@ -293,14 +297,19 @@ process_terminated(State, _Reason) ->
%%%===================================================================
%%% xmpp_stream_in callbacks
%%%===================================================================
-tls_options(#{lserver := LServer, tls_options := DefaultOpts}) ->
- TLSOpts1 = case ejabberd_config:get_option(
- {c2s_certfile, LServer},
- ejabberd_config:get_option(
- {domain_certfile, LServer})) of
- undefined -> DefaultOpts;
- CertFile -> lists:keystore(certfile, 1, DefaultOpts,
- {certfile, CertFile})
+tls_options(#{lserver := LServer, tls_options := DefaultOpts,
+ stream_encrypted := Encrypted}) ->
+ TLSOpts1 = case {Encrypted, proplists:get_value(certfile, DefaultOpts)} of
+ {true, CertFile} when CertFile /= undefined -> DefaultOpts;
+ {_, _} ->
+ case ejabberd_config:get_option(
+ {domain_certfile, LServer},
+ ejabberd_config:get_option(
+ {c2s_certfile, LServer})) of
+ undefined -> DefaultOpts;
+ CertFile -> lists:keystore(certfile, 1, DefaultOpts,
+ {certfile, CertFile})
+ end
end,
TLSOpts2 = case ejabberd_config:get_option(
{c2s_ciphers, LServer}) of
diff --git a/src/ejabberd_captcha.erl b/src/ejabberd_captcha.erl
index 48e4ac1e6..76af5278f 100644
--- a/src/ejabberd_captcha.erl
+++ b/src/ejabberd_captcha.erl
@@ -388,29 +388,24 @@ get_transfer_protocol(PortString) ->
get_port_listeners(PortNumber) ->
AllListeners = ejabberd_config:get_option(listen, []),
- lists:filter(fun (Listener) when is_list(Listener) ->
- case proplists:get_value(port, Listener) of
- PortNumber -> true;
- _ -> false
- end;
- (_) -> false
- end,
- AllListeners).
+ lists:filter(
+ fun({{Port, _IP, _Transport}, _Module, _Opts}) ->
+ Port == PortNumber
+ end, AllListeners).
get_captcha_transfer_protocol([]) ->
throw(<<"The port number mentioned in captcha_host "
"is not a ejabberd_http listener with "
"'captcha' option. Change the port number "
"or specify http:// in that option.">>);
-get_captcha_transfer_protocol([Listener | Listeners]) when is_list(Listener) ->
- case proplists:get_value(module, Listener) == ejabberd_http andalso
- proplists:get_bool(captcha, Listener) of
+get_captcha_transfer_protocol([{_, ejabberd_http, Opts} | Listeners]) ->
+ case proplists:get_bool(captcha, Opts) of
true ->
- case proplists:get_bool(tls, Listener) of
- true -> https;
- false -> http
- end;
- false -> get_captcha_transfer_protocol(Listeners)
+ case proplists:get_bool(tls, Opts) of
+ true -> https;
+ false -> http
+ end;
+ false -> get_captcha_transfer_protocol(Listeners)
end;
get_captcha_transfer_protocol([_ | Listeners]) ->
get_captcha_transfer_protocol(Listeners).
diff --git a/src/ejabberd_config.erl b/src/ejabberd_config.erl
index 1e5d495ce..5d3bc8680 100644
--- a/src/ejabberd_config.erl
+++ b/src/ejabberd_config.erl
@@ -28,7 +28,7 @@
-export([start/0, load_file/1, reload_file/0, read_file/1,
get_option/1, get_option/2, add_option/2, has_option/1,
- get_vh_by_auth_method/1, is_file_readable/1,
+ get_vh_by_auth_method/1,
get_version/0, get_myhosts/0, get_mylang/0, get_lang/1,
get_ejabberd_config_path/0, is_using_elixir_config/0,
prepare_opt_val/4, transform_options/1, collect_options/1,
@@ -46,11 +46,12 @@
get_global_option/2, get_local_option/2,
get_global_option/3, get_local_option/3,
get_option/3]).
+-export([is_file_readable/1]).
-deprecated([{add_global_option, 2}, {add_local_option, 2},
{get_global_option, 2}, {get_local_option, 2},
{get_global_option, 3}, {get_local_option, 3},
- {get_option, 3}]).
+ {get_option, 3}, {is_file_readable, 1}]).
-include("ejabberd.hrl").
-include("logger.hrl").
diff --git a/src/ejabberd_http_ws.erl b/src/ejabberd_http_ws.erl
index f4a73cc39..f9f7b07e9 100644
--- a/src/ejabberd_http_ws.erl
+++ b/src/ejabberd_http_ws.erl
@@ -28,7 +28,7 @@
-author('ecestari@process-one.net').
--behaviour(gen_fsm).
+-behaviour(p1_fsm).
-export([start/1, start_link/1, init/1, handle_event/3,
handle_sync_event/4, code_change/4, handle_info/3,
@@ -75,13 +75,13 @@
-export_type([ws_socket/0]).
start(WS) ->
- gen_fsm:start(?MODULE, [WS], ?FSMOPTS).
+ p1_fsm:start(?MODULE, [WS], ?FSMOPTS).
start_link(WS) ->
- gen_fsm:start_link(?MODULE, [WS], ?FSMOPTS).
+ p1_fsm:start_link(?MODULE, [WS], ?FSMOPTS).
send_xml({http_ws, FsmRef, _IP}, Packet) ->
- case catch gen_fsm:sync_send_all_state_event(FsmRef,
+ case catch p1_fsm:sync_send_all_state_event(FsmRef,
{send_xml, Packet},
15000)
of
@@ -93,7 +93,7 @@ send_xml({http_ws, FsmRef, _IP}, Packet) ->
setopts({http_ws, FsmRef, _IP}, Opts) ->
case lists:member({active, once}, Opts) of
true ->
- gen_fsm:send_all_state_event(FsmRef,
+ p1_fsm:send_all_state_event(FsmRef,
{activate, self()});
_ -> ok
end.
@@ -105,11 +105,11 @@ peername({http_ws, _FsmRef, IP}) -> {ok, IP}.
controlling_process(_Socket, _Pid) -> ok.
become_controller(FsmRef, C2SPid) ->
- gen_fsm:send_all_state_event(FsmRef,
+ p1_fsm:send_all_state_event(FsmRef,
{become_controller, C2SPid}).
close({http_ws, FsmRef, _IP}) ->
- catch gen_fsm:sync_send_all_state_event(FsmRef, close).
+ catch p1_fsm:sync_send_all_state_event(FsmRef, close).
socket_handoff(LocalPath, Request, Socket, SockMod, Buf, Opts) ->
ejabberd_websocket:socket_handoff(LocalPath, Request, Socket, SockMod,
@@ -241,6 +241,7 @@ handle_info(PingPong, StateName, StateData) when PingPong == ping orelse
StateData2#state{pong_expected = false}};
handle_info({timeout, Timer, _}, _StateName,
#state{timer = Timer} = StateData) ->
+ ?DEBUG("Closing websocket connection from hitting inactivity timeout", []),
{stop, normal, StateData};
handle_info({timeout, Timer, _}, StateName,
#state{ping_timer = Timer, ws = {_, WsPid}} = StateData) ->
@@ -253,6 +254,7 @@ handle_info({timeout, Timer, _}, StateName,
{next_state, StateName,
StateData#state{ping_timer = PingTimer, pong_expected = true}};
true ->
+ ?DEBUG("Closing websocket connection from missing pongs", []),
{stop, normal, StateData}
end;
handle_info(_, StateName, StateData) ->
diff --git a/src/ejabberd_logger.erl b/src/ejabberd_logger.erl
index 1e48732c8..eee9d3b83 100644
--- a/src/ejabberd_logger.erl
+++ b/src/ejabberd_logger.erl
@@ -151,6 +151,9 @@ do_start() ->
application:set_env(lager, crash_log_size, LogRotateSize),
application:set_env(lager, crash_log_count, LogRotateCount),
ejabberd:start_app(lager),
+ lists:foreach(fun(Handler) ->
+ lager:set_loghwm(Handler, LogRateLimit)
+ end, gen_event:which_handlers(lager_event)),
ok.
%% @spec () -> ok
diff --git a/src/ejabberd_receiver.erl b/src/ejabberd_receiver.erl
index 44c29680c..52077ac3c 100644
--- a/src/ejabberd_receiver.erl
+++ b/src/ejabberd_receiver.erl
@@ -234,7 +234,7 @@ terminate(_Reason,
State) ->
close_stream(XMLStreamState),
if C2SPid /= undefined ->
- gen_fsm:send_event(C2SPid, closed);
+ p1_fsm:send_event(C2SPid, closed);
true -> ok
end,
catch (State#state.sock_mod):close(State#state.socket),
@@ -272,7 +272,7 @@ process_data([Element | Els],
element(1, Element) == xmlstreamend ->
if C2SPid == undefined -> State;
true ->
- catch gen_fsm:send_event(C2SPid,
+ catch p1_fsm:send_event(C2SPid,
element_wrapper(Element)),
process_data(Els, State)
end;
diff --git a/src/ejabberd_regexp.erl b/src/ejabberd_regexp.erl
index b4ef7ac16..10f51aa47 100644
--- a/src/ejabberd_regexp.erl
+++ b/src/ejabberd_regexp.erl
@@ -25,7 +25,7 @@
-module(ejabberd_regexp).
--compile([export_all]).
+-export([exec/2, run/2, split/2, replace/3, greplace/3, sh_to_awk/1]).
exec({ReM, ReF, ReA}, {RgM, RgF, RgA}) ->
try apply(ReM, ReF, ReA) catch
diff --git a/src/ejabberd_s2s.erl b/src/ejabberd_s2s.erl
index a0e9411cf..cb4e5e5ec 100644
--- a/src/ejabberd_s2s.erl
+++ b/src/ejabberd_s2s.erl
@@ -39,7 +39,7 @@
remove_connection/2, start_connection/2, start_connection/3,
dirty_get_connections/0, allow_host/2,
incoming_s2s_number/0, outgoing_s2s_number/0,
- stop_all_connections/0,
+ stop_s2s_connections/0,
clean_temporarily_blocked_table/0,
list_temporarily_blocked_hosts/0,
external_host_overloaded/1, is_temporarly_blocked/1,
@@ -199,9 +199,9 @@ dirty_get_connections() ->
-spec tls_options(binary(), [proplists:property()]) -> [proplists:property()].
tls_options(LServer, DefaultOpts) ->
TLSOpts1 = case ejabberd_config:get_option(
- {s2s_certfile, LServer},
+ {domain_certfile, LServer},
ejabberd_config:get_option(
- {domain_certfile, LServer})) of
+ {s2s_certfile, LServer})) of
undefined -> DefaultOpts;
CertFile -> lists:keystore(certfile, 1, DefaultOpts,
{certfile, CertFile})
@@ -558,10 +558,10 @@ get_commands_spec() ->
module = ?MODULE, function = outgoing_s2s_number,
args = [], result = {s2s_outgoing, integer}},
#ejabberd_commands{
- name = stop_all_connections, tags = [s2s],
- desc = "Stop all outgoing and incoming connections",
+ name = stop_s2s_connections, tags = [s2s],
+ desc = "Stop all s2s outgoing and incoming connections",
policy = admin,
- module = ?MODULE, function = stop_all_connections,
+ module = ?MODULE, function = stop_s2s_connections,
args = [], result = {res, rescode}}].
%% TODO Move those stats commands to ejabberd stats command ?
@@ -578,8 +578,8 @@ supervisor_count(Supervisor) ->
length(Result)
end.
--spec stop_all_connections() -> ok.
-stop_all_connections() ->
+-spec stop_s2s_connections() -> ok.
+stop_s2s_connections() ->
lists:foreach(
fun({_Id, Pid, _Type, _Module}) ->
supervisor:terminate_child(ejabberd_s2s_in_sup, Pid)
@@ -682,7 +682,7 @@ complete_s2s_info([Connection | T], Type, Result) ->
-spec get_s2s_state(pid()) -> [{status, open | closed | error} | {s2s_pid, pid()}].
get_s2s_state(S2sPid) ->
- Infos = case gen_fsm:sync_send_all_state_event(S2sPid,
+ Infos = case p1_fsm:sync_send_all_state_event(S2sPid,
get_state_infos)
of
{state_infos, Is} -> [{status, open} | Is];
diff --git a/src/ejabberd_s2s_out.erl b/src/ejabberd_s2s_out.erl
index e8cad9792..fea5d8162 100644
--- a/src/ejabberd_s2s_out.erl
+++ b/src/ejabberd_s2s_out.erl
@@ -374,7 +374,7 @@ mk_bounce_error(_Lang, _State) ->
-spec get_delay() -> non_neg_integer().
get_delay() ->
MaxDelay = ejabberd_config:get_option(s2s_max_retry_delay, 300),
- crypto:rand_uniform(1, MaxDelay).
+ randoms:uniform(MaxDelay).
-spec set_idle_timeout(state()) -> state().
set_idle_timeout(#{on_route := send, server := LServer} = State) ->
diff --git a/src/ejabberd_sm.erl b/src/ejabberd_sm.erl
index 333edffa6..96dbb4e83 100644
--- a/src/ejabberd_sm.erl
+++ b/src/ejabberd_sm.erl
@@ -66,6 +66,8 @@
user_resources/2,
kick_user/2,
get_session_pid/3,
+ get_session_sid/3,
+ get_session_sids/2,
get_user_info/2,
get_user_info/3,
get_user_ip/3,
@@ -292,15 +294,32 @@ close_session_unset_presence(SID, User, Server,
-spec get_session_pid(binary(), binary(), binary()) -> none | pid().
get_session_pid(User, Server, Resource) ->
+ case get_session_sid(User, Server, Resource) of
+ {_, PID} -> PID;
+ none -> none
+ end.
+
+-spec get_session_sid(binary(), binary(), binary()) -> none | sid().
+
+get_session_sid(User, Server, Resource) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
LResource = jid:resourceprep(Resource),
Mod = get_sm_backend(LServer),
case online(get_sessions(Mod, LUser, LServer, LResource)) of
- [#session{sid = {_, Pid}}] -> Pid;
+ [#session{sid = SID}] -> SID;
_ -> none
end.
+-spec get_session_sids(binary(), binary()) -> [sid()].
+
+get_session_sids(User, Server) ->
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Server),
+ Mod = get_sm_backend(LServer),
+ Sessions = online(get_sessions(Mod, LUser, LServer)),
+ [SID || #session{sid = SID} <- Sessions].
+
-spec set_offline_info(sid(), binary(), binary(), binary(), info()) -> ok.
set_offline_info(SID, User, Server, Resource, Info) ->
@@ -471,9 +490,13 @@ host_up(Host) ->
-spec host_down(binary()) -> ok.
host_down(Host) ->
Mod = get_sm_backend(Host),
+ Err = case ejabberd_cluster:get_nodes() of
+ [Node] when Node == node() -> xmpp:serr_system_shutdown();
+ _ -> xmpp:serr_reset()
+ end,
lists:foreach(
fun(#session{sid = {_, Pid}}) when node(Pid) == node() ->
- ejabberd_c2s:send(Pid, xmpp:serr_system_shutdown()),
+ ejabberd_c2s:send(Pid, Err),
ejabberd_c2s:stop(Pid);
(_) ->
ok
@@ -972,7 +995,7 @@ get_commands_spec() ->
args = [], result = {num_sessions, integer}},
#ejabberd_commands{name = user_resources, tags = [session],
desc = "List user's connected resources",
- policy = user,
+ policy = admin,
module = ?MODULE, function = user_resources,
args = [{user, binary}, {host, binary}],
args_desc = ["User name", "Server name"],
diff --git a/src/ejabberd_sql.erl b/src/ejabberd_sql.erl
index 35d970291..cae41da6c 100644
--- a/src/ejabberd_sql.erl
+++ b/src/ejabberd_sql.erl
@@ -29,9 +29,7 @@
-author('alexey@process-one.net').
--define(GEN_FSM, p1_fsm).
-
--behaviour(?GEN_FSM).
+-behaviour(p1_fsm).
%% External exports
-export([start/1, start_link/2,
@@ -113,11 +111,11 @@
%%% API
%%%----------------------------------------------------------------------
start(Host) ->
- (?GEN_FSM):start(ejabberd_sql, [Host],
+ p1_fsm:start(ejabberd_sql, [Host],
fsm_limit_opts() ++ (?FSMOPTS)).
start_link(Host, StartInterval) ->
- (?GEN_FSM):start_link(ejabberd_sql,
+ p1_fsm:start_link(ejabberd_sql,
[Host, StartInterval],
fsm_limit_opts() ++ (?FSMOPTS)).
@@ -160,7 +158,7 @@ sql_call(Host, Msg) ->
case ejabberd_sql_sup:get_random_pid(Host) of
none -> {error, <<"Unknown Host">>};
Pid ->
- (?GEN_FSM):sync_send_event(Pid,{sql_cmd, Msg,
+ p1_fsm:sync_send_event(Pid,{sql_cmd, Msg,
p1_time_compat:monotonic_time(milli_seconds)},
query_timeout(Host))
end;
@@ -168,7 +166,7 @@ sql_call(Host, Msg) ->
end.
keep_alive(Host, PID) ->
- (?GEN_FSM):sync_send_event(PID,
+ p1_fsm:sync_send_event(PID,
{sql_cmd, {sql_query, ?KEEPALIVE_QUERY},
p1_time_compat:monotonic_time(milli_seconds)},
query_timeout(Host)).
@@ -280,7 +278,7 @@ init([Host, StartInterval]) ->
keep_alive, [Host, self()])
end,
[DBType | _] = db_opts(Host),
- (?GEN_FSM):send_event(self(), connect),
+ p1_fsm:send_event(self(), connect),
ejabberd_sql_sup:add_pid(Host, self()),
QueueType = case ejabberd_config:get_option({sql_queue_type, Host}) of
undefined ->
@@ -313,7 +311,7 @@ connecting(connect, #state{host = Host} = State) ->
PendingRequests =
p1_queue:dropwhile(
fun(Req) ->
- ?GEN_FSM:send_event(self(), Req),
+ p1_fsm:send_event(self(), Req),
true
end, State#state.pending_requests),
State1 = State#state{db_ref = Ref,
@@ -325,7 +323,7 @@ connecting(connect, #state{host = Host} = State) ->
"Retry after: ~p seconds",
[State#state.db_type, Reason,
State#state.start_interval div 1000]),
- (?GEN_FSM):send_event_after(State#state.start_interval,
+ p1_fsm:send_event_after(State#state.start_interval,
connect),
{next_state, connecting, State}
end;
@@ -337,7 +335,7 @@ connecting(Event, State) ->
connecting({sql_cmd, {sql_query, ?KEEPALIVE_QUERY},
_Timestamp},
From, State) ->
- (?GEN_FSM):reply(From,
+ p1_fsm:reply(From,
{error, <<"SQL connection failed">>}),
{next_state, connecting, State};
connecting({sql_cmd, Command, Timestamp} = Req, From,
@@ -350,7 +348,7 @@ connecting({sql_cmd, Command, Timestamp} = Req, From,
catch error:full ->
Q = p1_queue:dropwhile(
fun({sql_cmd, _, To, _Timestamp}) ->
- (?GEN_FSM):reply(
+ p1_fsm:reply(
To, {error, <<"SQL connection failed">>}),
true
end, State#state.pending_requests),
@@ -393,7 +391,7 @@ code_change(_OldVsn, StateName, State, _Extra) ->
%% monitoring the connection)
handle_info({'DOWN', _MonitorRef, process, _Pid, _Info},
_StateName, State) ->
- (?GEN_FSM):send_event(self(), connect),
+ p1_fsm:send_event(self(), connect),
{next_state, connecting, State};
handle_info(Info, StateName, State) ->
?WARNING_MSG("unexpected info in ~p: ~p",
@@ -734,16 +732,16 @@ sql_query_to_iolist(SQLQuery) ->
abort_on_driver_error({error, <<"query timed out">>} =
Reply,
From) ->
- (?GEN_FSM):reply(From, Reply),
+ p1_fsm:reply(From, Reply),
{stop, timeout, get(?STATE_KEY)};
abort_on_driver_error({error,
<<"Failed sending data on socket", _/binary>>} =
Reply,
From) ->
- (?GEN_FSM):reply(From, Reply),
+ p1_fsm:reply(From, Reply),
{stop, closed, get(?STATE_KEY)};
abort_on_driver_error(Reply, From) ->
- (?GEN_FSM):reply(From, Reply),
+ p1_fsm:reply(From, Reply),
{next_state, session_established, get(?STATE_KEY)}.
%% == pure ODBC code
diff --git a/src/ejd2sql.erl b/src/ejd2sql.erl
index c4f00a551..c801eb973 100644
--- a/src/ejd2sql.erl
+++ b/src/ejd2sql.erl
@@ -58,6 +58,7 @@ modules() ->
mod_offline,
mod_privacy,
mod_private,
+ mod_pubsub,
mod_roster,
mod_shared_roster,
mod_vcard].
@@ -80,8 +81,8 @@ export(Server, Output, Module) ->
case export(LServer, Table, IO, ConvertFun) of
{atomic, ok} -> ok;
{aborted, Reason} ->
- ?ERROR_MSG("Failed export for module ~p: ~p",
- [Module, Reason])
+ ?ERROR_MSG("Failed export for module ~p and table ~p: ~p",
+ [Module, Table, Reason])
end
end, Module:export(Server)),
close_output(Output, IO).
diff --git a/src/eldap.erl b/src/eldap.erl
index f47550353..8e6b710b1 100644
--- a/src/eldap.erl
+++ b/src/eldap.erl
@@ -63,7 +63,7 @@
%%% active_bind - sent bind() request and waiting for response
%%%----------------------------------------------------------------------
--behaviour(gen_fsm).
+-behaviour(p1_fsm).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -148,7 +148,7 @@
start_link(Name) ->
Reg_name = misc:binary_to_atom(<<"eldap_",
Name/binary>>),
- gen_fsm:start_link({local, Reg_name}, ?MODULE, [], []).
+ p1_fsm:start_link({local, Reg_name}, ?MODULE, [], []).
-spec start_link(binary(), [binary()], inet:port_number(), binary(),
binary(), tlsopts()) -> any().
@@ -156,7 +156,7 @@ start_link(Name) ->
start_link(Name, Hosts, Port, Rootdn, Passwd, Opts) ->
Reg_name = misc:binary_to_atom(<<"eldap_",
Name/binary>>),
- gen_fsm:start_link({local, Reg_name}, ?MODULE,
+ p1_fsm:start_link({local, Reg_name}, ?MODULE,
[Hosts, Port, Rootdn, Passwd, Opts], []).
-spec get_status(handle()) -> any().
@@ -166,7 +166,7 @@ start_link(Name, Hosts, Port, Rootdn, Passwd, Opts) ->
%%% --------------------------------------------------------------------
get_status(Handle) ->
Handle1 = get_handle(Handle),
- gen_fsm:sync_send_all_state_event(Handle1, get_status).
+ p1_fsm:sync_send_all_state_event(Handle1, get_status).
%%% --------------------------------------------------------------------
%%% Shutdown connection (and process) asynchronous.
@@ -175,7 +175,7 @@ get_status(Handle) ->
close(Handle) ->
Handle1 = get_handle(Handle),
- gen_fsm:send_all_state_event(Handle1, close).
+ p1_fsm:send_all_state_event(Handle1, close).
%%% --------------------------------------------------------------------
%%% Add an entry. The entry field MUST NOT exist for the AddRequest
@@ -192,7 +192,7 @@ close(Handle) ->
%%% --------------------------------------------------------------------
add(Handle, Entry, Attributes) ->
Handle1 = get_handle(Handle),
- gen_fsm:sync_send_event(Handle1,
+ p1_fsm:sync_send_event(Handle1,
{add, Entry, add_attrs(Attributes)}, ?CALL_TIMEOUT).
%%% Do sanity check !
@@ -216,7 +216,7 @@ add_attrs(Attrs) ->
%%% --------------------------------------------------------------------
delete(Handle, Entry) ->
Handle1 = get_handle(Handle),
- gen_fsm:sync_send_event(Handle1, {delete, Entry},
+ p1_fsm:sync_send_event(Handle1, {delete, Entry},
?CALL_TIMEOUT).
%%% --------------------------------------------------------------------
@@ -234,7 +234,7 @@ delete(Handle, Entry) ->
modify(Handle, Object, Mods) ->
Handle1 = get_handle(Handle),
- gen_fsm:sync_send_event(Handle1, {modify, Object, Mods},
+ p1_fsm:sync_send_event(Handle1, {modify, Object, Mods},
?CALL_TIMEOUT).
%%%
@@ -274,7 +274,7 @@ m(Operation, Type, Values) ->
modify_dn(Handle, Entry, NewRDN, DelOldRDN, NewSup) ->
Handle1 = get_handle(Handle),
- gen_fsm:sync_send_event(Handle1,
+ p1_fsm:sync_send_event(Handle1,
{modify_dn, Entry, NewRDN, bool_p(DelOldRDN),
optional(NewSup)},
?CALL_TIMEOUT).
@@ -283,7 +283,7 @@ modify_dn(Handle, Entry, NewRDN, DelOldRDN, NewSup) ->
modify_passwd(Handle, DN, Passwd) ->
Handle1 = get_handle(Handle),
- gen_fsm:sync_send_event(Handle1,
+ p1_fsm:sync_send_event(Handle1,
{modify_passwd, DN, Passwd}, ?CALL_TIMEOUT).
%%% --------------------------------------------------------------------
@@ -298,7 +298,7 @@ modify_passwd(Handle, DN, Passwd) ->
bind(Handle, RootDN, Passwd) ->
Handle1 = get_handle(Handle),
- gen_fsm:sync_send_event(Handle1, {bind, RootDN, Passwd},
+ p1_fsm:sync_send_event(Handle1, {bind, RootDN, Passwd},
?CALL_TIMEOUT).
%%% Sanity checks !
@@ -356,7 +356,7 @@ search(Handle, L) when is_list(L) ->
call_search(Handle, A) ->
Handle1 = get_handle(Handle),
- gen_fsm:sync_send_event(Handle1, {search, A},
+ p1_fsm:sync_send_event(Handle1, {search, A},
?CALL_TIMEOUT).
-spec parse_search_args(search_args()) -> eldap_search().
@@ -637,7 +637,7 @@ active(Event, From, S) ->
%%----------------------------------------------------------------------
%% Func: handle_event/3
-%% Called when gen_fsm:send_all_state_event/2 is invoked.
+%% Called when p1_fsm:send_all_state_event/2 is invoked.
%% Returns: {next_state, NextStateName, NextStateData} |
%% {next_state, NextStateName, NextStateData, Timeout} |
%% {stop, Reason, NewStateData}
@@ -680,7 +680,7 @@ handle_info({Tag, _Socket, Data}, StateName, S)
case catch recvd_packet(Data, S) of
{response, Response, RequestType} ->
NewS = case Response of
- {reply, Reply, To, S1} -> gen_fsm:reply(To, Reply), S1;
+ {reply, Reply, To, S1} -> p1_fsm:reply(To, Reply), S1;
{ok, S1} -> S1
end,
if StateName == active_bind andalso
@@ -709,7 +709,7 @@ handle_info({timeout, Timer, {cmd_timeout, Id}},
StateName, S) ->
case cmd_timeout(Timer, Id, S) of
{reply, To, Reason, NewS} ->
- gen_fsm:reply(To, Reason),
+ p1_fsm:reply(To, Reason),
{next_state, StateName, NewS};
{error, _Reason} -> {next_state, StateName, S}
end;
diff --git a/src/ext_mod.erl b/src/ext_mod.erl
index 80296f7b7..ecdfa04c3 100644
--- a/src/ext_mod.erl
+++ b/src/ext_mod.erl
@@ -146,16 +146,29 @@ get_commands_spec() ->
%% -- public modules functions
update() ->
- add_sources(?REPOS),
+ Contrib = maps:put(?REPOS, [], maps:new()),
+ Jungles = lists:foldl(fun({Package, Spec}, Acc) ->
+ Repo = proplists:get_value(url, Spec, ""),
+ Mods = maps:get(Repo, Acc, []),
+ maps:put(Repo, [Package|Mods], Acc)
+ end, Contrib, modules_spec(sources_dir(), "*/*")),
+ Repos = maps:fold(fun(Repo, _Mods, Acc) ->
+ Update = add_sources(Repo),
+ ?INFO_MSG("Update packages from repo ~s: ~p", [Repo, Update]),
+ case Update of
+ ok -> Acc;
+ Error -> [{repository, Repo, Error}|Acc]
+ end
+ end, [], Jungles),
Res = lists:foldl(fun({Package, Spec}, Acc) ->
- Path = proplists:get_value(url, Spec, ""),
- Update = add_sources(Package, Path),
+ Repo = proplists:get_value(url, Spec, ""),
+ Update = add_sources(Package, Repo),
?INFO_MSG("Update package ~s: ~p", [Package, Update]),
case Update of
ok -> Acc;
- Error -> [Error|Acc]
+ Error -> [{Package, Repo, Error}|Acc]
end
- end, [], modules_spec(sources_dir(), "*")),
+ end, Repos, modules_spec(sources_dir(), "*")),
case Res of
[] -> ok;
[Error|_] -> Error
@@ -547,7 +560,9 @@ compile_result(Results) ->
compile_options() ->
[verbose, report_errors, report_warnings]
++ [{i, filename:join(app_dir(App), "include")}
- || App <- [fast_xml, xmpp, p1_utils, ejabberd]].
+ || App <- [fast_xml, xmpp, p1_utils, ejabberd]]
+ ++ [{i, filename:join(mod_dir(Mod), "include")}
+ || Mod <- installed()].
app_dir(App) ->
case code:lib_dir(App) of
@@ -562,6 +577,10 @@ app_dir(App) ->
Dir
end.
+mod_dir({Package, Spec}) ->
+ Default = filename:join(modules_dir(), Package),
+ proplists:get_value(path, Spec, Default).
+
compile_erlang_file(Dest, File) ->
compile_erlang_file(Dest, File, compile_options()).
diff --git a/src/gen_mod.erl b/src/gen_mod.erl
index 5bfa3b4d4..e17197dfb 100644
--- a/src/gen_mod.erl
+++ b/src/gen_mod.erl
@@ -34,7 +34,8 @@
stop_child/1, stop_child/2, config_reloaded/0]).
-export([start_module/2, start_module/3,
stop_module/2, stop_module_keep_config/2,
- get_opt/2, get_opt/3, get_opt_host/3, opt_type/1, is_equal_opt/4,
+ get_opt/2, get_opt/3, get_opt_host/3,
+ get_opt_hosts/3, opt_type/1, is_equal_opt/4,
get_module_opt/3, get_module_opt/4, get_module_opt_host/3,
loaded_modules/1, loaded_modules_with_opts/1,
get_hosts/2, get_module_proc/2, is_loaded/2, is_loaded_elsewhere/2,
@@ -441,6 +442,20 @@ get_opt_host(Host, Opts, Default) ->
Val = get_opt(host, Opts, Default),
ejabberd_regexp:greplace(Val, <<"@HOST@">>, Host).
+-spec get_opt_hosts(binary(), opts(), binary()) -> [binary()].
+
+get_opt_hosts(Host, Opts, Default) ->
+ Vals = case get_opt(host, Opts, undefined) of
+ undefined ->
+ case get_opt(hosts, Opts, []) of
+ [] -> [Default];
+ L -> L
+ end;
+ Val ->
+ [Val]
+ end,
+ [ejabberd_regexp:greplace(V, <<"@HOST@">>, Host) || V <- Vals].
+
-spec get_validators(binary(), module(), opts()) -> dict:dict() | undef.
get_validators(Host, Module, Opts) ->
try Module:mod_opt_type('') of
diff --git a/src/misc.erl b/src/misc.erl
index 604a458af..32699e76b 100644
--- a/src/misc.erl
+++ b/src/misc.erl
@@ -32,8 +32,8 @@
hex_to_bin/1, hex_to_base64/1, expand_keyword/3,
atom_to_binary/1, binary_to_atom/1, tuple_to_binary/1,
l2i/1, i2l/1, i2l/2, expr_to_term/1, term_to_expr/1,
- encode_pid/1, decode_pid/2, compile_exprs/2, join_atoms/2,
- try_read_file/1]).
+ now_to_usec/1, usec_to_now/1, encode_pid/1, decode_pid/2,
+ compile_exprs/2, join_atoms/2, try_read_file/1]).
%% Deprecated functions
-export([decode_base64/1, encode_base64/1]).
@@ -127,6 +127,18 @@ expr_to_term(Expr) ->
term_to_expr(Term) ->
list_to_binary(io_lib:print(Term)).
+-spec now_to_usec(erlang:timestamp()) -> non_neg_integer().
+now_to_usec({MSec, Sec, USec}) ->
+ (MSec*1000000 + Sec)*1000000 + USec.
+
+-spec usec_to_now(non_neg_integer()) -> erlang:timestamp().
+usec_to_now(Int) ->
+ Secs = Int div 1000000,
+ USec = Int rem 1000000,
+ MSec = Secs div 1000000,
+ Sec = Secs rem 1000000,
+ {MSec, Sec, USec}.
+
l2i(I) when is_integer(I) -> I;
l2i(L) when is_binary(L) -> binary_to_integer(L).
@@ -192,22 +204,12 @@ join_atoms(Atoms, Sep) ->
%% in configuration validators only.
-spec try_read_file(file:filename_all()) -> binary().
try_read_file(Path) ->
- Res = case file:read_file_info(Path) of
- {ok, #file_info{type = Type, access = Access}} ->
- case {Type, Access} of
- {regular, read} -> ok;
- {regular, read_write} -> ok;
- {regular, _} -> {error, file:format_error(eaccess)};
- _ -> {error, "not a regular file"}
- end;
- {error, Why} ->
- {error, file:format_error(Why)}
- end,
- case Res of
- ok ->
+ case file:open(Path, [read]) of
+ {ok, Fd} ->
+ file:close(Fd),
iolist_to_binary(Path);
- {error, Reason} ->
- ?ERROR_MSG("Failed to read ~s: ~s", [Path, Reason]),
+ {error, Why} ->
+ ?ERROR_MSG("Failed to read ~s: ~s", [Path, file:format_error(Why)]),
erlang:error(badarg)
end.
diff --git a/src/mod_echo.erl b/src/mod_echo.erl
index 861b1a0ef..79dd59962 100644
--- a/src/mod_echo.erl
+++ b/src/mod_echo.erl
@@ -43,7 +43,7 @@
-include("xmpp.hrl").
--record(state, {host = <<"">> :: binary()}).
+-record(state, {hosts = [] :: [binary()]}).
%%====================================================================
%% gen_mod API
@@ -62,7 +62,9 @@ depends(_Host, _Opts) ->
[].
mod_opt_type(host) -> fun iolist_to_binary/1;
-mod_opt_type(_) -> [host].
+mod_opt_type(hosts) ->
+ fun(L) -> lists:map(fun iolist_to_binary/1, L) end;
+mod_opt_type(_) -> [host, hosts].
%%====================================================================
%% gen_server callbacks
@@ -77,10 +79,13 @@ mod_opt_type(_) -> [host].
%%--------------------------------------------------------------------
init([Host, Opts]) ->
process_flag(trap_exit, true),
- MyHost = gen_mod:get_opt_host(Host, Opts,
+ Hosts = gen_mod:get_opt_hosts(Host, Opts,
<<"echo.@HOST@">>),
- ejabberd_router:register_route(MyHost, Host),
- {ok, #state{host = MyHost}}.
+ lists:foreach(
+ fun(H) ->
+ ejabberd_router:register_route(H, Host)
+ end, Hosts),
+ {ok, #state{hosts = Hosts}}.
%%--------------------------------------------------------------------
%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
@@ -101,17 +106,19 @@ handle_call(stop, _From, State) ->
%% Description: Handling cast messages
%%--------------------------------------------------------------------
handle_cast({reload, Host, NewOpts, OldOpts}, State) ->
- NewMyHost = gen_mod:get_opt_host(Host, NewOpts,
- <<"echo.@HOST@">>),
- OldMyHost = gen_mod:get_opt_host(Host, OldOpts,
- <<"echo.@HOST@">>),
- if NewMyHost /= OldMyHost ->
- ejabberd_router:register_route(NewMyHost, Host),
- ejabberd_router:unregister_route(OldMyHost);
- true ->
- ok
- end,
- {noreply, State#state{host = NewMyHost}};
+ NewMyHosts = gen_mod:get_opt_hosts(Host, NewOpts,
+ <<"echo.@HOST@">>),
+ OldMyHosts = gen_mod:get_opt_hosts(Host, OldOpts,
+ <<"echo.@HOST@">>),
+ lists:foreach(
+ fun(H) ->
+ ejabberd_router:unregister_route(H)
+ end, OldMyHosts -- NewMyHosts),
+ lists:foreach(
+ fun(H) ->
+ ejabberd_router:register_route(H, Host)
+ end, NewMyHosts -- OldMyHosts),
+ {noreply, State#state{hosts = NewMyHosts}};
handle_cast(Msg, State) ->
?WARNING_MSG("unexpected cast: ~p", [Msg]),
{noreply, State}.
@@ -147,7 +154,7 @@ handle_info(_Info, State) -> {noreply, State}.
%% The return value is ignored.
%%--------------------------------------------------------------------
terminate(_Reason, State) ->
- ejabberd_router:unregister_route(State#state.host), ok.
+ lists:foreach(fun ejabberd_router:unregister_route/1, State#state.hosts).
%%--------------------------------------------------------------------
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
diff --git a/src/mod_http_api.erl b/src/mod_http_api.erl
index 7be05dbf1..ef881d14b 100644
--- a/src/mod_http_api.erl
+++ b/src/mod_http_api.erl
@@ -538,9 +538,17 @@ json_error(HTTPCode, JSONCode, Message) ->
log(Call, Args, {Addr, Port}) ->
AddrS = misc:ip_to_list({Addr, Port}),
- ?INFO_MSG("API call ~s ~p from ~s:~p", [Call, Args, AddrS, Port]);
+ ?INFO_MSG("API call ~s ~p from ~s:~p", [Call, hide_sensitive_args(Args), AddrS, Port]);
log(Call, Args, IP) ->
- ?INFO_MSG("API call ~s ~p (~p)", [Call, Args, IP]).
+ ?INFO_MSG("API call ~s ~p (~p)", [Call, hide_sensitive_args(Args), IP]).
+
+hide_sensitive_args(Args=[_H|_T]) ->
+ lists:map( fun({<<"password">>, Password}) -> {<<"password">>, ejabberd_config:may_hide_data(Password)};
+ ({<<"newpass">>,NewPassword}) -> {<<"newpass">>, ejabberd_config:may_hide_data(NewPassword)};
+ (E) -> E end,
+ Args);
+hide_sensitive_args(NonListArgs) ->
+ NonListArgs.
permission_addon() ->
Access = gen_mod:get_module_opt(global, ?MODULE, admin_ip_access, none),
diff --git a/src/mod_http_fileserver.erl b/src/mod_http_fileserver.erl
index c2144042e..4e3cfd08b 100644
--- a/src/mod_http_fileserver.erl
+++ b/src/mod_http_fileserver.erl
@@ -59,8 +59,13 @@
-define(HTTP_ERR_FILE_NOT_FOUND,
{-1, 404, [], <<"Not found">>}).
+-define(REQUEST_AUTH_HEADERS,
+ [{<<"WWW-Authenticate">>, <<"Basic realm=\"ejabberd\"">>}]).
+
-define(HTTP_ERR_FORBIDDEN,
{-1, 403, [], <<"Forbidden">>}).
+-define(HTTP_ERR_REQUEST_AUTH,
+ {-1, 401, ?REQUEST_AUTH_HEADERS, <<"Unauthorized">>}).
-define(DEFAULT_CONTENT_TYPE,
<<"application/octet-stream">>).
@@ -317,12 +322,17 @@ serve(LocalPath, Auth, DocRoot, DirectoryIndices, CustomHeaders, DefaultContentT
false
end,
case CanProceed of
+ false ->
+ ?HTTP_ERR_REQUEST_AUTH;
true ->
FileName = filename:join(filename:split(DocRoot) ++ LocalPath),
case file:read_file_info(FileName) of
- {error, enoent} -> ?HTTP_ERR_FILE_NOT_FOUND;
- {error, enotdir} -> ?HTTP_ERR_FILE_NOT_FOUND;
- {error, eacces} -> ?HTTP_ERR_FORBIDDEN;
+ {error, enoent} ->
+ ?HTTP_ERR_FILE_NOT_FOUND;
+ {error, enotdir} ->
+ ?HTTP_ERR_FILE_NOT_FOUND;
+ {error, eacces} ->
+ ?HTTP_ERR_FORBIDDEN;
{ok, #file_info{type = directory}} -> serve_index(FileName,
DirectoryIndices,
CustomHeaders,
diff --git a/src/mod_http_upload.erl b/src/mod_http_upload.erl
index c8cd300f4..8d986d0d3 100644
--- a/src/mod_http_upload.erl
+++ b/src/mod_http_upload.erl
@@ -94,7 +94,7 @@
-record(state,
{server_host :: binary(),
- host :: binary(),
+ hosts :: [binary()],
name :: binary(),
access :: atom(),
max_size :: pos_integer() | infinity,
@@ -151,6 +151,8 @@ stop(ServerHost) ->
mod_opt_type(host) ->
fun iolist_to_binary/1;
+mod_opt_type(hosts) ->
+ fun (L) -> lists:map(fun iolist_to_binary/1, L) end;
mod_opt_type(name) ->
fun iolist_to_binary/1;
mod_opt_type(access) ->
@@ -194,7 +196,7 @@ mod_opt_type(rm_on_unregister) ->
mod_opt_type(thumbnail) ->
fun(B) when is_boolean(B) -> B end;
mod_opt_type(_) ->
- [host, name, access, max_size, secret_length, jid_in_url, file_mode,
+ [host, hosts, name, access, max_size, secret_length, jid_in_url, file_mode,
dir_mode, docroot, put_url, get_url, service_url, custom_headers,
rm_on_unregister, thumbnail].
@@ -211,7 +213,7 @@ depends(_Host, _Opts) ->
init([ServerHost, Opts]) ->
process_flag(trap_exit, true),
- Host = gen_mod:get_opt_host(ServerHost, Opts, <<"upload.@HOST@">>),
+ Hosts = gen_mod:get_opt_hosts(ServerHost, Opts, <<"upload.@HOST@">>),
Name = gen_mod:get_opt(name, Opts, <<"HTTP File Upload">>),
Access = gen_mod:get_opt(access, Opts, local),
MaxSize = gen_mod:get_opt(max_size, Opts, 104857600),
@@ -244,8 +246,11 @@ init([ServerHost, Opts]) ->
false ->
ok
end,
- ejabberd_router:register_route(Host, ServerHost),
- {ok, #state{server_host = ServerHost, host = Host, name = Name,
+ lists:foreach(
+ fun(Host) ->
+ ejabberd_router:register_route(Host, ServerHost)
+ end, Hosts),
+ {ok, #state{server_host = ServerHost, hosts = Hosts, name = Name,
access = Access, max_size = MaxSize,
secret_length = SecretLength, jid_in_url = JIDinURL,
file_mode = FileMode, dir_mode = DirMode,
@@ -324,10 +329,9 @@ handle_info(Info, State) ->
-spec terminate(normal | shutdown | {shutdown, _} | _, state()) -> ok.
-terminate(Reason, #state{server_host = ServerHost, host = Host}) ->
+terminate(Reason, #state{server_host = ServerHost, hosts = Hosts}) ->
?DEBUG("Stopping HTTP upload process for ~s: ~p", [ServerHost, Reason]),
- ejabberd_router:unregister_route(Host),
- ok.
+ lists:foreach(fun ejabberd_router:unregister_route/1, Hosts).
-spec code_change({down, _} | _, state(), _) -> {ok, state()}.
@@ -654,7 +658,7 @@ make_rand_string(S, N) -> make_rand_string([make_rand_char() | S], N - 1).
-spec make_rand_char() -> char().
make_rand_char() ->
- map_int_to_char(crypto:rand_uniform(0, 62)).
+ map_int_to_char(randoms:uniform(0, 61)).
-spec map_int_to_char(0..61) -> char().
diff --git a/src/mod_irc.erl b/src/mod_irc.erl
index fc85668e8..04687ea67 100644
--- a/src/mod_irc.erl
+++ b/src/mod_irc.erl
@@ -58,7 +58,7 @@
[<<"koi8-r">>, <<"iso8859-15">>, <<"iso8859-1">>, <<"iso8859-2">>,
<<"utf-8">>, <<"utf-8+latin-1">>]).
--record(state, {host = <<"">> :: binary(),
+-record(state, {hosts = [] :: [binary()],
server_host = <<"">> :: binary(),
access = all :: atom()}).
@@ -99,8 +99,7 @@ depends(_Host, _Opts) ->
init([Host, Opts]) ->
process_flag(trap_exit, true),
ejabberd:start_app(iconv),
- MyHost = gen_mod:get_opt_host(Host, Opts,
- <<"irc.@HOST@">>),
+ MyHosts = gen_mod:get_opt_hosts(Host, Opts, <<"irc.@HOST@">>),
Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
Mod:init(Host, Opts),
Access = gen_mod:get_opt(access, Opts, all),
@@ -108,10 +107,13 @@ init([Host, Opts]) ->
[named_table, public,
{keypos, #irc_connection.jid_server_host}]),
IQDisc = gen_mod:get_opt(iqdisc, Opts, gen_iq_handler:iqdisc(Host)),
- register_hooks(MyHost, IQDisc),
- ejabberd_router:register_route(MyHost, Host),
+ lists:foreach(
+ fun(MyHost) ->
+ register_hooks(MyHost, IQDisc),
+ ejabberd_router:register_route(MyHost, Host)
+ end, MyHosts),
{ok,
- #state{host = MyHost, server_host = Host,
+ #state{hosts = MyHosts, server_host = Host,
access = Access}}.
%%--------------------------------------------------------------------
@@ -133,8 +135,8 @@ handle_call(stop, _From, State) ->
%% Description: Handling cast messages
%%--------------------------------------------------------------------
handle_cast({reload, ServerHost, NewOpts, OldOpts}, State) ->
- NewHost = gen_mod:get_opt_host(ServerHost, NewOpts, <<"irc.@HOST@">>),
- OldHost = gen_mod:get_opt_host(ServerHost, OldOpts, <<"irc.@HOST@">>),
+ NewHosts = gen_mod:get_opt_hosts(ServerHost, NewOpts, <<"irc.@HOST@">>),
+ OldHosts = gen_mod:get_opt_hosts(ServerHost, OldOpts, <<"irc.@HOST@">>),
NewIQDisc = gen_mod:get_opt(iqdisc, NewOpts, gen_iq_handler:iqdisc(ServerHost)),
OldIQDisc = gen_mod:get_opt(iqdisc, OldOpts, gen_iq_handler:iqdisc(ServerHost)),
NewMod = gen_mod:db_mod(ServerHost, NewOpts, ?MODULE),
@@ -145,20 +147,26 @@ handle_cast({reload, ServerHost, NewOpts, OldOpts}, State) ->
true ->
ok
end,
- if (NewIQDisc /= OldIQDisc) or (NewHost /= OldHost) ->
- register_hooks(NewHost, NewIQDisc);
- true ->
- ok
- end,
- if NewHost /= OldHost ->
- ejabberd_router:register_route(NewHost, ServerHost),
- ejabberd_router:unregister_route(OldHost),
- unregister_hooks(OldHost);
+ if (NewIQDisc /= OldIQDisc) ->
+ lists:foreach(
+ fun(NewHost) ->
+ register_hooks(NewHost, NewIQDisc)
+ end, NewHosts -- (NewHosts -- OldHosts));
true ->
ok
end,
+ lists:foreach(
+ fun(NewHost) ->
+ ejabberd_router:register_route(NewHost, ServerHost),
+ register_hooks(NewHost, NewIQDisc)
+ end, NewHosts -- OldHosts),
+ lists:foreach(
+ fun(OldHost) ->
+ ejabberd_router:unregister_route(OldHost),
+ unregister_hooks(OldHost)
+ end, OldHosts -- NewHosts),
Access = gen_mod:get_opt(access, NewOpts, all),
- {noreply, State#state{host = NewHost, access = Access}};
+ {noreply, State#state{hosts = NewHosts, access = Access}};
handle_cast(Msg, State) ->
?WARNING_MSG("unexpected cast: ~p", [Msg]),
{noreply, State}.
@@ -170,9 +178,10 @@ handle_cast(Msg, State) ->
%% Description: Handling all non call/cast messages
%%--------------------------------------------------------------------
handle_info({route, Packet},
- #state{host = Host, server_host = ServerHost,
- access = Access} =
+ #state{server_host = ServerHost, access = Access} =
State) ->
+ To = xmpp:get_to(Packet),
+ Host = To#jid.lserver,
case catch do_route(Host, ServerHost, Access, Packet) of
{'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]);
_ -> ok
@@ -187,9 +196,12 @@ handle_info(_Info, State) -> {noreply, State}.
%% cleaning up. When it returns, the gen_server terminates with Reason.
%% The return value is ignored.
%%--------------------------------------------------------------------
-terminate(_Reason, #state{host = MyHost}) ->
- ejabberd_router:unregister_route(MyHost),
- unregister_hooks(MyHost).
+terminate(_Reason, #state{hosts = MyHosts}) ->
+ lists:foreach(
+ fun(MyHost) ->
+ ejabberd_router:unregister_route(MyHost),
+ unregister_hooks(MyHost)
+ end, MyHosts).
%%--------------------------------------------------------------------
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
@@ -975,8 +987,10 @@ mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
mod_opt_type(default_encoding) ->
fun iolist_to_binary/1;
mod_opt_type(host) -> fun iolist_to_binary/1;
+mod_opt_type(hosts) ->
+ fun (L) -> lists:map(fun iolist_to_binary/1, L) end;
mod_opt_type(_) ->
- [access, db_type, default_encoding, host].
+ [access, db_type, default_encoding, host, hosts].
-spec extract_ident(stanza()) -> binary().
extract_ident(Packet) ->
diff --git a/src/mod_irc_connection.erl b/src/mod_irc_connection.erl
index 1e90c4005..b7b2f8e1d 100644
--- a/src/mod_irc_connection.erl
+++ b/src/mod_irc_connection.erl
@@ -27,7 +27,7 @@
-author('alexey@process-one.net').
--behaviour(gen_fsm).
+-behaviour(p1_fsm).
%% External exports
-export([start_link/12, start/13, route_chan/4,
@@ -91,7 +91,7 @@ start(From, Host, ServerHost, Server, Username,
start_link(From, Host, Server, Username, Encoding, Port,
Password, Ident, RemoteAddr, RealName, WebircPassword, Mod) ->
- gen_fsm:start_link(?MODULE,
+ p1_fsm:start_link(?MODULE,
[From, Host, Server, Username, Encoding, Port, Password,
Ident, RemoteAddr, RealName, WebircPassword, Mod],
?FSMOPTS).
@@ -109,7 +109,7 @@ start_link(From, Host, Server, Username, Encoding, Port,
%%----------------------------------------------------------------------
init([From, Host, Server, Username, Encoding, Port,
Password, Ident, RemoteAddr, RealName, WebircPassword, Mod]) ->
- gen_fsm:send_event(self(), init),
+ p1_fsm:send_event(self(), init),
{ok, open_socket,
#state{mod = Mod,
encoding = Encoding, port = Port, password = Password,
@@ -628,11 +628,11 @@ handle_info({tcp, _Socket, Data}, StateName,
StateData#state{inbuf = NewBuf}};
handle_info({tcp_closed, _Socket}, StateName,
StateData) ->
- gen_fsm:send_event(self(), closed),
+ p1_fsm:send_event(self(), closed),
{next_state, StateName, StateData};
handle_info({tcp_error, _Socket, _Reason}, StateName,
StateData) ->
- gen_fsm:send_event(self(), closed),
+ p1_fsm:send_event(self(), closed),
{next_state, StateName, StateData}.
%%----------------------------------------------------------------------
diff --git a/src/mod_mam.erl b/src/mod_mam.erl
index d4c18d41b..674cefc05 100644
--- a/src/mod_mam.erl
+++ b/src/mod_mam.erl
@@ -434,8 +434,9 @@ message_is_archived(false, #{jid := JID}, Pkt) ->
delete_old_messages(TypeBin, Days) when TypeBin == <<"chat">>;
TypeBin == <<"groupchat">>;
TypeBin == <<"all">> ->
+ CurrentTime = p1_time_compat:system_time(micro_seconds),
Diff = Days * 24 * 60 * 60 * 1000000,
- TimeStamp = usec_to_now(p1_time_compat:system_time(micro_seconds) - Diff),
+ TimeStamp = misc:usec_to_now(CurrentTime - Diff),
Type = misc:binary_to_atom(TypeBin),
DBTypes = lists:usort(
lists:map(
@@ -830,7 +831,7 @@ select(_LServer, JidRequestor, JidArchive, Query, RSM,
Msgs =
lists:flatmap(
fun({Nick, Pkt, _HaveSubject, Now, _Size}) ->
- TS = now_to_usec(Now),
+ TS = misc:now_to_usec(Now),
case match_interval(Now, Start, End) and
match_rsm(Now, RSM) of
true ->
@@ -979,24 +980,14 @@ match_interval(Now, Start, End) ->
(Now >= Start) and (Now =< End).
match_rsm(Now, #rsm_set{'after' = ID}) when is_binary(ID), ID /= <<"">> ->
- Now1 = (catch usec_to_now(binary_to_integer(ID))),
+ Now1 = (catch misc:usec_to_now(binary_to_integer(ID))),
Now > Now1;
match_rsm(Now, #rsm_set{before = ID}) when is_binary(ID), ID /= <<"">> ->
- Now1 = (catch usec_to_now(binary_to_integer(ID))),
+ Now1 = (catch misc:usec_to_now(binary_to_integer(ID))),
Now < Now1;
match_rsm(_Now, _) ->
true.
-now_to_usec({MSec, Sec, USec}) ->
- (MSec*1000000 + Sec)*1000000 + USec.
-
-usec_to_now(Int) ->
- Secs = Int div 1000000,
- USec = Int rem 1000000,
- MSec = Secs div 1000000,
- Sec = Secs rem 1000000,
- {MSec, Sec, USec}.
-
get_jids(undefined) ->
[];
get_jids(Js) ->
diff --git a/src/mod_mix.erl b/src/mod_mix.erl
index 4763447f3..90507665b 100644
--- a/src/mod_mix.erl
+++ b/src/mod_mix.erl
@@ -46,7 +46,7 @@
?NS_MIX_NODES_CONFIG]).
-record(state, {server_host :: binary(),
- host :: binary()}).
+ hosts :: [binary()]}).
%%%===================================================================
%%% API
@@ -124,36 +124,39 @@ process_iq(#iq{lang = Lang} = IQ) ->
%%%===================================================================
init([ServerHost, Opts]) ->
process_flag(trap_exit, true),
- Host = gen_mod:get_opt_host(ServerHost, Opts, <<"mix.@HOST@">>),
- IQDisc = gen_mod:get_opt(iqdisc, Opts, gen_iq_handler:iqdisc(Host)),
- ConfigTab = gen_mod:get_module_proc(Host, config),
- ets:new(ConfigTab, [named_table]),
- ets:insert(ConfigTab, {plugins, [<<"mix">>]}),
- ejabberd_hooks:add(disco_local_items, Host, ?MODULE, disco_items, 100),
- ejabberd_hooks:add(disco_local_features, Host, ?MODULE, disco_features, 100),
- ejabberd_hooks:add(disco_local_identity, Host, ?MODULE, disco_identity, 100),
- ejabberd_hooks:add(disco_sm_items, Host, ?MODULE, disco_items, 100),
- ejabberd_hooks:add(disco_sm_features, Host, ?MODULE, disco_features, 100),
- ejabberd_hooks:add(disco_sm_identity, Host, ?MODULE, disco_identity, 100),
- ejabberd_hooks:add(disco_info, Host, ?MODULE, disco_info, 100),
- gen_iq_handler:add_iq_handler(ejabberd_local, Host,
- ?NS_DISCO_ITEMS, mod_disco,
- process_local_iq_items, IQDisc),
- gen_iq_handler:add_iq_handler(ejabberd_local, Host,
- ?NS_DISCO_INFO, mod_disco,
- process_local_iq_info, IQDisc),
- gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
- ?NS_DISCO_ITEMS, mod_disco,
- process_local_iq_items, IQDisc),
- gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
- ?NS_DISCO_INFO, mod_disco,
- process_local_iq_info, IQDisc),
- gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
- ?NS_PUBSUB, mod_pubsub, iq_sm, IQDisc),
- gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
- ?NS_MIX_0, ?MODULE, process_iq, IQDisc),
- ejabberd_router:register_route(Host, ServerHost),
- {ok, #state{server_host = ServerHost, host = Host}}.
+ Hosts = gen_mod:get_opt_hosts(ServerHost, Opts, <<"mix.@HOST@">>),
+ IQDisc = gen_mod:get_opt(iqdisc, Opts, gen_iq_handler:iqdisc(ServerHost)),
+ lists:foreach(
+ fun(Host) ->
+ ConfigTab = gen_mod:get_module_proc(Host, config),
+ ets:new(ConfigTab, [named_table]),
+ ets:insert(ConfigTab, {plugins, [<<"mix">>]}),
+ ejabberd_hooks:add(disco_local_items, Host, ?MODULE, disco_items, 100),
+ ejabberd_hooks:add(disco_local_features, Host, ?MODULE, disco_features, 100),
+ ejabberd_hooks:add(disco_local_identity, Host, ?MODULE, disco_identity, 100),
+ ejabberd_hooks:add(disco_sm_items, Host, ?MODULE, disco_items, 100),
+ ejabberd_hooks:add(disco_sm_features, Host, ?MODULE, disco_features, 100),
+ ejabberd_hooks:add(disco_sm_identity, Host, ?MODULE, disco_identity, 100),
+ ejabberd_hooks:add(disco_info, Host, ?MODULE, disco_info, 100),
+ gen_iq_handler:add_iq_handler(ejabberd_local, Host,
+ ?NS_DISCO_ITEMS, mod_disco,
+ process_local_iq_items, IQDisc),
+ gen_iq_handler:add_iq_handler(ejabberd_local, Host,
+ ?NS_DISCO_INFO, mod_disco,
+ process_local_iq_info, IQDisc),
+ gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
+ ?NS_DISCO_ITEMS, mod_disco,
+ process_local_iq_items, IQDisc),
+ gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
+ ?NS_DISCO_INFO, mod_disco,
+ process_local_iq_info, IQDisc),
+ gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
+ ?NS_PUBSUB, mod_pubsub, iq_sm, IQDisc),
+ gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
+ ?NS_MIX_0, ?MODULE, process_iq, IQDisc),
+ ejabberd_router:register_route(Host, ServerHost)
+ end, Hosts),
+ {ok, #state{server_host = ServerHost, hosts = Hosts}}.
handle_call(_Request, _From, State) ->
Reply = ok,
@@ -180,22 +183,24 @@ handle_info({route, Packet}, State) ->
handle_info(_Info, State) ->
{noreply, State}.
-terminate(_Reason, #state{host = Host}) ->
- ejabberd_hooks:delete(disco_local_items, Host, ?MODULE, disco_items, 100),
- ejabberd_hooks:delete(disco_local_features, Host, ?MODULE, disco_features, 100),
- ejabberd_hooks:delete(disco_local_identity, Host, ?MODULE, disco_identity, 100),
- ejabberd_hooks:delete(disco_sm_items, Host, ?MODULE, disco_items, 100),
- ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE, disco_features, 100),
- ejabberd_hooks:delete(disco_sm_identity, Host, ?MODULE, disco_identity, 100),
- ejabberd_hooks:delete(disco_info, Host, ?MODULE, disco_info, 100),
- gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS),
- gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO),
- gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_DISCO_ITEMS),
- gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_DISCO_INFO),
- gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_PUBSUB),
- gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_MIX_0),
- ejabberd_router:unregister_route(Host),
- ok.
+terminate(_Reason, #state{hosts = Hosts}) ->
+ lists:foreach(
+ fun(Host) ->
+ ejabberd_hooks:delete(disco_local_items, Host, ?MODULE, disco_items, 100),
+ ejabberd_hooks:delete(disco_local_features, Host, ?MODULE, disco_features, 100),
+ ejabberd_hooks:delete(disco_local_identity, Host, ?MODULE, disco_identity, 100),
+ ejabberd_hooks:delete(disco_sm_items, Host, ?MODULE, disco_items, 100),
+ ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE, disco_features, 100),
+ ejabberd_hooks:delete(disco_sm_identity, Host, ?MODULE, disco_identity, 100),
+ ejabberd_hooks:delete(disco_info, Host, ?MODULE, disco_info, 100),
+ gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS),
+ gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO),
+ gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_DISCO_ITEMS),
+ gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_DISCO_INFO),
+ gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_PUBSUB),
+ gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_MIX_0),
+ ejabberd_router:unregister_route(Host)
+ end, Hosts).
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
@@ -316,4 +321,6 @@ depends(_Host, _Opts) ->
mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
mod_opt_type(host) -> fun iolist_to_binary/1;
-mod_opt_type(_) -> [host, iqdisc].
+mod_opt_type(hosts) ->
+ fun (L) -> lists:map(fun iolist_to_binary/1, L) end;
+mod_opt_type(_) -> [host, hosts, iqdisc].
diff --git a/src/mod_muc.erl b/src/mod_muc.erl
index 69fc2d0dc..2aebe2226 100644
--- a/src/mod_muc.erl
+++ b/src/mod_muc.erl
@@ -75,7 +75,7 @@
-include("mod_muc.hrl").
-record(state,
- {host = <<"">> :: binary(),
+ {hosts = [] :: [binary()],
server_host = <<"">> :: binary(),
access = {none, none, none, none} :: {atom(), atom(), atom(), atom()},
history_size = 20 :: non_neg_integer(),
@@ -151,8 +151,9 @@ room_destroyed(Host, Room, Pid, ServerHost) ->
%% If Opts = default, the default room options are used.
%% Else use the passed options as defined in mod_muc_room.
create_room(Host, Name, From, Nick, Opts) ->
- Proc = gen_mod:get_module_proc(Host, ?MODULE),
- gen_server:call(Proc, {create, Name, From, Nick, Opts}).
+ ServerHost = ejabberd_router:host_of_route(Host),
+ Proc = gen_mod:get_module_proc(ServerHost, ?MODULE),
+ gen_server:call(Proc, {create, Name, Host, From, Nick, Opts}).
store_room(ServerHost, Host, Name, Opts) ->
LServer = jid:nameprep(ServerHost),
@@ -225,22 +226,26 @@ get_online_rooms_by_user(ServerHost, LUser, LServer) ->
init([Host, Opts]) ->
process_flag(trap_exit, true),
IQDisc = gen_mod:get_opt(iqdisc, Opts, gen_iq_handler:iqdisc(Host)),
- #state{access = Access, host = MyHost,
+ #state{access = Access, hosts = MyHosts,
history_size = HistorySize, queue_type = QueueType,
room_shaper = RoomShaper} = State = init_state(Host, Opts),
Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
RMod = gen_mod:ram_db_mod(Host, Opts, ?MODULE),
- Mod:init(Host, [{host, MyHost}|Opts]),
- RMod:init(Host, [{host, MyHost}|Opts]),
- register_iq_handlers(MyHost, IQDisc),
- ejabberd_router:register_route(MyHost, Host),
- load_permanent_rooms(MyHost, Host, Access, HistorySize, RoomShaper, QueueType),
+ Mod:init(Host, [{hosts, MyHosts}|Opts]),
+ RMod:init(Host, [{hosts, MyHosts}|Opts]),
+ lists:foreach(
+ fun(MyHost) ->
+ register_iq_handlers(MyHost, IQDisc),
+ ejabberd_router:register_route(MyHost, Host),
+ load_permanent_rooms(MyHost, Host, Access, HistorySize,
+ RoomShaper, QueueType)
+ end, MyHosts),
{ok, State}.
handle_call(stop, _From, State) ->
{stop, normal, ok, State};
-handle_call({create, Room, From, Nick, Opts}, _From,
- #state{host = Host, server_host = ServerHost,
+handle_call({create, Room, Host, From, Nick, Opts}, _From,
+ #state{server_host = ServerHost,
access = Access, default_room_opts = DefOpts,
history_size = HistorySize, queue_type = QueueType,
room_shaper = RoomShaper} = State) ->
@@ -256,51 +261,59 @@ handle_call({create, Room, From, Nick, Opts}, _From,
Nick, NewOpts, QueueType),
RMod = gen_mod:ram_db_mod(ServerHost, ?MODULE),
RMod:register_online_room(ServerHost, Room, Host, Pid),
+ ejabberd_hooks:run(create_room, ServerHost, [ServerHost, Room, Host]),
{reply, ok, State}.
-handle_cast({reload, ServerHost, NewOpts, OldOpts}, #state{host = OldHost}) ->
+handle_cast({reload, ServerHost, NewOpts, OldOpts}, #state{hosts = OldHosts}) ->
NewIQDisc = gen_mod:get_opt(iqdisc, NewOpts, gen_iq_handler:iqdisc(ServerHost)),
OldIQDisc = gen_mod:get_opt(iqdisc, OldOpts, gen_iq_handler:iqdisc(ServerHost)),
NewMod = gen_mod:db_mod(ServerHost, NewOpts, ?MODULE),
NewRMod = gen_mod:ram_db_mod(ServerHost, NewOpts, ?MODULE),
OldMod = gen_mod:db_mod(ServerHost, OldOpts, ?MODULE),
OldRMod = gen_mod:ram_db_mod(ServerHost, OldOpts, ?MODULE),
- #state{host = NewHost} = NewState = init_state(ServerHost, NewOpts),
+ #state{hosts = NewHosts} = NewState = init_state(ServerHost, NewOpts),
if NewMod /= OldMod ->
- NewMod:init(ServerHost, [{host, NewHost}|NewOpts]);
+ NewMod:init(ServerHost, [{hosts, NewHosts}|NewOpts]);
true ->
ok
end,
if NewRMod /= OldRMod ->
- NewRMod:init(ServerHost, [{host, NewHost}|NewOpts]);
- true ->
- ok
- end,
- if (NewIQDisc /= OldIQDisc) or (NewHost /= OldHost) ->
- register_iq_handlers(NewHost, NewIQDisc);
+ NewRMod:init(ServerHost, [{hosts, NewHosts}|NewOpts]);
true ->
ok
end,
- if NewHost /= OldHost ->
- ejabberd_router:register_route(NewHost, ServerHost),
- ejabberd_router:unregister_route(OldHost),
- unregister_iq_handlers(OldHost);
+ if (NewIQDisc /= OldIQDisc) ->
+ lists:foreach(
+ fun(NewHost) ->
+ register_iq_handlers(NewHost, NewIQDisc)
+ end, NewHosts -- (NewHosts -- OldHosts));
true ->
ok
end,
+ lists:foreach(
+ fun(NewHost) ->
+ ejabberd_router:register_route(NewHost, ServerHost),
+ register_iq_handlers(NewHost, NewIQDisc)
+ end, NewHosts -- OldHosts),
+ lists:foreach(
+ fun(OldHost) ->
+ ejabberd_router:unregister_route(OldHost),
+ unregister_iq_handlers(OldHost)
+ end, OldHosts -- NewHosts),
{noreply, NewState};
handle_cast(Msg, State) ->
?WARNING_MSG("unexpected cast: ~p", [Msg]),
{noreply, State}.
handle_info({route, Packet},
- #state{host = Host, server_host = ServerHost,
+ #state{server_host = ServerHost,
access = Access, default_room_opts = DefRoomOpts,
history_size = HistorySize, queue_type = QueueType,
max_rooms_discoitems = MaxRoomsDiscoItems,
room_shaper = RoomShaper} = State) ->
From = xmpp:get_from(Packet),
To = xmpp:get_to(Packet),
+ Host = To#jid.lserver,
case catch do_route(Host, ServerHost, Access, HistorySize, RoomShaper,
From, To, Packet, DefRoomOpts, MaxRoomsDiscoItems,
QueueType) of
@@ -319,9 +332,12 @@ handle_info(Info, State) ->
?ERROR_MSG("unexpected info: ~p", [Info]),
{noreply, State}.
-terminate(_Reason, #state{host = MyHost}) ->
- ejabberd_router:unregister_route(MyHost),
- unregister_iq_handlers(MyHost).
+terminate(_Reason, #state{hosts = MyHosts}) ->
+ lists:foreach(
+ fun(MyHost) ->
+ ejabberd_router:unregister_route(MyHost),
+ unregister_iq_handlers(MyHost)
+ end, MyHosts).
code_change(_OldVsn, State, _Extra) -> {ok, State}.
@@ -329,8 +345,8 @@ code_change(_OldVsn, State, _Extra) -> {ok, State}.
%%% Internal functions
%%--------------------------------------------------------------------
init_state(Host, Opts) ->
- MyHost = gen_mod:get_opt_host(Host, Opts,
- <<"conference.@HOST@">>),
+ MyHosts = gen_mod:get_opt_hosts(Host, Opts,
+ <<"conference.@HOST@">>),
Access = gen_mod:get_opt(access, Opts, all),
AccessCreate = gen_mod:get_opt(access_create, Opts, all),
AccessAdmin = gen_mod:get_opt(access_admin, Opts, none),
@@ -341,7 +357,7 @@ init_state(Host, Opts) ->
QueueType = gen_mod:get_opt(queue_type, Opts,
ejabberd_config:default_queue_type(Host)),
RoomShaper = gen_mod:get_opt(room_shaper, Opts, none),
- #state{host = MyHost,
+ #state{hosts = MyHosts,
server_host = Host,
access = {Access, AccessCreate, AccessAdmin, AccessPersistent},
default_room_opts = DefRoomOpts,
@@ -667,7 +683,7 @@ iq_disco_items(_ServerHost, _Host, _From, Lang, _MaxRoomsDiscoItems, _Node, _RSM
{error, timeout | notfound}.
get_room_disco_item({Name, Host, Pid}, Query) ->
RoomJID = jid:make(Name, Host),
- try gen_fsm:sync_send_all_state_event(Pid, Query, 100) of
+ try p1_fsm:sync_send_all_state_event(Pid, Query, 100) of
{item, Desc} ->
{ok, #disco_item{jid = RoomJID, name = Desc}};
false ->
@@ -683,7 +699,7 @@ get_subscribed_rooms(ServerHost, Host, From) ->
BareFrom = jid:remove_resource(From),
lists:flatmap(
fun({Name, _, Pid}) ->
- case gen_fsm:sync_send_all_state_event(Pid, {is_subscribed, BareFrom}) of
+ case p1_fsm:sync_send_all_state_event(Pid, {is_subscribed, BareFrom}) of
true -> [jid:make(Name, Host)];
false -> []
end;
@@ -765,7 +781,7 @@ process_iq_register_set(ServerHost, Host, From,
broadcast_service_message(ServerHost, Host, Msg) ->
lists:foreach(
fun({_, _, Pid}) ->
- gen_fsm:send_all_state_event(
+ p1_fsm:send_all_state_event(
Pid, {service_message, Msg})
end, get_online_rooms(ServerHost, Host)).
@@ -850,6 +866,8 @@ mod_opt_type(ram_db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
mod_opt_type(history_size) ->
fun (I) when is_integer(I), I >= 0 -> I end;
mod_opt_type(host) -> fun iolist_to_binary/1;
+mod_opt_type(hosts) ->
+ fun (L) -> lists:map(fun iolist_to_binary/1, L) end;
mod_opt_type(max_room_desc) ->
fun (infinity) -> infinity;
(I) when is_integer(I), I > 0 -> I
@@ -943,7 +961,7 @@ mod_opt_type({default_room_options, presence_broadcast}) ->
end;
mod_opt_type(_) ->
[access, access_admin, access_create, access_persistent,
- db_type, ram_db_type, history_size, host,
+ db_type, ram_db_type, history_size, host, hosts,
max_room_desc, max_room_id, max_room_name,
max_rooms_discoitems, max_user_conferences, max_users,
max_users_admin_threshold, max_users_presence,
diff --git a/src/mod_muc_admin.erl b/src/mod_muc_admin.erl
index 2d1e66ba5..332c83b55 100644
--- a/src/mod_muc_admin.erl
+++ b/src/mod_muc_admin.erl
@@ -597,7 +597,7 @@ muc_create_room(ServerHost, {Name, Host, _}, DefRoomOpts) ->
destroy_room(Name, Service) ->
case mod_muc:find_online_room(Name, Service) of
{ok, Pid} ->
- gen_fsm:send_all_state_event(Pid, destroy),
+ p1_fsm:send_all_state_event(Pid, destroy),
ok;
error ->
error
@@ -716,11 +716,11 @@ get_rooms(ServerHost) ->
end, Hosts).
get_room_config(Room_pid) ->
- {ok, R} = gen_fsm:sync_send_all_state_event(Room_pid, get_config),
+ {ok, R} = p1_fsm:sync_send_all_state_event(Room_pid, get_config),
R.
get_room_state(Room_pid) ->
- {ok, R} = gen_fsm:sync_send_all_state_event(Room_pid, get_state),
+ {ok, R} = p1_fsm:sync_send_all_state_event(Room_pid, get_state),
R.
%%---------------
@@ -786,7 +786,7 @@ find_serverhost(Host, ServerHosts) ->
ServerHost.
act_on_room(destroy, {N, H, Pid}, SH) ->
- gen_fsm:send_all_state_event(
+ p1_fsm:send_all_state_event(
Pid, {destroy, <<"Room destroyed by rooms_unused_destroy.">>}),
mod_muc:room_destroyed(H, N, Pid, SH),
mod_muc:forget_room(SH, H, N);
@@ -888,7 +888,7 @@ change_room_option(Name, Service, OptionString, ValueString) ->
{Option, Value} = format_room_option(OptionString, ValueString),
Config = get_room_config(Pid),
Config2 = change_option(Option, Value, Config),
- {ok, _} = gen_fsm:sync_send_all_state_event(Pid, {change_config, Config2}),
+ {ok, _} = p1_fsm:sync_send_all_state_event(Pid, {change_config, Config2}),
ok
end.
@@ -983,7 +983,7 @@ get_room_affiliations(Name, Service) ->
case mod_muc:find_online_room(Name, Service) of
{ok, Pid} ->
%% Get the PID of the online room, then request its state
- {ok, StateData} = gen_fsm:sync_send_all_state_event(Pid, get_state),
+ {ok, StateData} = p1_fsm:sync_send_all_state_event(Pid, get_state),
Affiliations = ?DICT:to_list(StateData#state.affiliations),
lists:map(
fun({{Uname, Domain, _Res}, {Aff, Reason}}) when is_atom(Aff)->
@@ -1012,7 +1012,7 @@ set_room_affiliation(Name, Service, JID, AffiliationString) ->
case mod_muc:find_online_room(Name, Service) of
{ok, Pid} ->
%% Get the PID for the online room so we can get the state of the room
- {ok, StateData} = gen_fsm:sync_send_all_state_event(Pid, {process_item_change, {jid:decode(JID), affiliation, Affiliation, <<"">>}, undefined}),
+ {ok, StateData} = p1_fsm:sync_send_all_state_event(Pid, {process_item_change, {jid:decode(JID), affiliation, Affiliation, <<"">>}, undefined}),
mod_muc:store_room(StateData#state.server_host, StateData#state.host, StateData#state.room, make_opts(StateData)),
ok;
error ->
@@ -1035,7 +1035,7 @@ subscribe_room(User, Nick, Room, Nodes) ->
UserJID ->
case get_room_pid(Name, Host) of
Pid when is_pid(Pid) ->
- case gen_fsm:sync_send_all_state_event(
+ case p1_fsm:sync_send_all_state_event(
Pid,
{muc_subscribe, UserJID, Nick, NodeList}) of
{ok, SubscribedNodes} ->
@@ -1062,7 +1062,7 @@ unsubscribe_room(User, Room) ->
UserJID ->
case get_room_pid(Name, Host) of
Pid when is_pid(Pid) ->
- case gen_fsm:sync_send_all_state_event(
+ case p1_fsm:sync_send_all_state_event(
Pid,
{muc_unsubscribe, UserJID}) of
ok ->
@@ -1085,7 +1085,7 @@ unsubscribe_room(User, Room) ->
get_subscribers(Name, Host) ->
case get_room_pid(Name, Host) of
Pid when is_pid(Pid) ->
- {ok, JIDList} = gen_fsm:sync_send_all_state_event(Pid, get_subscribers),
+ {ok, JIDList} = p1_fsm:sync_send_all_state_event(Pid, get_subscribers),
[jid:encode(jid:remove_resource(J)) || J <- JIDList];
_ ->
throw({error, "The room does not exist"})
diff --git a/src/mod_muc_log.erl b/src/mod_muc_log.erl
index 3d4c87c0f..61101d1c2 100644
--- a/src/mod_muc_log.erl
+++ b/src/mod_muc_log.erl
@@ -1142,7 +1142,7 @@ get_room_state(RoomName, MucService) ->
-spec get_room_state(pid()) -> mod_muc_room:state().
get_room_state(RoomPid) ->
- {ok, R} = gen_fsm:sync_send_all_state_event(RoomPid,
+ {ok, R} = p1_fsm:sync_send_all_state_event(RoomPid,
get_state),
R.
diff --git a/src/mod_muc_mnesia.erl b/src/mod_muc_mnesia.erl
index 53f31cb9f..015c5ec43 100644
--- a/src/mod_muc_mnesia.erl
+++ b/src/mod_muc_mnesia.erl
@@ -296,7 +296,7 @@ import(_LServer, <<"muc_registered">>,
%%% gen_server callbacks
%%%===================================================================
init([Host, Opts]) ->
- MyHost = proplists:get_value(host, Opts),
+ MyHosts = proplists:get_value(hosts, Opts),
case gen_mod:db_mod(Host, Opts, mod_muc) of
?MODULE ->
ejabberd_mnesia:create(?MODULE, muc_room,
@@ -318,7 +318,10 @@ init([Host, Opts]) ->
{type, ordered_set},
{attributes, record_info(fields, muc_online_room)}]),
catch ets:new(muc_online_users, [bag, named_table, public, {keypos, 2}]),
- clean_table_from_bad_node(node(), MyHost),
+ lists:foreach(
+ fun(MyHost) ->
+ clean_table_from_bad_node(node(), MyHost)
+ end, MyHosts),
mnesia:subscribe(system);
_ ->
ok
diff --git a/src/mod_muc_riak.erl b/src/mod_muc_riak.erl
index 8dd1eddf8..42e644fdd 100644
--- a/src/mod_muc_riak.erl
+++ b/src/mod_muc_riak.erl
@@ -60,7 +60,7 @@ restore_room(_LServer, Host, Name) ->
forget_room(_LServer, Host, Name) ->
{atomic, ejabberd_riak:delete(muc_room, {Name, Host})}.
-can_use_nick(LServer, Host, JID, Nick) ->
+can_use_nick(_LServer, Host, JID, Nick) ->
{LUser, LServer, _} = jid:tolower(JID),
LUS = {LUser, LServer},
case ejabberd_riak:get_by_index(muc_registered,
diff --git a/src/mod_muc_room.erl b/src/mod_muc_room.erl
index ec1cffd6a..2a1ca6011 100644
--- a/src/mod_muc_room.erl
+++ b/src/mod_muc_room.erl
@@ -27,7 +27,7 @@
-author('alexey@process-one.net').
--behaviour(gen_fsm).
+-behaviour(p1_fsm).
%% External exports
-export([start_link/10,
@@ -94,23 +94,23 @@
%%%----------------------------------------------------------------------
start(Host, ServerHost, Access, Room, HistorySize, RoomShaper,
Creator, Nick, DefRoomOpts, QueueType) ->
- gen_fsm:start(?MODULE, [Host, ServerHost, Access, Room, HistorySize,
+ p1_fsm:start(?MODULE, [Host, ServerHost, Access, Room, HistorySize,
RoomShaper, Creator, Nick, DefRoomOpts, QueueType],
?FSMOPTS).
start(Host, ServerHost, Access, Room, HistorySize, RoomShaper, Opts, QueueType) ->
- gen_fsm:start(?MODULE, [Host, ServerHost, Access, Room, HistorySize,
+ p1_fsm:start(?MODULE, [Host, ServerHost, Access, Room, HistorySize,
RoomShaper, Opts, QueueType],
?FSMOPTS).
start_link(Host, ServerHost, Access, Room, HistorySize, RoomShaper,
Creator, Nick, DefRoomOpts, QueueType) ->
- gen_fsm:start_link(?MODULE, [Host, ServerHost, Access, Room, HistorySize,
+ p1_fsm:start_link(?MODULE, [Host, ServerHost, Access, Room, HistorySize,
RoomShaper, Creator, Nick, DefRoomOpts, QueueType],
?FSMOPTS).
start_link(Host, ServerHost, Access, Room, HistorySize, RoomShaper, Opts, QueueType) ->
- gen_fsm:start_link(?MODULE, [Host, ServerHost, Access, Room, HistorySize,
+ p1_fsm:start_link(?MODULE, [Host, ServerHost, Access, Room, HistorySize,
RoomShaper, Opts, QueueType],
?FSMOPTS).
@@ -703,7 +703,7 @@ terminate(Reason, _StateName, StateData) ->
-spec route(pid(), stanza()) -> ok.
route(Pid, Packet) ->
#jid{lresource = Nick} = xmpp:get_to(Packet),
- gen_fsm:send_event(Pid, {route, Nick, Packet}).
+ p1_fsm:send_event(Pid, {route, Nick, Packet}).
-spec process_groupchat_message(message(), state()) -> fsm_next().
process_groupchat_message(#message{from = From, lang = Lang} = Packet, StateData) ->
@@ -4004,6 +4004,7 @@ tab_add_online_user(JID, StateData) ->
Room = StateData#state.room,
Host = StateData#state.host,
ServerHost = StateData#state.server_host,
+ ejabberd_hooks:run(join_room, ServerHost, [ServerHost, Room, Host, JID]),
mod_muc:register_online_user(ServerHost, jid:tolower(JID), Room, Host).
-spec tab_remove_online_user(jid(), state()) -> any().
@@ -4011,6 +4012,7 @@ tab_remove_online_user(JID, StateData) ->
Room = StateData#state.room,
Host = StateData#state.host,
ServerHost = StateData#state.server_host,
+ ejabberd_hooks:run(leave_room, ServerHost, [ServerHost, Room, Host, JID]),
mod_muc:unregister_online_user(ServerHost, jid:tolower(JID), Room, Host).
-spec tab_count_user(jid(), state()) -> non_neg_integer().
diff --git a/src/mod_multicast.erl b/src/mod_multicast.erl
index 4d3e481a7..e10315b7a 100644
--- a/src/mod_multicast.erl
+++ b/src/mod_multicast.erl
@@ -998,8 +998,10 @@ build_service_limit_record(LimitOpts) ->
build_limit_record(LimitOptsR, remote)}.
get_from_limitopts(LimitOpts, SenderT) ->
- {SenderT, Result} = lists:keyfind(SenderT, 1, LimitOpts),
- Result.
+ case lists:keyfind(SenderT, 1, LimitOpts) of
+ false -> [];
+ {SenderT, Result} -> Result
+ end.
build_remote_limit_record(LimitOpts, SenderT) ->
build_limit_record(LimitOpts, SenderT).
@@ -1120,10 +1122,10 @@ mod_opt_type(host) -> fun iolist_to_binary/1;
mod_opt_type({limits, Type}) when (Type == local) or (Type == remote) ->
fun(L) ->
lists:map(
- fun ({message, infinite}) -> infinite;
- ({presence, infinite}) -> infinite;
- ({message, I}) when is_integer(I) -> I;
- ({presence, I}) when is_integer(I) -> I
+ fun ({message, infinite} = O) -> O;
+ ({presence, infinite} = O) -> O;
+ ({message, I} = O) when is_integer(I) -> O;
+ ({presence, I} = O) when is_integer(I) -> O
end, L)
end;
mod_opt_type(_) -> [access, host, {limits, local}, {limits, remote}].
diff --git a/src/mod_proxy65.erl b/src/mod_proxy65.erl
index aee324960..671baef9a 100644
--- a/src/mod_proxy65.erl
+++ b/src/mod_proxy65.erl
@@ -112,6 +112,8 @@ depends(_Host, _Opts) ->
mod_opt_type(access) -> fun acl:access_rules_validator/1;
mod_opt_type(host) -> fun iolist_to_binary/1;
+mod_opt_type(hosts) ->
+ fun(L) -> lists:map(fun iolist_to_binary/1, L) end;
mod_opt_type(hostname) -> fun iolist_to_binary/1;
mod_opt_type(ip) ->
fun (S) ->
@@ -131,7 +133,7 @@ mod_opt_type(ram_db_type) ->
mod_opt_type(Opt) ->
case mod_proxy65_stream:listen_opt_type(Opt) of
Opts when is_list(Opts) ->
- [access, host, hostname, ip, name, port,
+ [access, host, hosts, hostname, ip, name, port,
max_connections, ram_db_type] ++ Opts;
Fun ->
Fun
diff --git a/src/mod_proxy65_service.erl b/src/mod_proxy65_service.erl
index b27f3bc20..aaece980a 100644
--- a/src/mod_proxy65_service.erl
+++ b/src/mod_proxy65_service.erl
@@ -43,7 +43,7 @@
-define(PROCNAME, ejabberd_mod_proxy65_service).
--record(state, {myhost = <<"">> :: binary()}).
+-record(state, {myhosts = [] :: [binary()]}).
%%%------------------------
%%% gen_server callbacks
@@ -61,24 +61,27 @@ reload(Host, NewOpts, OldOpts) ->
init([Host, Opts]) ->
process_flag(trap_exit, true),
IQDisc = gen_mod:get_opt(iqdisc, Opts, gen_iq_handler:iqdisc(Host)),
- MyHost = gen_mod:get_opt_host(Host, Opts, <<"proxy.@HOST@">>),
- gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_INFO,
- ?MODULE, process_disco_info, IQDisc),
- gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_ITEMS,
- ?MODULE, process_disco_items, IQDisc),
- gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_VCARD,
- ?MODULE, process_vcard, IQDisc),
- gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_BYTESTREAMS,
- ?MODULE, process_bytestreams, IQDisc),
- ejabberd_router:register_route(MyHost, Host),
- {ok, #state{myhost = MyHost}}.
+ MyHosts = gen_mod:get_opt_hosts(Host, Opts, <<"proxy.@HOST@">>),
+ lists:foreach(
+ fun(MyHost) ->
+ gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_INFO,
+ ?MODULE, process_disco_info, IQDisc),
+ gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_ITEMS,
+ ?MODULE, process_disco_items, IQDisc),
+ gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_VCARD,
+ ?MODULE, process_vcard, IQDisc),
+ gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_BYTESTREAMS,
+ ?MODULE, process_bytestreams, IQDisc),
+ ejabberd_router:register_route(MyHost, Host)
+ end, MyHosts),
+ {ok, #state{myhosts = MyHosts}}.
-terminate(_Reason, #state{myhost = MyHost}) ->
- ejabberd_router:unregister_route(MyHost),
- gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_INFO),
- gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_ITEMS),
- gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_VCARD),
- gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_BYTESTREAMS).
+terminate(_Reason, #state{myhosts = MyHosts}) ->
+ lists:foreach(
+ fun(MyHost) ->
+ ejabberd_router:unregister_route(MyHost),
+ unregister_handlers(MyHost)
+ end, MyHosts).
handle_info({route, #iq{} = Packet}, State) ->
ejabberd_router:process_iq(Packet),
@@ -89,33 +92,29 @@ handle_call(_Request, _From, State) ->
{reply, ok, State}.
handle_cast({reload, ServerHost, NewOpts, OldOpts}, State) ->
- NewHost = gen_mod:get_opt_host(ServerHost, NewOpts, <<"proxy.@HOST@">>),
- OldHost = gen_mod:get_opt_host(ServerHost, OldOpts, <<"proxy.@HOST@">>),
+ NewHosts = gen_mod:get_opt_hosts(ServerHost, NewOpts, <<"proxy.@HOST@">>),
+ OldHosts = gen_mod:get_opt_hosts(ServerHost, OldOpts, <<"proxy.@HOST@">>),
NewIQDisc = gen_mod:get_opt(iqdisc, NewOpts, gen_iq_handler:iqdisc(ServerHost)),
OldIQDisc = gen_mod:get_opt(iqdisc, OldOpts, gen_iq_handler:iqdisc(ServerHost)),
- if (NewIQDisc /= OldIQDisc) or (NewHost /= OldHost) ->
- gen_iq_handler:add_iq_handler(ejabberd_local, NewHost, ?NS_DISCO_INFO,
- ?MODULE, process_disco_info, NewIQDisc),
- gen_iq_handler:add_iq_handler(ejabberd_local, NewHost, ?NS_DISCO_ITEMS,
- ?MODULE, process_disco_items, NewIQDisc),
- gen_iq_handler:add_iq_handler(ejabberd_local, NewHost, ?NS_VCARD,
- ?MODULE, process_vcard, NewIQDisc),
- gen_iq_handler:add_iq_handler(ejabberd_local, NewHost, ?NS_BYTESTREAMS,
- ?MODULE, process_bytestreams, NewIQDisc);
- true ->
- ok
- end,
- if NewHost /= OldHost ->
- ejabberd_router:register_route(NewHost, ServerHost),
- ejabberd_router:unregister_route(OldHost),
- gen_iq_handler:remove_iq_handler(ejabberd_local, OldHost, ?NS_DISCO_INFO),
- gen_iq_handler:remove_iq_handler(ejabberd_local, OldHost, ?NS_DISCO_ITEMS),
- gen_iq_handler:remove_iq_handler(ejabberd_local, OldHost, ?NS_VCARD),
- gen_iq_handler:remove_iq_handler(ejabberd_local, OldHost, ?NS_BYTESTREAMS);
+ if (NewIQDisc /= OldIQDisc) ->
+ lists:foreach(
+ fun(NewHost) ->
+ register_handlers(NewHost, NewIQDisc)
+ end, NewHosts -- (NewHosts -- OldHosts));
true ->
ok
end,
- {noreply, State#state{myhost = NewHost}};
+ lists:foreach(
+ fun(NewHost) ->
+ ejabberd_router:register_route(NewHost, ServerHost),
+ register_handlers(NewHost, NewIQDisc)
+ end, NewHosts -- OldHosts),
+ lists:foreach(
+ fun(OldHost) ->
+ ejabberd_router:unregister_route(OldHost),
+ unregister_handlers(OldHost)
+ end, OldHosts -- NewHosts),
+ {noreply, State#state{myhosts = NewHosts}};
handle_cast(Msg, State) ->
?WARNING_MSG("unexpected cast: ~p", [Msg]),
{noreply, State}.
@@ -276,3 +275,19 @@ get_my_ip() ->
max_connections(ServerHost) ->
gen_mod:get_module_opt(ServerHost, mod_proxy65, max_connections, infinity).
+
+register_handlers(Host, IQDisc) ->
+ gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO,
+ ?MODULE, process_disco_info, IQDisc),
+ gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS,
+ ?MODULE, process_disco_items, IQDisc),
+ gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_VCARD,
+ ?MODULE, process_vcard, IQDisc),
+ gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_BYTESTREAMS,
+ ?MODULE, process_bytestreams, IQDisc).
+
+unregister_handlers(Host) ->
+ gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO),
+ gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS),
+ gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_VCARD),
+ gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_BYTESTREAMS).
diff --git a/src/mod_proxy65_stream.erl b/src/mod_proxy65_stream.erl
index 899b66727..1fa6ff804 100644
--- a/src/mod_proxy65_stream.erl
+++ b/src/mod_proxy65_stream.erl
@@ -26,7 +26,7 @@
-author('xram@jabber.ru').
--behaviour(gen_fsm).
+-behaviour(p1_fsm).
%% gen_fsm callbacks.
-export([init/1, handle_event/3, handle_sync_event/4,
@@ -75,7 +75,7 @@ start({gen_tcp, Socket}, Opts1) ->
[Socket, Host, Opts]).
start_link(Socket, Host, Opts) ->
- gen_fsm:start_link(?MODULE, [Socket, Host, Opts], []).
+ p1_fsm:start_link(?MODULE, [Socket, Host, Opts], []).
init([Socket, Host, Opts]) ->
process_flag(trap_exit, true),
@@ -106,9 +106,9 @@ socket_type() -> raw.
stop(StreamPid) -> StreamPid ! stop.
activate({P1, J1}, {P2, J2}) ->
- case catch {gen_fsm:sync_send_all_state_event(P1,
+ case catch {p1_fsm:sync_send_all_state_event(P1,
get_socket),
- gen_fsm:sync_send_all_state_event(P2, get_socket)}
+ p1_fsm:sync_send_all_state_event(P2, get_socket)}
of
{S1, S2} when is_port(S1), is_port(S2) ->
P1 ! {activate, P2, S2, J1, J2},
@@ -197,7 +197,7 @@ handle_info({tcp, _S, Data}, StateName, StateData)
when StateName /= wait_for_activation ->
erlang:cancel_timer(StateData#state.timer),
TRef = erlang:send_after(?WAIT_TIMEOUT, self(), stop),
- gen_fsm:send_event(self(), Data),
+ p1_fsm:send_event(self(), Data),
{next_state, StateName, StateData#state{timer = TRef}};
%% Activation message.
handle_info({activate, PeerPid, PeerSocket, IJid, TJid},
diff --git a/src/mod_pubsub.erl b/src/mod_pubsub.erl
index 1a620cb6b..a67ae5bfc 100644
--- a/src/mod_pubsub.erl
+++ b/src/mod_pubsub.erl
@@ -52,7 +52,7 @@
%% exports for hooks
-export([presence_probe/3, caps_add/3, caps_update/3,
in_subscription/6, out_subscription/4,
- on_user_offline/3, remove_user/2,
+ on_user_online/1, on_user_offline/2, remove_user/2,
disco_local_identity/5, disco_local_features/5,
disco_local_items/5, disco_sm_identity/5,
disco_sm_features/5, disco_sm_items/5,
@@ -89,11 +89,7 @@
%% API and gen_server callbacks
-export([start/2, stop/1, init/1,
handle_call/3, handle_cast/2, handle_info/2,
- terminate/2, code_change/3, depends/2]).
-
--export([send_loop/1, mod_opt_type/1]).
-
--define(LOOPNAME, ejabberd_mod_pubsub_loop).
+ terminate/2, code_change/3, depends/2, export/1, mod_opt_type/1]).
%%====================================================================
%% API
@@ -189,7 +185,7 @@
-record(state,
{
server_host,
- host,
+ hosts,
access,
pep_mapping = [],
ignore_pep_from_offline = true,
@@ -205,7 +201,7 @@
-type(state() ::
#state{
server_host :: binary(),
- host :: mod_pubsub:hostPubsub(),
+ hosts :: [mod_pubsub:hostPubsub()],
access :: atom(),
pep_mapping :: [{binary(), binary()}],
ignore_pep_from_offline :: boolean(),
@@ -243,43 +239,66 @@ stop(Host) ->
init([ServerHost, Opts]) ->
process_flag(trap_exit, true),
?DEBUG("pubsub init ~p ~p", [ServerHost, Opts]),
- Host = gen_mod:get_opt_host(ServerHost, Opts, <<"pubsub.@HOST@">>),
- ejabberd_router:register_route(Host, ServerHost),
+ Hosts = gen_mod:get_opt_hosts(ServerHost, Opts, <<"pubsub.@HOST@">>),
Access = gen_mod:get_opt(access_createnode, Opts, all),
PepOffline = gen_mod:get_opt(ignore_pep_from_offline, Opts, true),
- IQDisc = gen_mod:get_opt(iqdisc, Opts, gen_iq_handler:iqdisc(Host)),
+ IQDisc = gen_mod:get_opt(iqdisc, Opts, gen_iq_handler:iqdisc(ServerHost)),
LastItemCache = gen_mod:get_opt(last_item_cache, Opts, false),
MaxItemsNode = gen_mod:get_opt(max_items_node, Opts, ?MAXITEMS),
MaxSubsNode = gen_mod:get_opt(max_subscriptions_node, Opts),
- case gen_mod:db_type(ServerHost, ?MODULE) of
- mnesia -> pubsub_index:init(Host, ServerHost, Opts);
- _ -> ok
- end,
- {Plugins, NodeTree, PepMapping} = init_plugins(Host, ServerHost, Opts),
- DefaultModule = plugin(Host, hd(Plugins)),
- BaseOptions = DefaultModule:options(),
- DefaultNodeCfg = filter_node_options(
- gen_mod:get_opt(default_node_config, Opts, []),
- BaseOptions),
ejabberd_mnesia:create(?MODULE, pubsub_last_item,
- [{ram_copies, [node()]},
- {attributes, record_info(fields, pubsub_last_item)}]),
- lists:foreach(
- fun(H) ->
- T = gen_mod:get_module_proc(H, config),
- ets:new(T, [set, named_table]),
- ets:insert(T, {nodetree, NodeTree}),
- ets:insert(T, {plugins, Plugins}),
- ets:insert(T, {last_item_cache, LastItemCache}),
- ets:insert(T, {max_items_node, MaxItemsNode}),
- ets:insert(T, {max_subscriptions_node, MaxSubsNode}),
- ets:insert(T, {default_node_config, DefaultNodeCfg}),
- ets:insert(T, {pep_mapping, PepMapping}),
- ets:insert(T, {ignore_pep_from_offline, PepOffline}),
- ets:insert(T, {host, Host}),
- ets:insert(T, {access, Access})
- end, [Host, ServerHost]),
- ejabberd_hooks:add(sm_remove_connection_hook, ServerHost,
+ [{ram_copies, [node()]},
+ {attributes, record_info(fields, pubsub_last_item)}]),
+ AllPlugins =
+ lists:flatmap(
+ fun(Host) ->
+ ejabberd_router:register_route(Host, ServerHost),
+ case gen_mod:db_type(ServerHost, ?MODULE) of
+ mnesia -> pubsub_index:init(Host, ServerHost, Opts);
+ _ -> ok
+ end,
+ {Plugins, NodeTree, PepMapping} = init_plugins(Host, ServerHost, Opts),
+ DefaultModule = plugin(Host, hd(Plugins)),
+ BaseOptions = DefaultModule:options(),
+ DefaultNodeCfg = filter_node_options(
+ gen_mod:get_opt(default_node_config, Opts, []),
+ BaseOptions),
+ lists:foreach(
+ fun(H) ->
+ T = gen_mod:get_module_proc(H, config),
+ try
+ ets:new(T, [set, named_table]),
+ ets:insert(T, {nodetree, NodeTree}),
+ ets:insert(T, {plugins, Plugins}),
+ ets:insert(T, {last_item_cache, LastItemCache}),
+ ets:insert(T, {max_items_node, MaxItemsNode}),
+ ets:insert(T, {max_subscriptions_node, MaxSubsNode}),
+ ets:insert(T, {default_node_config, DefaultNodeCfg}),
+ ets:insert(T, {pep_mapping, PepMapping}),
+ ets:insert(T, {ignore_pep_from_offline, PepOffline}),
+ ets:insert(T, {host, Host}),
+ ets:insert(T, {access, Access})
+ catch error:badarg when H == ServerHost ->
+ ok
+ end
+ end, [Host, ServerHost]),
+ gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO,
+ ?MODULE, process_disco_info, IQDisc),
+ gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS,
+ ?MODULE, process_disco_items, IQDisc),
+ gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_PUBSUB,
+ ?MODULE, process_pubsub, IQDisc),
+ gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_PUBSUB_OWNER,
+ ?MODULE, process_pubsub_owner, IQDisc),
+ gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_VCARD,
+ ?MODULE, process_vcard, IQDisc),
+ gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_COMMANDS,
+ ?MODULE, process_commands, IQDisc),
+ Plugins
+ end, Hosts),
+ ejabberd_hooks:add(c2s_session_opened, ServerHost,
+ ?MODULE, on_user_online, 75),
+ ejabberd_hooks:add(c2s_terminated, ServerHost,
?MODULE, on_user_offline, 75),
ejabberd_hooks:add(disco_local_identity, ServerHost,
?MODULE, disco_local_identity, 75),
@@ -297,19 +316,7 @@ init([ServerHost, Opts]) ->
?MODULE, remove_user, 50),
ejabberd_hooks:add(c2s_handle_info, ServerHost,
?MODULE, c2s_handle_info, 50),
- gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO,
- ?MODULE, process_disco_info, IQDisc),
- gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS,
- ?MODULE, process_disco_items, IQDisc),
- gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_PUBSUB,
- ?MODULE, process_pubsub, IQDisc),
- gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_PUBSUB_OWNER,
- ?MODULE, process_pubsub_owner, IQDisc),
- gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_VCARD,
- ?MODULE, process_vcard, IQDisc),
- gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_COMMANDS,
- ?MODULE, process_commands, IQDisc),
- case lists:member(?PEPNODE, Plugins) of
+ case lists:member(?PEPNODE, AllPlugins) of
true ->
ejabberd_hooks:add(caps_add, ServerHost,
?MODULE, caps_add, 80),
@@ -328,35 +335,16 @@ init([ServerHost, Opts]) ->
false ->
ok
end,
- {_, State} = init_send_loop(ServerHost),
- {ok, State}.
-
-init_send_loop(ServerHost) ->
NodeTree = config(ServerHost, nodetree),
Plugins = config(ServerHost, plugins),
- LastItemCache = config(ServerHost, last_item_cache),
- MaxItemsNode = config(ServerHost, max_items_node),
PepMapping = config(ServerHost, pep_mapping),
- PepOffline = config(ServerHost, ignore_pep_from_offline),
- Host = config(ServerHost, host),
- Access = config(ServerHost, access),
DBType = gen_mod:db_type(ServerHost, ?MODULE),
- State = #state{host = Host, server_host = ServerHost,
- access = Access, pep_mapping = PepMapping,
- ignore_pep_from_offline = PepOffline,
- last_item_cache = LastItemCache,
- max_items_node = MaxItemsNode, nodetree = NodeTree,
- plugins = Plugins, db_type = DBType},
- Proc = gen_mod:get_module_proc(ServerHost, ?LOOPNAME),
- Pid = case whereis(Proc) of
- undefined ->
- SendLoop = spawn(?MODULE, send_loop, [State]),
- register(Proc, SendLoop),
- SendLoop;
- Loop ->
- Loop
- end,
- {Pid, State}.
+ {ok, #state{hosts = Hosts, server_host = ServerHost,
+ access = Access, pep_mapping = PepMapping,
+ ignore_pep_from_offline = PepOffline,
+ last_item_cache = LastItemCache,
+ max_items_node = MaxItemsNode, nodetree = NodeTree,
+ plugins = Plugins, db_type = DBType}}.
depends(ServerHost, Opts) ->
Host = gen_mod:get_opt_host(ServerHost, Opts, <<"pubsub.@HOST@">>),
@@ -406,94 +394,6 @@ terminate_plugins(Host, ServerHost, Plugins, TreePlugin) ->
TreePlugin:terminate(Host, ServerHost),
ok.
-get_subscribed(User, Server) ->
- Items = ejabberd_hooks:run_fold(roster_get, Server, [], [{User, Server}]),
- lists:filtermap(
- fun(#roster{jid = LJID, subscription = Sub})
- when Sub == both orelse Sub == from ->
- {true, LJID};
- (_) ->
- false
- end, Items).
-
-send_loop(State) ->
- receive
- {presence, JID, _Pid} ->
- Host = State#state.host,
- ServerHost = State#state.server_host,
- DBType = State#state.db_type,
- LJID = jid:tolower(JID),
- BJID = jid:remove_resource(LJID),
- lists:foreach(
- fun(PType) ->
- Subs = get_subscriptions_for_send_last(Host, PType, DBType, JID, LJID, BJID),
- lists:foreach(
- fun({NodeRec, _, _, SubJID}) ->
- {_, Node} = NodeRec#pubsub_node.nodeid,
- Nidx = NodeRec#pubsub_node.id,
- Options = NodeRec#pubsub_node.options,
- [send_items(Host, Node, Nidx, PType, Options, SubJID, last)
- || NodeRec#pubsub_node.type == PType]
- end,
- lists:usort(Subs))
- end,
- State#state.plugins),
- if not State#state.ignore_pep_from_offline ->
- {User, Server, Resource} = LJID,
- Contacts = get_subscribed(User, Server),
- lists:foreach(
- fun({U, S, R}) when S == ServerHost ->
- case user_resources(U, S) of
- [] -> %% offline
- PeerJID = jid:make(U, S, R),
- self() ! {presence, User, Server, [Resource], PeerJID};
- _ -> %% online
- %% this is already handled by presence probe
- ok
- end;
- (_) ->
- %% we can not do anything in any cases
- ok
- end, Contacts);
- true ->
- ok
- end,
- send_loop(State);
- {presence, User, Server, Resources, JID} ->
- spawn(fun() ->
- Host = State#state.host,
- Owner = jid:remove_resource(jid:tolower(JID)),
- lists:foreach(fun(#pubsub_node{nodeid = {_, Node}, type = Type, id = Nidx, options = Options}) ->
- case match_option(Options, send_last_published_item, on_sub_and_presence) of
- true ->
- lists:foreach(fun(Resource) ->
- LJID = {User, Server, Resource},
- Subscribed = case get_option(Options, access_model) of
- open -> true;
- presence -> true;
- whitelist -> false; % subscribers are added manually
- authorize -> false; % likewise
- roster ->
- Grps = get_option(Options, roster_groups_allowed, []),
- {OU, OS, _} = Owner,
- element(2, get_roster_info(OU, OS, LJID, Grps))
- end,
- if Subscribed -> send_items(Owner, Node, Nidx, Type, Options, LJID, last);
- true -> ok
- end
- end,
- Resources);
- _ ->
- ok
- end
- end,
- tree_action(Host, get_nodes, [Owner, JID]))
- end),
- send_loop(State);
- stop ->
- ok
- end.
-
%% -------
%% disco hooks handling functions
%%
@@ -546,13 +446,11 @@ disco_identity(Host, Node, From) ->
case get_allowed_items_call(Host, Nidx, From, Type,
Options, Owners) of
{result, _} ->
- {result, [#identity{category = <<"pubsub">>,
- type = <<"pep">>},
- #identity{category = <<"pubsub">>,
- type = <<"leaf">>,
+ {result, [#identity{category = <<"pubsub">>, type = <<"pep">>},
+ #identity{category = <<"pubsub">>, type = <<"leaf">>,
name = case get_option(Options, title) of
false -> <<>>;
- [Title] -> Title
+ Title -> Title
end}]};
_ ->
{result, []}
@@ -586,8 +484,7 @@ disco_features(Host, Node, From) ->
Type, Options, Owners) of
{result, _} ->
{result,
- [?NS_PUBSUB |
- [feature(F) || F <- plugin_features(Host, <<"pep">>)]]};
+ [?NS_PUBSUB | [feature(F) || F <- plugin_features(Host, <<"pep">>)]]};
_ ->
{result, []}
end
@@ -620,7 +517,7 @@ disco_items(Host, <<>>, From) ->
jid = jid:make(Host),
name = case get_option(Options, title) of
false -> <<>>;
- [Title] -> Title
+ Title -> Title
end} | Acc];
_ ->
Acc
@@ -655,12 +552,12 @@ disco_items(Host, Node, From) ->
end.
%% -------
-%% presence hooks handling functions
+%% presence and session hooks handling functions
%%
-spec caps_add(jid(), jid(), [binary()]) -> ok.
-caps_add(#jid{luser = U, lserver = S, lresource = R}, #jid{lserver = Host} = JID, _Features)
- when Host =/= S ->
+caps_add(#jid{lserver = S1} = From, #jid{lserver = S2} = To, _Features)
+ when S1 =/= S2 ->
%% When a remote contact goes online while the local user is offline, the
%% remote contact won't receive last items from the local user even if
%% ignore_pep_from_offline is set to false. To work around this issue a bit,
@@ -670,36 +567,36 @@ caps_add(#jid{luser = U, lserver = S, lresource = R}, #jid{lserver = Host} = JID
%% contact becomes available; the former is also executed when the local
%% user goes online (because that triggers the contact to send a presence
%% packet with CAPS).
- presence(Host, {presence, U, S, [R], JID});
+ send_last_pep(To, From);
caps_add(_From, _To, _Feature) ->
ok.
-spec caps_update(jid(), jid(), [binary()]) -> ok.
-caps_update(#jid{luser = U, lserver = S, lresource = R}, #jid{lserver = Host} = JID, _Features) ->
- presence(Host, {presence, U, S, [R], JID}).
+caps_update(From, To, _Features) ->
+ send_last_pep(To, From).
-spec presence_probe(jid(), jid(), pid()) -> ok.
-presence_probe(#jid{luser = U, lserver = S, lresource = R} = JID, JID, Pid) ->
- presence(S, {presence, JID, Pid}),
- presence(S, {presence, U, S, [R], JID});
presence_probe(#jid{luser = U, lserver = S}, #jid{luser = U, lserver = S}, _Pid) ->
%% ignore presence_probe from my other ressources
- %% to not get duplicated last items
ok;
-presence_probe(#jid{luser = U, lserver = S, lresource = R}, #jid{lserver = S} = JID, _Pid) ->
- presence(S, {presence, U, S, [R], JID});
-presence_probe(_Host, _JID, _Pid) ->
- %% ignore presence_probe from remote contacts,
- %% those are handled via caps_add
+presence_probe(#jid{lserver = S} = From, #jid{lserver = S} = To, _Pid) ->
+ send_last_pep(To, From);
+presence_probe(_From, _To, _Pid) ->
+ %% ignore presence_probe from remote contacts, those are handled via caps_add
ok.
-presence(ServerHost, Presence) ->
- {SendLoop, _} = case whereis(gen_mod:get_module_proc(ServerHost, ?LOOPNAME)) of
- undefined -> init_send_loop(ServerHost);
- Pid -> {Pid, undefined}
- end,
- SendLoop ! Presence,
- ok.
+-spec on_user_online(ejabberd_c2s:state()) -> ejabberd_c2s:state().
+on_user_online(C2SState) ->
+ JID = maps:get(jid, C2SState),
+ send_last_items(JID),
+ C2SState.
+
+-spec on_user_offline(ejabberd_c2s:state(), atom()) -> ejabberd_c2s:state().
+on_user_offline(#{jid := JID} = C2SState, _Reason) ->
+ purge_offline(jid:tolower(JID)),
+ C2SState;
+on_user_offline(C2SState, _Reason) ->
+ C2SState.
%% -------
%% subscription hooks handling functions
@@ -708,14 +605,8 @@ presence(ServerHost, Presence) ->
-spec out_subscription(
binary(), binary(), jid(),
subscribed | unsubscribed | subscribe | unsubscribe) -> boolean().
-out_subscription(User, Server, JID, subscribed) ->
- Owner = jid:make(User, Server),
- {PUser, PServer, PResource} = jid:tolower(JID),
- PResources = case PResource of
- <<>> -> user_resources(PUser, PServer);
- _ -> [PResource]
- end,
- presence(Server, {presence, PUser, PServer, PResources, Owner}),
+out_subscription(User, Server, To, subscribed) ->
+ send_last_pep(jid:make(User, Server), To),
true;
out_subscription(_, _, _, _) ->
true.
@@ -864,7 +755,7 @@ handle_info(_Info, State) ->
%%--------------------------------------------------------------------
%% @private
terminate(_Reason,
- #state{host = Host, server_host = ServerHost, nodetree = TreePlugin, plugins = Plugins}) ->
+ #state{hosts = Hosts, server_host = ServerHost, nodetree = TreePlugin, plugins = Plugins}) ->
case lists:member(?PEPNODE, Plugins) of
true ->
ejabberd_hooks:delete(caps_add, ServerHost,
@@ -884,7 +775,9 @@ terminate(_Reason,
false ->
ok
end,
- ejabberd_hooks:delete(sm_remove_connection_hook, ServerHost,
+ ejabberd_hooks:delete(c2s_session_opened, ServerHost,
+ ?MODULE, on_user_online, 75),
+ ejabberd_hooks:delete(c2s_terminated, ServerHost,
?MODULE, on_user_offline, 75),
ejabberd_hooks:delete(disco_local_identity, ServerHost,
?MODULE, disco_local_identity, 75),
@@ -902,20 +795,17 @@ terminate(_Reason,
?MODULE, remove_user, 50),
ejabberd_hooks:delete(c2s_handle_info, ServerHost,
?MODULE, c2s_handle_info, 50),
- gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO),
- gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS),
- gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_PUBSUB),
- gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_PUBSUB_OWNER),
- gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_VCARD),
- gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_COMMANDS),
- case whereis(gen_mod:get_module_proc(ServerHost, ?LOOPNAME)) of
- undefined ->
- ?ERROR_MSG("~s process is dead, pubsub was broken", [?LOOPNAME]);
- Pid ->
- Pid ! stop
- end,
- terminate_plugins(Host, ServerHost, Plugins, TreePlugin),
- ejabberd_router:unregister_route(Host).
+ lists:foreach(
+ fun(Host) ->
+ gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO),
+ gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS),
+ gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_PUBSUB),
+ gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_PUBSUB_OWNER),
+ gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_VCARD),
+ gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_COMMANDS),
+ terminate_plugins(Host, ServerHost, Plugins, TreePlugin),
+ ejabberd_router:unregister_route(Host)
+ end, Hosts).
%%--------------------------------------------------------------------
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
@@ -1723,7 +1613,7 @@ delete_node(Host, Node, Owner) ->
%%<li>The node does not support subscriptions.</li>
%%<li>The node does not exist.</li>
%%</ul>
--spec subscribe_node(host(), binary(), jid(), binary(), [{binary(), [binary()]}]) ->
+-spec subscribe_node(host(), binary(), jid(), jid(), [{binary(), [binary()]}]) ->
{result, pubsub()} | {error, stanza_error()}.
subscribe_node(Host, Node, From, JID, Configuration) ->
SubModule = subscription_plugin(Host),
@@ -1795,7 +1685,7 @@ subscribe_node(Host, Node, From, JID, Configuration) ->
Nidx = TNode#pubsub_node.id,
Type = TNode#pubsub_node.type,
Options = TNode#pubsub_node.options,
- send_items(Host, Node, Nidx, Type, Options, Subscriber, last),
+ send_items(Host, Node, Nidx, Type, Options, Subscriber, 1),
ServerHost = serverhost(Host),
ejabberd_hooks:run(pubsub_subscribe_node, ServerHost,
[ServerHost, Host, Node, Subscriber, SubId]),
@@ -2067,8 +1957,6 @@ purge_node(Host, Node, Owner) ->
%% @doc <p>Return the items of a given node.</p>
%% <p>The number of items to return is limited by MaxItems.</p>
%% <p>The permission are not checked in this function.</p>
-%% @todo We probably need to check that the user doing the query has the right
-%% to read the items.
-spec get_items(host(), binary(), jid(), binary(),
binary(), [binary()], undefined | rsm_set()) ->
{result, pubsub()} | {error, stanza_error()}.
@@ -2151,38 +2039,23 @@ get_allowed_items_call(Host, Nidx, From, Type, Options, Owners, RSM) ->
{PS, RG} = get_presence_and_roster_permissions(Host, From, Owners, AccessModel, AllowedGroups),
node_call(Host, Type, get_items, [Nidx, From, AccessModel, PS, RG, undefined, RSM]).
-get_last_items(Host, Type, Nidx, LJID, Count) ->
+get_last_items(Host, Type, Nidx, LJID, 1) ->
+ case get_cached_item(Host, Nidx) of
+ undefined ->
+ case node_action(Host, Type, get_last_items, [Nidx, LJID, 1]) of
+ {result, Items} -> Items;
+ _ -> []
+ end;
+ LastItem ->
+ [LastItem]
+ end;
+get_last_items(Host, Type, Nidx, LJID, Count) when Count > 1 ->
case node_action(Host, Type, get_last_items, [Nidx, LJID, Count]) of
{result, Items} -> Items;
_ -> []
- end.
-
-%% @doc <p>Resend the items of a node to the user.</p>
-%% @todo use cache-last-item feature
-send_items(Host, Node, Nidx, Type, Options, LJID, last) ->
- case get_last_items(Host, Type, Nidx, LJID, 1) of
- [LastItem] ->
- Stanza = items_event_stanza(Node, Options, [LastItem]),
- dispatch_items(Host, LJID, Node, Stanza);
- _ ->
- ok
end;
-send_items(Host, Node, Nidx, Type, Options, LJID, Number) when Number > 0 ->
- Stanza = items_event_stanza(Node, Options, get_last_items(Host, Type, Nidx, Number, LJID)),
- dispatch_items(Host, LJID, Node, Stanza);
-send_items(Host, Node, _Nidx, _Type, Options, LJID, _) ->
- Stanza = items_event_stanza(Node, Options, []),
- dispatch_items(Host, LJID, Node, Stanza).
-
-dispatch_items({FromU, FromS, FromR}, To, Node, Stanza) ->
- SenderResource = user_resource(FromU, FromS, FromR),
- ejabberd_sm:route(jid:make(FromU, FromS, SenderResource),
- {send_filtered, {pep_message, <<((Node))/binary, "+notify">>},
- jid:make(FromU, FromS), jid:make(To),
- Stanza});
-dispatch_items(From, To, _Node, Stanza) ->
- ejabberd_router:route(
- xmpp:set_from_to(Stanza, service_jid(From), jid:make(To))).
+get_last_items(_Host, _Type, _Nidx, _LJID, _Count) ->
+ [].
%% @doc <p>Return the list of affiliations as an XMPP response.</p>
-spec get_affiliations(host(), binary(), jid(), [binary()]) ->
@@ -2529,14 +2402,15 @@ get_subscriptions_for_send_last(Host, PType, sql, JID, LJID, BJID) ->
{result, Subs} = node_action(Host, PType,
get_entity_subscriptions_for_send_last,
[Host, JID]),
- [{Node, Sub, SubId, SubJID}
+ [{Node, SubId, SubJID}
|| {Node, Sub, SubId, SubJID} <- Subs,
Sub =:= subscribed, (SubJID == LJID) or (SubJID == BJID)];
+ % sql version already filter result by on_sub_and_presence
get_subscriptions_for_send_last(Host, PType, _, JID, LJID, BJID) ->
{result, Subs} = node_action(Host, PType,
get_entity_subscriptions,
[Host, JID]),
- [{Node, Sub, SubId, SubJID}
+ [{Node, SubId, SubJID}
|| {Node, Sub, SubId, SubJID} <- Subs,
Sub =:= subscribed, (SubJID == LJID) or (SubJID == BJID),
match_option(Node, send_last_published_item, on_sub_and_presence)].
@@ -2925,8 +2799,9 @@ get_options_for_subs(Host, Nidx, Subs, true) ->
broadcast_stanza(Host, _Node, _Nidx, _Type, NodeOptions, SubsByDepth, NotifyType, BaseStanza, SHIM) ->
NotificationType = get_option(NodeOptions, notification_type, headline),
BroadcastAll = get_option(NodeOptions, broadcast_all_resources), %% XXX this is not standard, but usefull
- From = service_jid(Host),
- Stanza = add_message_type(BaseStanza, NotificationType),
+ Stanza = add_message_type(
+ xmpp:set_from(BaseStanza, service_jid(Host)),
+ NotificationType),
%% Handles explicit subscriptions
SubIDsByJID = subscribed_nodes_by_jid(NotifyType, SubsByDepth),
lists:foreach(fun ({LJID, _NodeName, SubIDs}) ->
@@ -2949,7 +2824,7 @@ broadcast_stanza(Host, _Node, _Nidx, _Type, NodeOptions, SubsByDepth, NotifyType
end,
lists:foreach(fun(To) ->
ejabberd_router:route(
- xmpp:set_from_to(StanzaToSend, From, jid:make(To)))
+ xmpp:set_to(StanzaToSend, jid:make(To)))
end, LJIDs)
end, SubIDsByJID).
@@ -2958,55 +2833,142 @@ broadcast_stanza({LUser, LServer, LResource}, Publisher, Node, Nidx, Type, NodeO
%% Handles implicit presence subscriptions
SenderResource = user_resource(LUser, LServer, LResource),
NotificationType = get_option(NodeOptions, notification_type, headline),
- Stanza = add_message_type(BaseStanza, NotificationType),
+ Stanza = add_message_type(
+ xmpp:set_from(BaseStanza, jid:make(LUser, LServer)),
+ NotificationType),
%% set the from address on the notification to the bare JID of the account owner
%% Also, add "replyto" if entity has presence subscription to the account owner
%% See XEP-0163 1.1 section 4.3.1
ejabberd_sm:route(jid:make(LUser, LServer, SenderResource),
{pep_message, <<((Node))/binary, "+notify">>,
- jid:make(LUser, LServer),
add_extended_headers(
Stanza, extended_headers([Publisher]))});
broadcast_stanza(Host, _Publisher, Node, Nidx, Type, NodeOptions, SubsByDepth, NotifyType, BaseStanza, SHIM) ->
broadcast_stanza(Host, Node, Nidx, Type, NodeOptions, SubsByDepth, NotifyType, BaseStanza, SHIM).
-spec c2s_handle_info(ejabberd_c2s:state(), term()) -> ejabberd_c2s:state().
-c2s_handle_info(#{server := Server} = C2SState,
- {pep_message, Feature, From, Packet}) ->
- LServer = jid:nameprep(Server),
- lists:foreach(
- fun({USR, Caps}) ->
- Features = mod_caps:get_features(LServer, Caps),
- case lists:member(Feature, Features) of
- true ->
- To = jid:make(USR),
- NewPacket = xmpp:set_from_to(Packet, From, To),
- ejabberd_router:route(NewPacket);
- false ->
- ok
- end
- end, mod_caps:list_features(C2SState)),
+c2s_handle_info(#{lserver := LServer} = C2SState,
+ {pep_message, Feature, Packet}) ->
+ [maybe_send_pep_stanza(LServer, USR, Caps, Feature, Packet)
+ || {USR, Caps} <- mod_caps:list_features(C2SState)],
{stop, C2SState};
-c2s_handle_info(#{server := Server} = C2SState,
- {send_filtered, {pep_message, Feature}, From, To, Packet}) ->
- LServer = jid:nameprep(Server),
- case mod_caps:get_user_caps(To, C2SState) of
- {ok, Caps} ->
- Features = mod_caps:get_features(LServer, Caps),
- case lists:member(Feature, Features) of
- true ->
- NewPacket = xmpp:set_from_to(Packet, From, To),
- ejabberd_router:route(NewPacket);
- false ->
- ok
- end;
- error ->
- ok
+c2s_handle_info(#{lserver := LServer} = C2SState,
+ {pep_message, Feature, Packet, USR}) ->
+ case mod_caps:get_user_caps(USR, C2SState) of
+ {ok, Caps} -> maybe_send_pep_stanza(LServer, USR, Caps, Feature, Packet);
+ error -> ok
end,
{stop, C2SState};
c2s_handle_info(C2SState, _) ->
C2SState.
+send_items(Host, Node, Nidx, Type, Options, LJID, Number) ->
+ send_items(Host, Node, Nidx, Type, Options, Host, LJID, LJID, Number).
+send_items(Host, Node, Nidx, Type, Options, Publisher, SubLJID, ToLJID, Number) ->
+ case get_last_items(Host, Type, Nidx, SubLJID, Number) of
+ [] ->
+ ok;
+ Items ->
+ Stanza = items_event_stanza(Node, Options, Items),
+ send_stanza(Publisher, ToLJID, Node, Stanza)
+ end.
+
+send_stanza({LUser, LServer, _} = Publisher, USR, Node, BaseStanza) ->
+ Stanza = xmpp:set_from(BaseStanza, jid:make(LUser, LServer)),
+ USRs = case USR of
+ {PUser, PServer, <<>>} ->
+ [{PUser, PServer, PRessource}
+ || PRessource <- user_resources(PUser, PServer)];
+ _ ->
+ [USR]
+ end,
+ [ejabberd_sm:route(jid:make(Publisher),
+ {pep_message, <<((Node))/binary, "+notify">>,
+ add_extended_headers(
+ Stanza, extended_headers([Publisher])),
+ To}) || To <- USRs];
+send_stanza(Host, USR, _Node, Stanza) ->
+ ejabberd_router:route(
+ xmpp:set_from_to(Stanza, service_jid(Host), jid:make(USR))).
+
+maybe_send_pep_stanza(LServer, USR, Caps, Feature, Packet) ->
+ Features = mod_caps:get_features(LServer, Caps),
+ case lists:member(Feature, Features) of
+ true ->
+ ejabberd_router:route(xmpp:set_to(Packet, jid:make(USR)));
+ false ->
+ ok
+ end.
+
+send_last_items(JID) ->
+ ServerHost = JID#jid.lserver,
+ Host = host(ServerHost),
+ DBType = config(ServerHost, db_type),
+ LJID = jid:tolower(JID),
+ BJID = jid:remove_resource(LJID),
+ lists:foreach(
+ fun(PType) ->
+ Subs = get_subscriptions_for_send_last(Host, PType, DBType, JID, LJID, BJID),
+ lists:foreach(
+ fun({#pubsub_node{nodeid = {_, Node}, type = Type, id = Nidx,
+ options = Options}, _, SubJID})
+ when Type == PType->
+ send_items(Host, Node, Nidx, PType, Options, Host, SubJID, LJID, 1);
+ (_) ->
+ ok
+ end,
+ lists:usort(Subs))
+ end, config(ServerHost, plugins)).
+% pep_from_offline hack can not work anymore, as sender c2s does not
+% exists when sender is offline, so we can't get match receiver caps
+% does it make sens to send PEP from an offline contact anyway ?
+% case config(ServerHost, ignore_pep_from_offline) of
+% false ->
+% Roster = ejabberd_hooks:run_fold(roster_get, ServerHost, [],
+% [{JID#jid.luser, ServerHost}]),
+% lists:foreach(
+% fun(#roster{jid = {U, S, R}, subscription = Sub})
+% when Sub == both orelse Sub == from,
+% S == ServerHost ->
+% case user_resources(U, S) of
+% [] -> send_last_pep(jid:make(U, S, R), JID);
+% _ -> ok %% this is already handled by presence probe
+% end;
+% (_) ->
+% ok %% we can not do anything in any cases
+% end, Roster);
+% true ->
+% ok
+% end.
+send_last_pep(From, To) ->
+ ServerHost = From#jid.lserver,
+ Host = host(ServerHost),
+ Publisher = jid:tolower(From),
+ Owner = jid:remove_resource(Publisher),
+ lists:foreach(
+ fun(#pubsub_node{nodeid = {_, Node}, type = Type, id = Nidx, options = Options}) ->
+ case match_option(Options, send_last_published_item, on_sub_and_presence) of
+ true ->
+ LJID = jid:tolower(To),
+ Subscribed = case get_option(Options, access_model) of
+ open -> true;
+ presence -> true;
+ whitelist -> false; % subscribers are added manually
+ authorize -> false; % likewise
+ roster ->
+ Grps = get_option(Options, roster_groups_allowed, []),
+ {OU, OS, _} = Owner,
+ element(2, get_roster_info(OU, OS, LJID, Grps))
+ end,
+ if Subscribed -> send_items(Owner, Node, Nidx, Type, Options, Publisher, LJID, LJID, 1);
+ true -> ok
+ end;
+ _ ->
+ ok
+ end
+ end,
+ tree_action(Host, get_nodes, [Owner, From])).
+
subscribed_nodes_by_jid(NotifyType, SubsByDepth) ->
NodesToDeliver = fun (Depth, Node, Subs, Acc) ->
NodeName = case Node#pubsub_node.nodeid of
@@ -3179,9 +3141,6 @@ node_owners_call(_Host, _Type, _Nidx, Owners) ->
%% @doc <p>Return the maximum number of items for a given node.</p>
%% <p>Unlimited means that there is no limit in the number of items that can
%% be stored.</p>
-%% @todo In practice, the current data structure means that we cannot manage
-%% millions of items on a given node. This should be addressed in a new
-%% version.
-spec max_items(host(), [{atom(), any()}]) -> non_neg_integer().
max_items(Host, Options) ->
case get_option(Options, persist_items) of
@@ -3785,14 +3744,6 @@ subid_shim(SubIds) ->
extended_headers(Jids) ->
[#address{type = replyto, jid = Jid} || Jid <- Jids].
--spec on_user_offline(ejabberd_sm:sid(), jid(), ejabberd_sm:info()) -> ok.
-on_user_offline(_, JID, _) ->
- {User, Server, Resource} = jid:tolower(JID),
- case user_resources(User, Server) of
- [] -> purge_offline({User, Server, Resource});
- _ -> ok
- end.
-
-spec purge_offline(ljid()) -> ok.
purge_offline(LJID) ->
Host = host(element(2, LJID)),
@@ -3833,7 +3784,7 @@ purge_offline(LJID) ->
end
end, lists:usort(lists:flatten(Affs)));
{Error, _} ->
- ?DEBUG("on_user_offline ~p", [Error])
+ ?ERROR_MSG("can not purge offline: ~p", [Error])
end.
-spec purge_offline(host(), ljid(), binary()) -> ok | {error, stanza_error()}.
@@ -3845,13 +3796,13 @@ purge_offline(Host, LJID, Node) ->
{result, {[], _}} ->
ok;
{result, {Items, _}} ->
- {User, Server, _} = LJID,
+ {User, Server, Resource} = LJID,
PublishModel = get_option(Options, publish_model),
ForceNotify = get_option(Options, notify_retract),
{_, NodeId} = Node#pubsub_node.nodeid,
lists:foreach(fun
- (#pubsub_item{itemid = {ItemId, _}, modification = {_, {U, S, _}}})
- when (U == User) and (S == Server) ->
+ (#pubsub_item{itemid = {ItemId, _}, modification = {_, {U, S, R}}})
+ when (U == User) and (S == Server) and (R == Resource) ->
case node_action(Host, Type, delete_item, [Nidx, {U, S, <<>>}, PublishModel, ItemId]) of
{result, {_, broadcast}} ->
broadcast_retract_items(Host, NodeId, Nidx, Type, Options, [ItemId], ForceNotify),
@@ -3871,9 +3822,14 @@ purge_offline(Host, LJID, Node) ->
Error
end.
+export(Server) ->
+ pubsub_db_sql:export(Server).
+
mod_opt_type(access_createnode) -> fun acl:access_rules_validator/1;
mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
mod_opt_type(host) -> fun iolist_to_binary/1;
+mod_opt_type(hosts) ->
+ fun (L) -> lists:map(fun iolist_to_binary/1, L) end;
mod_opt_type(ignore_pep_from_offline) ->
fun (A) when is_boolean(A) -> A end;
mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
@@ -3892,7 +3848,7 @@ mod_opt_type(pep_mapping) ->
mod_opt_type(plugins) ->
fun (A) when is_list(A) -> A end;
mod_opt_type(_) ->
- [access_createnode, db_type, host,
+ [access_createnode, db_type, host, hosts,
ignore_pep_from_offline, iqdisc, last_item_cache,
max_items_node, nodetree, pep_mapping, plugins,
max_subscriptions_node, default_node_config].
diff --git a/src/mod_push.erl b/src/mod_push.erl
new file mode 100644
index 000000000..2ca0bf525
--- /dev/null
+++ b/src/mod_push.erl
@@ -0,0 +1,596 @@
+%%%----------------------------------------------------------------------
+%%% File : mod_push.erl
+%%% Author : Holger Weiss <holger@zedat.fu-berlin.de>
+%%% Purpose : Push Notifications (XEP-0357)
+%%% Created : 15 Jul 2017 by Holger Weiss <holger@zedat.fu-berlin.de>
+%%%
+%%%
+%%% ejabberd, Copyright (C) 2017 ProcessOne
+%%%
+%%% This program is free software; you can redistribute it and/or
+%%% modify it under the terms of the GNU General Public License as
+%%% published by the Free Software Foundation; either version 2 of the
+%%% License, or (at your option) any later version.
+%%%
+%%% This program is distributed in the hope that it will be useful,
+%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
+%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%%% General Public License for more details.
+%%%
+%%% You should have received a copy of the GNU General Public License along
+%%% with this program; if not, write to the Free Software Foundation, Inc.,
+%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+%%%
+%%%----------------------------------------------------------------------
+
+-module(mod_push).
+-author('holger@zedat.fu-berlin.de').
+-protocol({xep, 357, '0.2'}).
+
+-behavior(gen_mod).
+
+%% gen_mod callbacks.
+-export([start/2, stop/1, reload/3, mod_opt_type/1, depends/2]).
+
+%% ejabberd_hooks callbacks.
+-export([disco_sm_features/5, c2s_session_pending/1, c2s_copy_session/2,
+ c2s_handle_cast/2, c2s_stanza/3, mam_message/6, offline_message/1,
+ remove_user/2]).
+
+%% gen_iq_handler callback.
+-export([process_iq/1]).
+
+%% ejabberd command.
+-export([get_commands_spec/0, delete_old_sessions/1]).
+
+%% API (used by mod_push_keepalive).
+-export([notify/1, notify/3, notify/5]).
+
+-include("ejabberd.hrl").
+-include("ejabberd_commands.hrl").
+-include("logger.hrl").
+-include("xmpp.hrl").
+
+-define(PUSH_CACHE, push_cache).
+
+-type c2s_state() :: ejabberd_c2s:state().
+-type timestamp() :: erlang:timestamp().
+-type push_session() :: {timestamp(), ljid(), binary(), xdata()}.
+
+-callback init(binary(), gen_mod:opts())
+ -> any().
+-callback store_session(binary(), binary(), timestamp(), jid(), binary(),
+ xdata())
+ -> {ok, push_session()} | error.
+-callback lookup_session(binary(), binary(), jid(), binary())
+ -> {ok, push_session()} | error.
+-callback lookup_session(binary(), binary(), timestamp())
+ -> {ok, push_session()} | error.
+-callback lookup_sessions(binary(), binary(), jid())
+ -> {ok, [push_session()]} | error.
+-callback lookup_sessions(binary(), binary())
+ -> {ok, [push_session()]} | error.
+-callback lookup_sessions(binary())
+ -> {ok, [push_session()]} | error.
+-callback delete_session(binary(), binary(), timestamp())
+ -> ok | error.
+-callback delete_old_sessions(binary() | global, erlang:timestamp())
+ -> any().
+-callback use_cache(binary())
+ -> boolean().
+-callback cache_nodes(binary())
+ -> [node()].
+
+-optional_callbacks([use_cache/1, cache_nodes/1]).
+
+%%--------------------------------------------------------------------
+%% gen_mod callbacks.
+%%--------------------------------------------------------------------
+-spec start(binary(), gen_mod:opts()) -> ok.
+start(Host, Opts) ->
+ IQDisc = gen_mod:get_opt(iqdisc, Opts, gen_iq_handler:iqdisc(Host)),
+ Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
+ Mod:init(Host, Opts),
+ init_cache(Mod, Host, Opts),
+ register_iq_handlers(Host, IQDisc),
+ register_hooks(Host),
+ ejabberd_commands:register_commands(get_commands_spec()).
+
+-spec stop(binary()) -> ok.
+stop(Host) ->
+ unregister_hooks(Host),
+ unregister_iq_handlers(Host),
+ ejabberd_commands:unregister_commands(get_commands_spec()).
+
+-spec reload(binary(), gen_mod:opts(), gen_mod:opts()) -> ok.
+reload(Host, NewOpts, OldOpts) ->
+ NewMod = gen_mod:db_mod(Host, NewOpts, ?MODULE),
+ OldMod = gen_mod:db_mod(Host, OldOpts, ?MODULE),
+ if NewMod /= OldMod ->
+ NewMod:init(Host, NewOpts);
+ true ->
+ ok
+ end,
+ case gen_mod:is_equal_opt(iqdisc, NewOpts, OldOpts,
+ gen_iq_handler:iqdisc(Host)) of
+ {false, IQDisc, _} ->
+ register_iq_handlers(Host, IQDisc);
+ true ->
+ ok
+ end.
+
+-spec depends(binary(), gen_mod:opts()) -> [{module(), hard | soft}].
+depends(_Host, _Opts) ->
+ [].
+
+-spec mod_opt_type(atom()) -> fun((term()) -> term()) | [atom()].
+mod_opt_type(db_type) ->
+ fun(T) -> ejabberd_config:v_db(?MODULE, T) 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_opt_type(O) when O == use_cache; O == cache_missed ->
+ fun (B) when is_boolean(B) -> B end;
+mod_opt_type(iqdisc) ->
+ fun gen_iq_handler:check_type/1;
+mod_opt_type(_) ->
+ [db_type, cache_life_time, cache_size, use_cache, cache_missed, iqdisc].
+
+%%--------------------------------------------------------------------
+%% ejabberd command callback.
+%%--------------------------------------------------------------------
+-spec get_commands_spec() -> [ejabberd_commands()].
+get_commands_spec() ->
+ [#ejabberd_commands{name = delete_old_push_sessions, tags = [purge],
+ desc = "Remove push sessions older than DAYS",
+ module = ?MODULE, function = delete_old_sessions,
+ args = [{days, integer}],
+ result = {res, rescode}}].
+
+-spec delete_old_sessions(non_neg_integer()) -> ok | any().
+delete_old_sessions(Days) ->
+ CurrentTime = p1_time_compat:system_time(micro_seconds),
+ Diff = Days * 24 * 60 * 60 * 1000000,
+ TimeStamp = misc:usec_to_now(CurrentTime - Diff),
+ DBTypes = lists:usort(
+ lists:map(
+ fun(Host) ->
+ case gen_mod:db_type(Host, ?MODULE) of
+ sql -> {sql, Host};
+ Other -> {Other, global}
+ end
+ end, ?MYHOSTS)),
+ Results = lists:map(
+ fun({DBType, Host}) ->
+ Mod = gen_mod:db_mod(DBType, ?MODULE),
+ Mod:delete_old_sessions(Host, TimeStamp)
+ end, DBTypes),
+ ets_cache:clear(?PUSH_CACHE, ejabberd_cluster:get_nodes()),
+ case lists:filter(fun(Res) -> Res /= ok end, Results) of
+ [] ->
+ ?INFO_MSG("Deleted push sessions older than ~B days", [Days]),
+ ok;
+ [NotOk | _] ->
+ ?ERROR_MSG("Error while deleting old push sessions: ~p", [NotOk]),
+ NotOk
+ end.
+
+%%--------------------------------------------------------------------
+%% Register/unregister hooks.
+%%--------------------------------------------------------------------
+-spec register_hooks(binary()) -> ok.
+register_hooks(Host) ->
+ ejabberd_hooks:add(disco_sm_features, Host, ?MODULE,
+ disco_sm_features, 50),
+ ejabberd_hooks:add(c2s_session_pending, Host, ?MODULE,
+ c2s_session_pending, 50),
+ ejabberd_hooks:add(c2s_copy_session, Host, ?MODULE,
+ c2s_copy_session, 50),
+ ejabberd_hooks:add(c2s_handle_cast, Host, ?MODULE,
+ c2s_handle_cast, 50),
+ ejabberd_hooks:add(c2s_handle_send, Host, ?MODULE,
+ c2s_stanza, 50),
+ ejabberd_hooks:add(store_mam_message, Host, ?MODULE,
+ mam_message, 50),
+ ejabberd_hooks:add(offline_message_hook, Host, ?MODULE,
+ offline_message, 50),
+ ejabberd_hooks:add(remove_user, Host, ?MODULE,
+ remove_user, 50).
+
+-spec unregister_hooks(binary()) -> ok.
+unregister_hooks(Host) ->
+ ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE,
+ disco_sm_features, 50),
+ ejabberd_hooks:delete(c2s_session_pending, Host, ?MODULE,
+ c2s_session_pending, 50),
+ ejabberd_hooks:delete(c2s_copy_session, Host, ?MODULE,
+ c2s_copy_session, 50),
+ ejabberd_hooks:delete(c2s_handle_cast, Host, ?MODULE,
+ c2s_handle_cast, 50),
+ ejabberd_hooks:delete(c2s_handle_send, Host, ?MODULE,
+ c2s_stanza, 50),
+ ejabberd_hooks:delete(store_mam_message, Host, ?MODULE,
+ mam_message, 50),
+ ejabberd_hooks:delete(offline_message_hook, Host, ?MODULE,
+ offline_message, 50),
+ ejabberd_hooks:delete(remove_user, Host, ?MODULE,
+ remove_user, 50).
+
+%%--------------------------------------------------------------------
+%% Service discovery.
+%%--------------------------------------------------------------------
+-spec disco_sm_features(empty | {result, [binary()]} | {error, stanza_error()},
+ jid(), jid(), binary(), binary())
+ -> {result, [binary()]} | {error, stanza_error()}.
+disco_sm_features(empty, From, To, Node, Lang) ->
+ disco_sm_features({result, []}, From, To, Node, Lang);
+disco_sm_features({result, OtherFeatures},
+ #jid{luser = U, lserver = S},
+ #jid{luser = U, lserver = S}, <<"">>, _Lang) ->
+ {result, [?NS_PUSH_0 | OtherFeatures]};
+disco_sm_features(Acc, _From, _To, _Node, _Lang) ->
+ Acc.
+
+%%--------------------------------------------------------------------
+%% IQ handlers.
+%%--------------------------------------------------------------------
+-spec register_iq_handlers(binary(), gen_iq_handler:type()) -> ok.
+register_iq_handlers(Host, IQDisc) ->
+ gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_PUSH_0,
+ ?MODULE, process_iq, IQDisc).
+
+-spec unregister_iq_handlers(binary()) -> ok.
+unregister_iq_handlers(Host) ->
+ gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_PUSH_0).
+
+-spec process_iq(iq()) -> iq().
+process_iq(#iq{type = get, lang = Lang} = IQ) ->
+ Txt = <<"Value 'get' of 'type' attribute is not allowed">>,
+ xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
+process_iq(#iq{lang = Lang, sub_els = [#push_enable{node = <<>>}]} = IQ) ->
+ Txt = <<"Enabling push without 'node' attribute is not supported">>,
+ xmpp:make_error(IQ, xmpp:err_feature_not_implemented(Txt, Lang));
+process_iq(#iq{from = #jid{lserver = LServer} = JID,
+ to = #jid{lserver = LServer},
+ sub_els = [#push_enable{jid = PushJID,
+ node = Node,
+ xdata = XData}]} = IQ) ->
+ case enable(JID, PushJID, Node, XData) of
+ ok ->
+ xmpp:make_iq_result(IQ);
+ error ->
+ xmpp:make_error(IQ, xmpp:err_internal_server_error())
+ end;
+process_iq(#iq{from = #jid{lserver = LServer} = JID,
+ to = #jid{lserver = LServer},
+ sub_els = [#push_disable{jid = PushJID,
+ node = Node}]} = IQ) ->
+ case disable(JID, PushJID, Node) of
+ ok ->
+ xmpp:make_iq_result(IQ);
+ error ->
+ xmpp:make_error(IQ, xmpp:err_item_not_found())
+ end;
+process_iq(IQ) ->
+ xmpp:make_error(IQ, xmpp:err_not_allowed()).
+
+-spec enable(jid(), jid(), binary(), xdata()) -> ok | error.
+enable(#jid{luser = LUser, lserver = LServer, lresource = LResource} = JID,
+ PushJID, Node, XData) ->
+ case ejabberd_sm:get_session_sid(LUser, LServer, LResource) of
+ {TS, PID} ->
+ case store_session(LUser, LServer, TS, PushJID, Node, XData) of
+ {ok, _} ->
+ ?INFO_MSG("Enabling push notifications for ~s",
+ [jid:encode(JID)]),
+ ejabberd_c2s:cast(PID, push_enable);
+ error ->
+ ?ERROR_MSG("Cannot enable push for ~s: database error",
+ [jid:encode(JID)]),
+ error
+ end;
+ none ->
+ ?WARNING_MSG("Cannot enable push for ~s: session not found",
+ [jid:encode(JID)]),
+ error
+ end.
+
+-spec disable(jid(), jid(), binary() | undefined) -> ok | error.
+disable(#jid{luser = LUser, lserver = LServer, lresource = LResource} = JID,
+ PushJID, Node) ->
+ case ejabberd_sm:get_session_sid(LUser, LServer, LResource) of
+ {_TS, PID} ->
+ ?INFO_MSG("Disabling push notifications for ~s",
+ [jid:encode(JID)]),
+ ejabberd_c2s:cast(PID, push_disable);
+ none ->
+ ?WARNING_MSG("Session not found while disabling push for ~s",
+ [jid:encode(JID)])
+ end,
+ if Node /= undefined ->
+ delete_session(LUser, LServer, PushJID, Node);
+ true ->
+ delete_sessions(LUser, LServer, PushJID)
+ end.
+
+%%--------------------------------------------------------------------
+%% Hook callbacks.
+%%--------------------------------------------------------------------
+-spec c2s_stanza(c2s_state(), xmpp_element() | xmlel(), term()) -> c2s_state().
+c2s_stanza(#{push_enabled := true, mgmt_state := pending} = State,
+ _Pkt, _SendResult) ->
+ notify(State),
+ State;
+c2s_stanza(State, _Pkt, _SendResult) ->
+ State.
+
+-spec mam_message(message() | drop, binary(), binary(), jid(),
+ chat | groupchat, recv | send) -> message().
+mam_message(#message{meta = #{push_notified := true}} = Pkt,
+ _LUser, _LServer, _Peer, _Type, _Dir) ->
+ Pkt;
+mam_message(#message{} = Pkt, LUser, LServer, _Peer, chat, _Dir) ->
+ case lookup_sessions(LUser, LServer) of
+ {ok, [_|_] = Clients} ->
+ case drop_online_sessions(LUser, LServer, Clients) of
+ [_|_] = Clients1 ->
+ ?DEBUG("Notifying ~s@~s of MAM message", [LUser, LServer]),
+ notify(LUser, LServer, Clients1);
+ [] ->
+ ok
+ end;
+ _ ->
+ ok
+ end,
+ xmpp:put_meta(Pkt, push_notified, true);
+mam_message(Pkt, _LUser, _LServer, _Peer, _Type, _Dir) ->
+ Pkt.
+
+-spec offline_message({any(), message()}) -> {any(), message()}.
+offline_message({_Action, #message{meta = #{push_notified := true}}} = Acc) ->
+ Acc;
+offline_message({Action, #message{to = #jid{luser = LUser,
+ lserver = LServer}} = Pkt}) ->
+ case lookup_sessions(LUser, LServer) of
+ {ok, [_|_] = Clients} ->
+ ?DEBUG("Notifying ~s@~s of offline message", [LUser, LServer]),
+ notify(LUser, LServer, Clients);
+ _ ->
+ ok
+ end,
+ {Action, xmpp:put_meta(Pkt, push_notified, true)}.
+
+-spec c2s_session_pending(c2s_state()) -> c2s_state().
+c2s_session_pending(#{push_enabled := true, mgmt_queue := Queue} = State) ->
+ case p1_queue:len(Queue) of
+ Len when Len > 0 ->
+ ?DEBUG("Notifying client of unacknowledged messages", []),
+ notify(State),
+ State;
+ 0 ->
+ State
+ end;
+c2s_session_pending(State) ->
+ State.
+
+-spec c2s_copy_session(c2s_state(), c2s_state()) -> c2s_state().
+c2s_copy_session(State, #{push_enabled := true}) ->
+ State#{push_enabled => true};
+c2s_copy_session(State, _) ->
+ State.
+
+-spec c2s_handle_cast(c2s_state(), any()) -> c2s_state() | {stop, c2s_state()}.
+c2s_handle_cast(State, push_enable) ->
+ {stop, State#{push_enabled => true}};
+c2s_handle_cast(State, push_disable) ->
+ {stop, maps:remove(push_enabled, State)};
+c2s_handle_cast(State, _Msg) ->
+ State.
+
+-spec remove_user(binary(), binary()) -> ok | error.
+remove_user(LUser, LServer) ->
+ ?INFO_MSG("Removing any push sessions of ~s@~s", [LUser, LServer]),
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ LookupFun = fun() -> Mod:lookup_sessions(LUser, LServer) end,
+ delete_sessions(LUser, LServer, LookupFun, Mod).
+
+%%--------------------------------------------------------------------
+%% Generate push notifications.
+%%--------------------------------------------------------------------
+-spec notify(c2s_state()) -> ok.
+notify(#{jid := #jid{luser = LUser, lserver = LServer}, sid := {TS, _}}) ->
+ case lookup_session(LUser, LServer, TS) of
+ {ok, Client} ->
+ notify(LUser, LServer, [Client]);
+ error ->
+ ok
+ end.
+
+-spec notify(binary(), binary(), [push_session()]) -> ok.
+notify(LUser, LServer, Clients) ->
+ lists:foreach(
+ fun({TS, PushLJID, Node, XData}) ->
+ HandleResponse = fun(#iq{type = result}) ->
+ ok;
+ (#iq{type = error}) ->
+ delete_session(LUser, LServer, TS);
+ (timeout) ->
+ ok % Hmm.
+ end,
+ notify(LServer, PushLJID, Node, XData, HandleResponse)
+ end, Clients).
+
+-spec notify(binary(), ljid(), binary(), xdata(),
+ fun((iq() | timeout) -> any())) -> ok.
+notify(LServer, PushLJID, Node, XData, HandleResponse) ->
+ From = jid:make(LServer),
+ Item = #ps_item{xml_els = [xmpp:encode(#push_notification{})]},
+ PubSub = #pubsub{publish = #ps_publish{node = Node, items = [Item]},
+ publish_options = XData},
+ IQ = #iq{type = set,
+ from = From,
+ to = jid:make(PushLJID),
+ id = randoms:get_string(),
+ sub_els = [PubSub]},
+ ejabberd_local:route_iq(IQ, HandleResponse),
+ ok.
+
+%%--------------------------------------------------------------------
+%% Internal functions.
+%%--------------------------------------------------------------------
+-spec store_session(binary(), binary(), timestamp(), jid(), binary(), xdata())
+ -> {ok, push_session()} | error.
+store_session(LUser, LServer, TS, PushJID, Node, XData) ->
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ delete_session(LUser, LServer, PushJID, Node),
+ case use_cache(Mod, LServer) of
+ true ->
+ ets_cache:delete(?PUSH_CACHE, {LUser, LServer},
+ cache_nodes(Mod, LServer)),
+ ets_cache:update(
+ ?PUSH_CACHE,
+ {LUser, LServer, TS}, {ok, {TS, PushJID, Node, XData}},
+ fun() ->
+ Mod:store_session(LUser, LServer, TS, PushJID, Node,
+ XData)
+ end, cache_nodes(Mod, LServer));
+ false ->
+ Mod:store_session(LUser, LServer, TS, PushJID, Node, XData)
+ end.
+
+-spec lookup_session(binary(), binary(), timestamp())
+ -> {ok, push_session()} | error.
+lookup_session(LUser, LServer, TS) ->
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ case use_cache(Mod, LServer) of
+ true ->
+ ets_cache:lookup(
+ ?PUSH_CACHE, {LUser, LServer, TS},
+ fun() -> Mod:lookup_session(LUser, LServer, TS) end);
+ false ->
+ Mod:lookup_session(LUser, LServer, TS)
+ end.
+
+-spec lookup_sessions(binary(), binary()) -> {ok, [push_session()]} | error.
+lookup_sessions(LUser, LServer) ->
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ case use_cache(Mod, LServer) of
+ true ->
+ ets_cache:lookup(
+ ?PUSH_CACHE, {LUser, LServer},
+ fun() -> Mod:lookup_sessions(LUser, LServer) end);
+ false ->
+ Mod:lookup_sessions(LUser, LServer)
+ end.
+
+-spec delete_session(binary(), binary(), timestamp()) -> ok | error.
+delete_session(LUser, LServer, TS) ->
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ ok = Mod:delete_session(LUser, LServer, TS),
+ case use_cache(Mod, LServer) of
+ true ->
+ ets_cache:delete(?PUSH_CACHE, {LUser, LServer},
+ cache_nodes(Mod, LServer)),
+ ets_cache:delete(?PUSH_CACHE, {LUser, LServer, TS},
+ cache_nodes(Mod, LServer));
+ false ->
+ ok
+ end.
+
+-spec delete_session(binary(), binary(), jid(), binary()) -> ok | error.
+delete_session(LUser, LServer, PushJID, Node) ->
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ case Mod:lookup_session(LUser, LServer, PushJID, Node) of
+ {ok, {TS, _, _, _}} ->
+ delete_session(LUser, LServer, TS);
+ error ->
+ error
+ end.
+
+-spec delete_sessions(binary(), binary(), jid()) -> ok | error.
+delete_sessions(LUser, LServer, PushJID) ->
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ LookupFun = fun() -> Mod:lookup_sessions(LUser, LServer, PushJID) end,
+ delete_sessions(LUser, LServer, LookupFun, Mod).
+
+-spec delete_sessions(binary(), binary(), fun(() -> ok | error), module())
+ -> ok | error.
+delete_sessions(LUser, LServer, LookupFun, Mod) ->
+ case LookupFun() of
+ {ok, Clients} ->
+ case use_cache(Mod, LServer) of
+ true ->
+ ets_cache:delete(?PUSH_CACHE, {LUser, LServer},
+ cache_nodes(Mod, LServer));
+ false ->
+ ok
+ end,
+ lists:foreach(
+ fun({TS, _, _, _}) ->
+ ok = Mod:delete_session(LUser, LServer, TS),
+ case use_cache(Mod, LServer) of
+ true ->
+ ets_cache:delete(?PUSH_CACHE,
+ {LUser, LServer, TS},
+ cache_nodes(Mod, LServer));
+ false ->
+ ok
+ end
+ end, Clients);
+ error ->
+ error
+ end.
+
+-spec drop_online_sessions(binary(), binary(), [push_session()])
+ -> [push_session()].
+drop_online_sessions(LUser, LServer, Clients) ->
+ SessIDs = ejabberd_sm:get_session_sids(LUser, LServer),
+ [Client || {TS, _, _, _} = Client <- Clients,
+ lists:keyfind(TS, 1, SessIDs) == false].
+
+%%--------------------------------------------------------------------
+%% Caching.
+%%--------------------------------------------------------------------
+-spec init_cache(module(), binary(), gen_mod:opts()) -> ok.
+init_cache(Mod, Host, Opts) ->
+ case use_cache(Mod, Host) of
+ true ->
+ CacheOpts = cache_opts(Host, Opts),
+ ets_cache:new(?PUSH_CACHE, CacheOpts);
+ false ->
+ ets_cache:delete(?PUSH_CACHE)
+ end.
+
+-spec cache_opts(binary(), gen_mod:opts()) -> [proplists:property()].
+cache_opts(Host, Opts) ->
+ MaxSize = gen_mod:get_opt(
+ cache_size, Opts,
+ ejabberd_config:cache_size(Host)),
+ CacheMissed = gen_mod:get_opt(
+ cache_missed, Opts,
+ ejabberd_config:cache_missed(Host)),
+ LifeTime = case gen_mod:get_opt(
+ cache_life_time, Opts,
+ ejabberd_config:cache_life_time(Host)) of
+ infinity -> infinity;
+ I -> timer:seconds(I)
+ end,
+ [{max_size, MaxSize}, {cache_missed, CacheMissed}, {life_time, LifeTime}].
+
+-spec use_cache(module(), binary()) -> boolean().
+use_cache(Mod, Host) ->
+ case erlang:function_exported(Mod, use_cache, 1) of
+ true -> Mod:use_cache(Host);
+ false ->
+ gen_mod:get_module_opt(
+ Host, ?MODULE, use_cache,
+ ejabberd_config:use_cache(Host))
+ end.
+
+-spec cache_nodes(module(), binary()) -> [node()].
+cache_nodes(Mod, Host) ->
+ case erlang:function_exported(Mod, cache_nodes, 1) of
+ true -> Mod:cache_nodes(Host);
+ false -> ejabberd_cluster:get_nodes()
+ end.
diff --git a/src/mod_push_keepalive.erl b/src/mod_push_keepalive.erl
new file mode 100644
index 000000000..bde62fc67
--- /dev/null
+++ b/src/mod_push_keepalive.erl
@@ -0,0 +1,236 @@
+%%%----------------------------------------------------------------------
+%%% File : mod_push_keepalive.erl
+%%% Author : Holger Weiss <holger@zedat.fu-berlin.de>
+%%% Purpose : Keep pending XEP-0198 sessions alive with XEP-0357
+%%% Created : 15 Jul 2017 by Holger Weiss <holger@zedat.fu-berlin.de>
+%%%
+%%%
+%%% ejabberd, Copyright (C) 2017 ProcessOne
+%%%
+%%% This program is free software; you can redistribute it and/or
+%%% modify it under the terms of the GNU General Public License as
+%%% published by the Free Software Foundation; either version 2 of the
+%%% License, or (at your option) any later version.
+%%%
+%%% This program is distributed in the hope that it will be useful,
+%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
+%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%%% General Public License for more details.
+%%%
+%%% You should have received a copy of the GNU General Public License along
+%%% with this program; if not, write to the Free Software Foundation, Inc.,
+%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+%%%
+%%%----------------------------------------------------------------------
+
+-module(mod_push_keepalive).
+-author('holger@zedat.fu-berlin.de').
+
+-behavior(gen_mod).
+
+%% gen_mod callbacks.
+-export([start/2, stop/1, reload/3, mod_opt_type/1, depends/2]).
+
+%% ejabberd_hooks callbacks.
+-export([c2s_session_pending/1, c2s_session_resumed/1, c2s_copy_session/2,
+ c2s_handle_cast/2, c2s_handle_info/2, c2s_stanza/3]).
+
+-include("logger.hrl").
+-include("xmpp.hrl").
+
+-define(PUSH_BEFORE_TIMEOUT_SECS, 120).
+
+-type c2s_state() :: ejabberd_c2s:state().
+
+%%--------------------------------------------------------------------
+%% gen_mod callbacks.
+%%--------------------------------------------------------------------
+-spec start(binary(), gen_mod:opts()) -> ok.
+start(Host, Opts) ->
+ case gen_mod:get_opt(wake_on_start, Opts, false) of
+ true ->
+ wake_all(Host);
+ false ->
+ ok
+ end,
+ register_hooks(Host).
+
+-spec stop(binary()) -> ok.
+stop(Host) ->
+ unregister_hooks(Host).
+
+-spec reload(binary(), gen_mod:opts(), gen_mod:opts()) -> ok.
+reload(Host, NewOpts, OldOpts) ->
+ case gen_mod:is_equal_opt(wake_on_start, NewOpts, OldOpts, false) of
+ {false, true, _} ->
+ wake_all(Host);
+ _ ->
+ ok
+ end,
+ ok.
+
+-spec depends(binary(), gen_mod:opts()) -> [{module(), hard | soft}].
+depends(_Host, _Opts) ->
+ [{mod_push, hard},
+ {mod_client_state, soft},
+ {mod_stream_mgmt, soft}].
+
+-spec mod_opt_type(atom()) -> fun((term()) -> term()) | [atom()].
+mod_opt_type(resume_timeout) ->
+ fun(I) when is_integer(I), I >= 0 -> I;
+ (undefined) -> undefined
+ end;
+mod_opt_type(wake_on_start) ->
+ fun (B) when is_boolean(B) -> B end;
+mod_opt_type(wake_on_timeout) ->
+ fun (B) when is_boolean(B) -> B 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_opt_type(O) when O == use_cache; O == cache_missed ->
+ fun (B) when is_boolean(B) -> B end;
+mod_opt_type(_) ->
+ [resume_timeout, wake_on_start, wake_on_timeout, db_type, cache_life_time,
+ cache_size, use_cache, cache_missed, iqdisc].
+
+%%--------------------------------------------------------------------
+%% Register/unregister hooks.
+%%--------------------------------------------------------------------
+-spec register_hooks(binary()) -> ok.
+register_hooks(Host) ->
+ ejabberd_hooks:add(c2s_session_pending, Host, ?MODULE,
+ c2s_session_pending, 50),
+ ejabberd_hooks:add(c2s_session_resumed, Host, ?MODULE,
+ c2s_session_resumed, 50),
+ ejabberd_hooks:add(c2s_copy_session, Host, ?MODULE,
+ c2s_copy_session, 50),
+ ejabberd_hooks:add(c2s_handle_cast, Host, ?MODULE,
+ c2s_handle_cast, 40),
+ ejabberd_hooks:add(c2s_handle_info, Host, ?MODULE,
+ c2s_handle_info, 50),
+ ejabberd_hooks:add(c2s_handle_send, Host, ?MODULE,
+ c2s_stanza, 50).
+
+-spec unregister_hooks(binary()) -> ok.
+unregister_hooks(Host) ->
+ ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE,
+ disco_sm_features, 50),
+ ejabberd_hooks:delete(c2s_session_pending, Host, ?MODULE,
+ c2s_session_pending, 50),
+ ejabberd_hooks:delete(c2s_session_resumed, Host, ?MODULE,
+ c2s_session_resumed, 50),
+ ejabberd_hooks:delete(c2s_copy_session, Host, ?MODULE,
+ c2s_copy_session, 50),
+ ejabberd_hooks:delete(c2s_handle_cast, Host, ?MODULE,
+ c2s_handle_cast, 40),
+ ejabberd_hooks:delete(c2s_handle_info, Host, ?MODULE,
+ c2s_handle_info, 50),
+ ejabberd_hooks:delete(c2s_handle_send, Host, ?MODULE,
+ c2s_stanza, 50).
+
+%%--------------------------------------------------------------------
+%% Hook callbacks.
+%%--------------------------------------------------------------------
+-spec c2s_stanza(c2s_state(), xmpp_element() | xmlel(), term()) -> c2s_state().
+c2s_stanza(#{push_enabled := true, mgmt_state := pending} = State,
+ _Pkt, _SendResult) ->
+ maybe_restore_resume_timeout(State);
+c2s_stanza(State, _Pkt, _SendResult) ->
+ State.
+
+-spec c2s_session_pending(c2s_state()) -> c2s_state().
+c2s_session_pending(#{push_enabled := true, mgmt_queue := Queue} = State) ->
+ case p1_queue:len(Queue) of
+ 0 ->
+ State1 = maybe_adjust_resume_timeout(State),
+ maybe_start_wakeup_timer(State1);
+ _ ->
+ State
+ end;
+c2s_session_pending(State) ->
+ State.
+
+-spec c2s_session_resumed(c2s_state()) -> c2s_state().
+c2s_session_resumed(#{push_enabled := true} = State) ->
+ maybe_restore_resume_timeout(State);
+c2s_session_resumed(State) ->
+ State.
+
+-spec c2s_copy_session(c2s_state(), c2s_state()) -> c2s_state().
+c2s_copy_session(State, #{push_enabled := true,
+ push_resume_timeout := ResumeTimeout,
+ push_wake_on_timeout := WakeOnTimeout}) ->
+ State#{push_resume_timeout => ResumeTimeout,
+ push_wake_on_timeout => WakeOnTimeout};
+c2s_copy_session(State, _) ->
+ State.
+
+-spec c2s_handle_cast(c2s_state(), any()) -> c2s_state().
+c2s_handle_cast(#{lserver := LServer} = State, push_enable) ->
+ ResumeTimeout = gen_mod:get_module_opt(LServer, ?MODULE,
+ resume_timeout, 86400),
+ WakeOnTimeout = gen_mod:get_module_opt(LServer, ?MODULE,
+ wake_on_timeout, true),
+ State#{push_resume_timeout => ResumeTimeout,
+ push_wake_on_timeout => WakeOnTimeout};
+c2s_handle_cast(State, push_disable) ->
+ State1 = maps:remove(push_resume_timeout, State),
+ maps:remove(push_wake_on_timeout, State1);
+c2s_handle_cast(State, _Msg) ->
+ State.
+
+-spec c2s_handle_info(c2s_state(), any()) -> c2s_state() | {stop, c2s_state()}.
+c2s_handle_info(#{push_enabled := true, mgmt_state := pending,
+ jid := JID} = State, {timeout, _, push_keepalive}) ->
+ ?INFO_MSG("Waking ~s before session times out", [jid:encode(JID)]),
+ mod_push:notify(State),
+ {stop, State};
+c2s_handle_info(State, _) ->
+ State.
+
+%%--------------------------------------------------------------------
+%% Internal functions.
+%%--------------------------------------------------------------------
+-spec maybe_adjust_resume_timeout(c2s_state()) -> c2s_state().
+maybe_adjust_resume_timeout(#{push_resume_timeout := undefined} = State) ->
+ State;
+maybe_adjust_resume_timeout(#{push_resume_timeout := Timeout} = State) ->
+ OrigTimeout = mod_stream_mgmt:get_resume_timeout(State),
+ ?DEBUG("Adjusting resume timeout to ~B seconds", [Timeout]),
+ State1 = mod_stream_mgmt:set_resume_timeout(State, Timeout),
+ State1#{push_resume_timeout_orig => OrigTimeout}.
+
+-spec maybe_restore_resume_timeout(c2s_state()) -> c2s_state().
+maybe_restore_resume_timeout(#{push_resume_timeout_orig := Timeout} = State) ->
+ ?DEBUG("Restoring resume timeout to ~B seconds", [Timeout]),
+ State1 = mod_stream_mgmt:set_resume_timeout(State, Timeout),
+ maps:remove(push_resume_timeout_orig, State1);
+maybe_restore_resume_timeout(State) ->
+ State.
+
+-spec maybe_start_wakeup_timer(c2s_state()) -> c2s_state().
+maybe_start_wakeup_timer(#{push_wake_on_timeout := true,
+ push_resume_timeout := ResumeTimeout} = State)
+ when is_integer(ResumeTimeout), ResumeTimeout > ?PUSH_BEFORE_TIMEOUT_SECS ->
+ WakeTimeout = ResumeTimeout - ?PUSH_BEFORE_TIMEOUT_SECS,
+ ?DEBUG("Scheduling wake-up timer to fire in ~B seconds", [WakeTimeout]),
+ erlang:start_timer(timer:seconds(WakeTimeout), self(), push_keepalive),
+ State;
+maybe_start_wakeup_timer(State) ->
+ State.
+
+-spec wake_all(binary()) -> ok | error.
+wake_all(LServer) ->
+ ?INFO_MSG("Waking all push clients on ~s", [LServer]),
+ Mod = gen_mod:db_mod(LServer, mod_push),
+ case Mod:lookup_sessions(LServer) of
+ {ok, Sessions} ->
+ IgnoreResponse = fun(_) -> ok end,
+ lists:foreach(fun({_, PushLJID, Node, XData}) ->
+ mod_push:notify(LServer, PushLJID, Node,
+ XData, IgnoreResponse)
+ end, Sessions);
+ error ->
+ error
+ end.
diff --git a/src/mod_push_mnesia.erl b/src/mod_push_mnesia.erl
new file mode 100644
index 000000000..04ea8d60a
--- /dev/null
+++ b/src/mod_push_mnesia.erl
@@ -0,0 +1,204 @@
+%%%----------------------------------------------------------------------
+%%% File : mod_push_mnesia.erl
+%%% Author : Holger Weiss <holger@zedat.fu-berlin.de>
+%%% Purpose : Mnesia backend for Push Notifications (XEP-0357)
+%%% Created : 15 Jul 2017 by Holger Weiss <holger@zedat.fu-berlin.de>
+%%%
+%%%
+%%% ejabberd, Copyright (C) 2017 ProcessOne
+%%%
+%%% This program is free software; you can redistribute it and/or
+%%% modify it under the terms of the GNU General Public License as
+%%% published by the Free Software Foundation; either version 2 of the
+%%% License, or (at your option) any later version.
+%%%
+%%% This program is distributed in the hope that it will be useful,
+%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
+%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%%% General Public License for more details.
+%%%
+%%% You should have received a copy of the GNU General Public License along
+%%% with this program; if not, write to the Free Software Foundation, Inc.,
+%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+%%%
+%%%----------------------------------------------------------------------
+
+-module(mod_push_mnesia).
+-author('holger@zedat.fu-berlin.de').
+
+-behavior(mod_push).
+
+%% API
+-export([init/2, store_session/6, lookup_session/4, lookup_session/3,
+ lookup_sessions/3, lookup_sessions/2, lookup_sessions/1,
+ delete_session/3, delete_old_sessions/2]).
+
+-include_lib("stdlib/include/ms_transform.hrl").
+-include("logger.hrl").
+-include("xmpp.hrl").
+
+-record(push_session,
+ {us = {<<"">>, <<"">>} :: {binary(), binary()},
+ timestamp = p1_time_compat:timestamp() :: erlang:timestamp(),
+ service = {<<"">>, <<"">>, <<"">>} :: ljid(),
+ node = <<"">> :: binary(),
+ xdata = #xdata{} :: xdata()}).
+
+%%%-------------------------------------------------------------------
+%%% API
+%%%-------------------------------------------------------------------
+init(_Host, _Opts) ->
+ ejabberd_mnesia:create(?MODULE, push_session,
+ [{disc_only_copies, [node()]},
+ {type, bag},
+ {attributes, record_info(fields, push_session)}]).
+
+store_session(LUser, LServer, TS, PushJID, Node, XData) ->
+ US = {LUser, LServer},
+ PushLJID = jid:tolower(PushJID),
+ MaxSessions = ejabberd_sm:get_max_user_sessions(LUser, LServer),
+ F = fun() ->
+ if is_integer(MaxSessions) ->
+ enforce_max_sessions(US, MaxSessions - 1);
+ MaxSessions == infinity ->
+ ok
+ end,
+ mnesia:write(#push_session{us = US,
+ timestamp = TS,
+ service = PushLJID,
+ node = Node,
+ xdata = XData})
+ end,
+ case mnesia:transaction(F) of
+ {atomic, ok} ->
+ {ok, {TS, PushLJID, Node, XData}};
+ {aborted, E} ->
+ ?ERROR_MSG("Cannot store push session for ~s@~s: ~p",
+ [LUser, LServer, E]),
+ error
+ end.
+
+lookup_session(LUser, LServer, PushJID, Node) ->
+ PushLJID = jid:tolower(PushJID),
+ MatchSpec = ets:fun2ms(
+ fun(#push_session{us = {U, S}, service = P, node = N} = Rec)
+ when U == LUser,
+ S == LServer,
+ P == PushLJID,
+ N == Node ->
+ Rec
+ end),
+ case mnesia:dirty_select(push_session, MatchSpec) of
+ [#push_session{timestamp = TS, xdata = XData}] ->
+ {ok, {TS, PushLJID, Node, XData}};
+ _ ->
+ ?DEBUG("No push session found for ~s@~s (~p, ~s)",
+ [LUser, LServer, PushJID, Node]),
+ error
+ end.
+
+lookup_session(LUser, LServer, TS) ->
+ MatchSpec = ets:fun2ms(
+ fun(#push_session{us = {U, S}, timestamp = T} = Rec)
+ when U == LUser,
+ S == LServer,
+ T == TS ->
+ Rec
+ end),
+ case mnesia:dirty_select(push_session, MatchSpec) of
+ [#push_session{service = PushLJID, node = Node, xdata = XData}] ->
+ {ok, {TS, PushLJID, Node, XData}};
+ _ ->
+ ?DEBUG("No push session found for ~s@~s (~p)",
+ [LUser, LServer, TS]),
+ error
+ end.
+
+lookup_sessions(LUser, LServer, PushJID) ->
+ PushLJID = jid:tolower(PushJID),
+ MatchSpec = ets:fun2ms(
+ fun(#push_session{us = {U, S}, service = P, node = N} = Rec)
+ when U == LUser,
+ S == LServer,
+ P == PushLJID ->
+ Rec
+ end),
+ {ok, mnesia:dirty_select(push_session, MatchSpec)}.
+
+lookup_sessions(LUser, LServer) ->
+ Records = mnesia:dirty_read(push_session, {LUser, LServer}),
+ Clients = [{TS, PushLJID, Node, XData}
+ || #push_session{timestamp = TS,
+ service = PushLJID,
+ node = Node,
+ xdata = XData} <- Records],
+ {ok, Clients}.
+
+lookup_sessions(LServer) ->
+ MatchSpec = ets:fun2ms(
+ fun(#push_session{us = {_U, S},
+ timestamp = TS,
+ service = PushLJID,
+ node = Node,
+ xdata = XData})
+ when S == LServer ->
+ {TS, PushLJID, Node, XData}
+ end),
+ {ok, mnesia:dirty_select(push_session, MatchSpec)}.
+
+delete_session(LUser, LServer, TS) ->
+ MatchSpec = ets:fun2ms(
+ fun(#push_session{us = {U, S}, timestamp = T} = Rec)
+ when U == LUser,
+ S == LServer,
+ T == TS ->
+ Rec
+ end),
+ F = fun() ->
+ Recs = mnesia:select(push_session, MatchSpec),
+ lists:foreach(fun mnesia:delete_object/1, Recs)
+ end,
+ case mnesia:transaction(F) of
+ {atomic, ok} ->
+ ok;
+ {aborted, E} ->
+ ?ERROR_MSG("Cannot delete push seesion of ~s@~s: ~p",
+ [LUser, LServer, E]),
+ error
+ end.
+
+delete_old_sessions(_LServer, Time) ->
+ DelIfOld = fun(#push_session{timestamp = T} = Rec, ok) when T < Time ->
+ mnesia:delete_object(Rec);
+ (_Rec, ok) ->
+ ok
+ end,
+ F = fun() ->
+ mnesia:foldl(DelIfOld, ok, push_session)
+ end,
+ case mnesia:transaction(F) of
+ {atomic, ok} ->
+ ok;
+ {aborted, E} ->
+ ?ERROR_MSG("Cannot delete old push sessions: ~p", [E]),
+ error
+ end.
+
+%%--------------------------------------------------------------------
+%% Internal functions.
+%%--------------------------------------------------------------------
+-spec enforce_max_sessions({binary(), binary()}, non_neg_integer()) -> ok.
+enforce_max_sessions({U, S} = US, Max) ->
+ Recs = mnesia:wread({push_session, US}),
+ NumRecs = length(Recs),
+ if NumRecs > Max ->
+ NumOldRecs = NumRecs - Max,
+ Recs1 = lists:keysort(#push_session.timestamp, Recs),
+ Recs2 = lists:reverse(Recs1),
+ OldRecs = lists:sublist(Recs2, Max + 1, NumOldRecs),
+ ?INFO_MSG("Disabling ~B old push session(s) of ~s@~s",
+ [NumOldRecs, U, S]),
+ lists:foreach(fun(Rec) -> mnesia:delete_object(Rec) end, OldRecs);
+ true ->
+ ok
+ end.
diff --git a/src/mod_roster.erl b/src/mod_roster.erl
index 28bc06171..7bc5f7de7 100644
--- a/src/mod_roster.erl
+++ b/src/mod_roster.erl
@@ -35,7 +35,6 @@
-module(mod_roster).
-protocol({xep, 237, '1.3'}).
--protocol({xep, 321, '0.1'}).
-author('alexey@process-one.net').
@@ -441,14 +440,13 @@ decode_item(Item, R, Managed) ->
end,
groups = Item#roster_item.groups}.
-process_iq_set(#iq{from = From, to = To,
+process_iq_set(#iq{from = _From, to = To,
sub_els = [#roster_query{items = [QueryItem]}]} = IQ) ->
#jid{user = User, luser = LUser, lserver = LServer} = To,
- Managed = {From#jid.luser, From#jid.lserver} /= {LUser, LServer},
LJID = jid:tolower(QueryItem#roster_item.jid),
F = fun () ->
Item = get_roster_item(LUser, LServer, LJID),
- Item2 = decode_item(QueryItem, Item, Managed),
+ Item2 = decode_item(QueryItem, Item, false),
Item3 = ejabberd_hooks:run_fold(roster_process_item,
LServer, Item2,
[LServer]),
@@ -1200,8 +1198,6 @@ mod_opt_type(access) ->
fun acl:access_rules_validator/1;
mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
-mod_opt_type(managers) ->
- fun (B) when is_list(B) -> B end;
mod_opt_type(store_current_id) ->
fun (B) when is_boolean(B) -> B end;
mod_opt_type(versioning) ->
@@ -1213,5 +1209,5 @@ mod_opt_type(O) when O == cache_life_time; O == cache_size ->
mod_opt_type(O) when O == use_cache; O == cache_missed ->
fun (B) when is_boolean(B) -> B end;
mod_opt_type(_) ->
- [access, db_type, iqdisc, managers, store_current_id,
+ [access, db_type, iqdisc, store_current_id,
versioning, cache_life_time, cache_size, use_cache, cache_missed].
diff --git a/src/mod_sip_proxy.erl b/src/mod_sip_proxy.erl
index 19a02e8e4..25f035377 100644
--- a/src/mod_sip_proxy.erl
+++ b/src/mod_sip_proxy.erl
@@ -27,8 +27,7 @@
-ifndef(SIP).
-export([]).
-else.
--define(GEN_FSM, p1_fsm).
--behaviour(?GEN_FSM).
+-behaviour(p1_fsm).
%% API
-export([start/2, start_link/2, route/3, route/4]).
@@ -58,10 +57,10 @@ start(LServer, Opts) ->
supervisor:start_child(mod_sip_proxy_sup, [LServer, Opts]).
start_link(LServer, Opts) ->
- ?GEN_FSM:start_link(?MODULE, [LServer, Opts], []).
+ p1_fsm:start_link(?MODULE, [LServer, Opts], []).
route(SIPMsg, _SIPSock, TrID, Pid) ->
- ?GEN_FSM:send_event(Pid, {SIPMsg, TrID}).
+ p1_fsm:send_event(Pid, {SIPMsg, TrID}).
route(#sip{hdrs = Hdrs} = Req, LServer, Opts) ->
case proplists:get_bool(authenticated, Opts) of
diff --git a/src/mod_stream_mgmt.erl b/src/mod_stream_mgmt.erl
index 127eea3e8..2f6b0fc71 100644
--- a/src/mod_stream_mgmt.erl
+++ b/src/mod_stream_mgmt.erl
@@ -33,6 +33,8 @@
c2s_unbinded_packet/2, c2s_closed/2, c2s_terminated/2,
c2s_handle_send/3, c2s_handle_info/2, c2s_handle_call/3,
c2s_handle_recv/3]).
+%% adjust pending session timeout
+-export([get_resume_timeout/1, set_resume_timeout/2]).
-include("xmpp.hrl").
-include("logger.hrl").
@@ -69,7 +71,12 @@ start(Host, _Opts) ->
ejabberd_hooks:add(c2s_terminated, Host, ?MODULE, c2s_terminated, 50).
stop(Host) ->
- %% TODO: do something with global 'c2s_init' hook
+ case gen_mod:is_loaded_elsewhere(Host, ?MODULE) of
+ true ->
+ ok;
+ false ->
+ ejabberd_hooks:delete(c2s_init, ?MODULE, c2s_stream_init, 50)
+ end,
ejabberd_hooks:delete(c2s_stream_started, Host, ?MODULE,
c2s_stream_started, 50),
ejabberd_hooks:delete(c2s_post_auth_features, Host, ?MODULE,
@@ -235,8 +242,9 @@ c2s_handle_info(#{mgmt_ack_timer := TRef, jid := JID, mod := Mod} = State,
[jid:encode(JID)]),
State1 = Mod:close(State),
{stop, transition_to_pending(State1)};
-c2s_handle_info(#{mgmt_state := pending, jid := JID, mod := Mod} = State,
- {timeout, _, pending_timeout}) ->
+c2s_handle_info(#{mgmt_state := pending,
+ mgmt_pending_timer := TRef, jid := JID, mod := Mod} = State,
+ {timeout, TRef, pending_timeout}) ->
?DEBUG("Timed out waiting for resumption of stream for ~s",
[jid:encode(JID)]),
Mod:stop(State#{mgmt_state => timeout});
@@ -283,6 +291,20 @@ c2s_terminated(State, _Reason) ->
State.
%%%===================================================================
+%%% Adjust pending session timeout
+%%%===================================================================
+-spec get_resume_timeout(state()) -> non_neg_integer().
+get_resume_timeout(#{mgmt_timeout := Timeout}) ->
+ Timeout.
+
+-spec set_resume_timeout(state(), non_neg_integer()) -> state().
+set_resume_timeout(#{mgmt_timeout := Timeout} = State, Timeout) ->
+ State;
+set_resume_timeout(State, Timeout) ->
+ State1 = restart_pending_timer(State, Timeout),
+ State1#{mgmt_timeout => Timeout}.
+
+%%%===================================================================
%%% Internal functions
%%%===================================================================
-spec negotiate_stream_mgmt(xmpp_element(), state()) -> state().
@@ -388,8 +410,6 @@ handle_resume(#{user := User, lserver := LServer, sockmod := SockMod,
previd = AttrId}),
State3 = resend_unacked_stanzas(State2),
State4 = send(State3, #sm_r{xmlns = AttrXmlns}),
- %% TODO: move this to mod_client_state
- %% csi_flush_queue(State4),
State5 = ejabberd_hooks:run_fold(c2s_session_resumed, LServer, State4, []),
?INFO_MSG("(~s) Resumed session for ~s",
[SockMod:pp(Socket), jid:encode(JID)]),
@@ -408,8 +428,8 @@ transition_to_pending(#{mgmt_state := active, jid := JID,
lserver := LServer, mgmt_timeout := Timeout} = State) ->
State1 = cancel_ack_timer(State),
?INFO_MSG("Waiting for resumption of stream for ~s", [jid:encode(JID)]),
- erlang:start_timer(timer:seconds(Timeout), self(), pending_timeout),
- State2 = State1#{mgmt_state => pending},
+ TRef = erlang:start_timer(timer:seconds(Timeout), self(), pending_timeout),
+ State2 = State1#{mgmt_state => pending, mgmt_pending_timer => TRef},
ejabberd_hooks:run_fold(c2s_session_pending, LServer, State2, []);
transition_to_pending(State) ->
State.
@@ -648,20 +668,33 @@ add_resent_delay_info(_State, El, _Time) ->
send(#{mod := Mod} = State, Pkt) ->
Mod:send(State, Pkt).
+-spec restart_pending_timer(state(), non_neg_integer()) -> state().
+restart_pending_timer(#{mgmt_pending_timer := TRef} = State, NewTimeout) ->
+ cancel_timer(TRef),
+ NewTRef = erlang:start_timer(timer:seconds(NewTimeout), self(),
+ pending_timeout),
+ State#{mgmt_pending_timer => NewTRef};
+restart_pending_timer(State, _NewTimeout) ->
+ State.
+
-spec cancel_ack_timer(state()) -> state().
cancel_ack_timer(#{mgmt_ack_timer := TRef} = State) ->
- case erlang:cancel_timer(TRef) of
- false ->
- receive {timeout, TRef, _} -> ok
- after 0 -> ok
- end;
- _ ->
- ok
- end,
+ cancel_timer(TRef),
maps:remove(mgmt_ack_timer, State);
cancel_ack_timer(State) ->
State.
+-spec cancel_timer(reference()) -> ok.
+cancel_timer(TRef) ->
+ case erlang:cancel_timer(TRef) of
+ false ->
+ receive {timeout, TRef, _} -> ok
+ after 0 -> ok
+ end;
+ _ ->
+ ok
+ end.
+
-spec bounce_message_queue() -> ok.
bounce_message_queue() ->
receive {route, Pkt} ->
diff --git a/src/mod_vcard.erl b/src/mod_vcard.erl
index 495393f72..67d01a085 100644
--- a/src/mod_vcard.erl
+++ b/src/mod_vcard.erl
@@ -67,7 +67,7 @@
-optional_callbacks([use_cache/1, cache_nodes/1]).
--record(state, {host :: binary(), server_host :: binary()}).
+-record(state, {hosts :: [binary()], server_host :: binary()}).
%%====================================================================
%% gen_mod callbacks
@@ -95,37 +95,40 @@ init([Host, Opts]) ->
?NS_VCARD, ?MODULE, process_sm_iq, IQDisc),
ejabberd_hooks:add(disco_sm_features, Host, ?MODULE,
get_sm_features, 50),
- MyHost = gen_mod:get_opt_host(Host, Opts, <<"vjud.@HOST@">>),
+ MyHosts = gen_mod:get_opt_hosts(Host, Opts, <<"vjud.@HOST@">>),
Search = gen_mod:get_opt(search, Opts, false),
if Search ->
- ejabberd_hooks:add(
- disco_local_items, MyHost, ?MODULE, disco_items, 100),
- ejabberd_hooks:add(
- disco_local_features, MyHost, ?MODULE, disco_features, 100),
- ejabberd_hooks:add(
- disco_local_identity, MyHost, ?MODULE, disco_identity, 100),
- gen_iq_handler:add_iq_handler(
- ejabberd_local, MyHost, ?NS_SEARCH, ?MODULE, process_search, IQDisc),
- gen_iq_handler:add_iq_handler(
- ejabberd_local, MyHost, ?NS_VCARD, ?MODULE, process_vcard, IQDisc),
- gen_iq_handler:add_iq_handler(
- ejabberd_local, MyHost, ?NS_DISCO_ITEMS, mod_disco,
- process_local_iq_items, IQDisc),
- gen_iq_handler:add_iq_handler(
- ejabberd_local, MyHost, ?NS_DISCO_INFO, mod_disco,
- process_local_iq_info, IQDisc),
- case Mod:is_search_supported(Host) of
- false ->
- ?WARNING_MSG("vcard search functionality is "
- "not implemented for ~s backend",
- [gen_mod:db_type(Host, Opts, ?MODULE)]);
- true ->
- ejabberd_router:register_route(MyHost, Host)
- end;
+ lists:foreach(
+ fun(MyHost) ->
+ ejabberd_hooks:add(
+ disco_local_items, MyHost, ?MODULE, disco_items, 100),
+ ejabberd_hooks:add(
+ disco_local_features, MyHost, ?MODULE, disco_features, 100),
+ ejabberd_hooks:add(
+ disco_local_identity, MyHost, ?MODULE, disco_identity, 100),
+ gen_iq_handler:add_iq_handler(
+ ejabberd_local, MyHost, ?NS_SEARCH, ?MODULE, process_search, IQDisc),
+ gen_iq_handler:add_iq_handler(
+ ejabberd_local, MyHost, ?NS_VCARD, ?MODULE, process_vcard, IQDisc),
+ gen_iq_handler:add_iq_handler(
+ ejabberd_local, MyHost, ?NS_DISCO_ITEMS, mod_disco,
+ process_local_iq_items, IQDisc),
+ gen_iq_handler:add_iq_handler(
+ ejabberd_local, MyHost, ?NS_DISCO_INFO, mod_disco,
+ process_local_iq_info, IQDisc),
+ case Mod:is_search_supported(Host) of
+ false ->
+ ?WARNING_MSG("vcard search functionality is "
+ "not implemented for ~s backend",
+ [gen_mod:db_type(Host, Opts, ?MODULE)]);
+ true ->
+ ejabberd_router:register_route(MyHost, Host)
+ end
+ end, MyHosts);
true ->
ok
end,
- {ok, #state{host = MyHost, server_host = Host}}.
+ {ok, #state{hosts = MyHosts, server_host = Host}}.
handle_call(_Call, _From, State) ->
{noreply, State}.
@@ -144,21 +147,24 @@ handle_info(Info, State) ->
?WARNING_MSG("unexpected info: ~p", [Info]),
{noreply, State}.
-terminate(_Reason, #state{host = MyHost, server_host = Host}) ->
+terminate(_Reason, #state{hosts = MyHosts, server_host = Host}) ->
ejabberd_hooks:delete(remove_user, Host, ?MODULE, remove_user, 50),
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_VCARD),
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_VCARD),
ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE, get_sm_features, 50),
Mod = gen_mod:db_mod(Host, ?MODULE),
Mod:stop(Host),
- ejabberd_router:unregister_route(MyHost),
- ejabberd_hooks:delete(disco_local_items, MyHost, ?MODULE, disco_items, 100),
- ejabberd_hooks:delete(disco_local_features, MyHost, ?MODULE, disco_features, 100),
- ejabberd_hooks:delete(disco_local_identity, MyHost, ?MODULE, disco_identity, 100),
- gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_SEARCH),
- gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_VCARD),
- gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_ITEMS),
- gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_INFO).
+ lists:foreach(
+ fun(MyHost) ->
+ ejabberd_router:unregister_route(MyHost),
+ ejabberd_hooks:delete(disco_local_items, MyHost, ?MODULE, disco_items, 100),
+ ejabberd_hooks:delete(disco_local_features, MyHost, ?MODULE, disco_features, 100),
+ ejabberd_hooks:delete(disco_local_identity, MyHost, ?MODULE, disco_identity, 100),
+ gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_SEARCH),
+ gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_VCARD),
+ gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_ITEMS),
+ gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_INFO)
+ end, MyHosts).
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
@@ -527,6 +533,8 @@ mod_opt_type(allow_return_all) ->
fun (B) when is_boolean(B) -> B end;
mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
mod_opt_type(host) -> fun iolist_to_binary/1;
+mod_opt_type(hosts) ->
+ fun (L) -> lists:map(fun iolist_to_binary/1, L) end;
mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
mod_opt_type(matches) ->
fun (infinity) -> infinity;
@@ -543,6 +551,6 @@ mod_opt_type(O) when O == cache_life_time; O == cache_size ->
mod_opt_type(O) when O == use_cache; O == cache_missed ->
fun (B) when is_boolean(B) -> B end;
mod_opt_type(_) ->
- [allow_return_all, db_type, host, iqdisc, matches,
+ [allow_return_all, db_type, host, hosts, iqdisc, matches,
search, search_all_hosts, cache_life_time, cache_size,
use_cache, cache_missed].
diff --git a/src/mod_vcard_ldap.erl b/src/mod_vcard_ldap.erl
index f1f076468..38c4747e6 100644
--- a/src/mod_vcard_ldap.erl
+++ b/src/mod_vcard_ldap.erl
@@ -47,7 +47,7 @@
-record(state,
{serverhost = <<"">> :: binary(),
- myhost = <<"">> :: binary(),
+ myhosts = [] :: [binary()],
eldap_id = <<"">> :: binary(),
search = false :: boolean(),
servers = [] :: [binary()],
@@ -351,8 +351,7 @@ default_search_reported() ->
{<<"Organization Unit">>, <<"ORGUNIT">>}].
parse_options(Host, Opts) ->
- MyHost = gen_mod:get_opt_host(Host, Opts,
- <<"vjud.@HOST@">>),
+ MyHosts = gen_mod:get_opt_hosts(Host, Opts, <<"vjud.@HOST@">>),
Search = gen_mod:get_opt(search, Opts, false),
Matches = gen_mod:get_opt(matches, Opts, 30),
Eldap_ID = misc:atom_to_binary(gen_mod:get_module_proc(Host, ?PROCNAME)),
@@ -394,7 +393,7 @@ parse_options(Host, Opts) ->
end,
SearchReported)
++ UIDAttrs),
- #state{serverhost = Host, myhost = MyHost,
+ #state{serverhost = Host, myhosts = MyHosts,
eldap_id = Eldap_ID, search = Search,
servers = Cfg#eldap_config.servers,
backups = Cfg#eldap_config.backups,
diff --git a/src/node_pep.erl b/src/node_pep.erl
index 6dd7a68c4..cc0dd41fb 100644
--- a/src/node_pep.erl
+++ b/src/node_pep.erl
@@ -119,7 +119,7 @@ create_node(Nidx, Owner) ->
delete_node(Nodes) ->
{result, {_, _, Result}} = node_flat:delete_node(Nodes),
- {result, {[], Result}}.
+ {result, {default, Result}}.
subscribe_node(Nidx, Sender, Subscriber, AccessModel,
SendLast, PresenceSubscription, RosterGroup, Options) ->
diff --git a/src/node_pep_sql.erl b/src/node_pep_sql.erl
index 68a2ce946..b84c945bd 100644
--- a/src/node_pep_sql.erl
+++ b/src/node_pep_sql.erl
@@ -73,7 +73,7 @@ create_node(Nidx, Owner) ->
delete_node(Nodes) ->
{result, {_, _, Result}} = node_flat_sql:delete_node(Nodes),
- {result, {[], Result}}.
+ {result, {default, Result}}.
subscribe_node(Nidx, Sender, Subscriber, AccessModel,
SendLast, PresenceSubscription, RosterGroup, Options) ->
diff --git a/src/prosody2ejabberd.erl b/src/prosody2ejabberd.erl
index c60f2c24b..2c7dabb48 100644
--- a/src/prosody2ejabberd.erl
+++ b/src/prosody2ejabberd.erl
@@ -50,7 +50,7 @@ from_dir(ProsodyDir) ->
convert_dir(Path, Host, SubDir)
end, ["vcard", "accounts", "roster",
"private", "config", "offline",
- "privacy", "pubsub"])
+ "privacy", "pep", "pubsub"])
end, HostDirs);
{error, Why} = Err ->
?ERROR_MSG("failed to list ~s: ~s",
@@ -67,12 +67,24 @@ convert_dir(Path, Host, Type) ->
lists:foreach(
fun(File) ->
FilePath = filename:join(Path, File),
- case eval_file(FilePath) of
- {ok, Data} ->
- Name = iolist_to_binary(filename:rootname(File)),
- convert_data(Host, Type, Name, Data);
- Err ->
- Err
+ case Type of
+ "pep" ->
+ case filelib:is_dir(FilePath) of
+ true ->
+ JID = list_to_binary(File ++ "@" ++ Host),
+ convert_dir(FilePath, JID, "pubsub");
+ false ->
+ ok
+ end;
+ _ ->
+ case eval_file(FilePath) of
+ {ok, Data} ->
+ Name = iolist_to_binary(filename:rootname(File)),
+ convert_data(url_decode(Host), Type,
+ url_decode(Name), Data);
+ Err ->
+ Err
+ end
end
end, Files);
{error, enoent} ->
@@ -212,44 +224,49 @@ convert_data(Host, "privacy", User, [Data]) ->
end
end, Lists)},
mod_privacy:set_list(Priv);
-convert_data(PubSub, "pubsub", NodeId, [Data]) ->
- Host = url_decode(PubSub),
- Node = url_decode(NodeId),
- Type = node_type(Host, Node),
- NodeData = convert_node_config(Host, Data),
- DefaultConfig = mod_pubsub:config(Host, default_node_config, []),
- Owner = proplists:get_value(owner, NodeData),
- Options = lists:foldl(
- fun({_Opt, undefined}, Acc) ->
- Acc;
- ({Opt, Val}, Acc) ->
- lists:keystore(Opt, 1, Acc, {Opt, Val})
- end, DefaultConfig, proplists:get_value(options, NodeData)),
- case mod_pubsub:tree_action(Host, create_node, [Host, Node, Type, Owner, Options, []]) of
- {ok, Nidx} ->
- case mod_pubsub:node_action(Host, Type, create_node, [Nidx, Owner]) of
- {result, _} ->
- Access = open, % always allow subscriptions proplists:get_value(access_model, Options),
- Publish = open, % always allow publications proplists:get_value(publish_model, Options),
- MaxItems = proplists:get_value(max_items, Options),
- Affiliations = proplists:get_value(affiliations, NodeData),
- Subscriptions = proplists:get_value(subscriptions, NodeData),
- Items = proplists:get_value(items, NodeData),
- [mod_pubsub:node_action(Host, Type, set_affiliation,
- [Nidx, Entity, Aff])
- || {Entity, Aff} <- Affiliations, Entity =/= Owner],
- [mod_pubsub:node_action(Host, Type, subscribe_node,
- [Nidx, jid:make(Entity), Entity, Access, never, [], [], []])
- || Entity <- Subscriptions],
- [mod_pubsub:node_action(Host, Type, publish_item,
- [Nidx, Publisher, Publish, MaxItems, ItemId, Payload, []])
- || {ItemId, Publisher, Payload} <- Items];
+convert_data(HostStr, "pubsub", Node, [Data]) ->
+ case decode_pubsub_host(HostStr) of
+ Host when is_binary(Host);
+ is_tuple(Host) ->
+ Type = node_type(Host),
+ NodeData = convert_node_config(HostStr, Data),
+ DefaultConfig = mod_pubsub:config(Host, default_node_config, []),
+ Owner = proplists:get_value(owner, NodeData),
+ Options = lists:foldl(
+ fun({_Opt, undefined}, Acc) ->
+ Acc;
+ ({Opt, Val}, Acc) ->
+ lists:keystore(Opt, 1, Acc, {Opt, Val})
+ end, DefaultConfig, proplists:get_value(options, NodeData)),
+ case mod_pubsub:tree_action(Host, create_node, [Host, Node, Type, Owner, Options, []]) of
+ {ok, Nidx} ->
+ case mod_pubsub:node_action(Host, Type, create_node, [Nidx, Owner]) of
+ {result, _} ->
+ Access = open, % always allow subscriptions proplists:get_value(access_model, Options),
+ Publish = open, % always allow publications proplists:get_value(publish_model, Options),
+ MaxItems = proplists:get_value(max_items, Options),
+ Affiliations = proplists:get_value(affiliations, NodeData),
+ Subscriptions = proplists:get_value(subscriptions, NodeData),
+ Items = proplists:get_value(items, NodeData),
+ [mod_pubsub:node_action(Host, Type, set_affiliation,
+ [Nidx, Entity, Aff])
+ || {Entity, Aff} <- Affiliations, Entity =/= Owner],
+ [mod_pubsub:node_action(Host, Type, subscribe_node,
+ [Nidx, jid:make(Entity), Entity, Access, never, [], [], []])
+ || Entity <- Subscriptions],
+ [mod_pubsub:node_action(Host, Type, publish_item,
+ [Nidx, Publisher, Publish, MaxItems, ItemId, Payload, []])
+ || {ItemId, Publisher, Payload} <- Items];
+ Error ->
+ Error
+ end;
Error ->
+ ?ERROR_MSG("failed to import pubsub node ~s on ~p:~n~p",
+ [Node, Host, NodeData]),
Error
end;
Error ->
- ?ERROR_MSG("failed to import pubsub node ~s on host ~s:~n~p",
- [Node, Host, NodeData]),
+ ?ERROR_MSG("failed to import pubsub node: ~p", [Error]),
Error
end;
convert_data(_Host, _Type, _User, _Data) ->
@@ -383,10 +400,15 @@ url_decode(<<H, Tail/binary>>, Acc) ->
url_decode(<<>>, Acc) ->
Acc.
-node_type(_Host, <<"urn:", _Tail/binary>>) -> <<"pep">>;
-node_type(_Host, <<"http:", _Tail/binary>>) -> <<"pep">>;
-node_type(_Host, <<"https:", _Tail/binary>>) -> <<"pep">>;
-node_type(Host, _) -> hd(mod_pubsub:plugins(Host)).
+decode_pubsub_host(Host) ->
+ try jid:decode(Host) of
+ #jid{luser = <<>>, lserver = LServer} -> LServer;
+ #jid{luser = LUser, lserver = LServer} -> {LUser, LServer, <<>>}
+ catch _:{bad_jid, _} -> bad_jid
+ end.
+
+node_type({_U, _S, _R}) -> <<"pep">>;
+node_type(Host) -> hd(mod_pubsub:plugins(Host)).
max_items(Config, Default) ->
case round(proplists:get_value(<<"max_items">>, Config, Default)) of
@@ -422,7 +444,7 @@ convert_node_items(Host, Data) ->
Authors = proplists:get_value(<<"data_author">>, Data, []),
lists:flatmap(
fun({ItemId, Item}) ->
- try catch jid:decode(proplists:get_value(ItemId, Authors, Host)) of
+ try jid:decode(proplists:get_value(ItemId, Authors, Host)) of
JID ->
[El] = deserialize(Item),
[{ItemId, JID, El#xmlel.children}]
@@ -496,5 +518,5 @@ deserialize([{_, S}|T], #xmlel{children = Els} = El, Acc) when is_binary(S) ->
deserialize(T, El#xmlel{children = [{xmlcdata, S}|Els]}, Acc);
deserialize([{_, L}|T], #xmlel{children = Els} = El, Acc) when is_list(L) ->
deserialize(T, El#xmlel{children = deserialize(L) ++ Els}, Acc);
-deserialize([], El, Acc) ->
- [El|Acc].
+deserialize([], #xmlel{children = Els} = El, Acc) ->
+ [El#xmlel{children = lists:reverse(Els)}|Acc].
diff --git a/src/pubsub_db_sql.erl b/src/pubsub_db_sql.erl
index ae7a9fde6..ae28184db 100644
--- a/src/pubsub_db_sql.erl
+++ b/src/pubsub_db_sql.erl
@@ -25,12 +25,16 @@
-module(pubsub_db_sql).
+-compile([{parse_transform, ejabberd_sql_pt}]).
+
-author("pablo.polvorin@process-one.net").
-include("pubsub.hrl").
+-include("ejabberd_sql_pt.hrl").
-export([add_subscription/1, read_subscription/1,
delete_subscription/1, update_subscription/1]).
+-export([export/1]).
%% TODO: Those -spec lines produce errors in old Erlang versions.
%% They can be enabled again in ejabberd 3.0 because it uses R12B or higher.
@@ -139,3 +143,93 @@ sql_to_integer(N) -> binary_to_integer(N).
sql_to_boolean(B) -> B == <<"1">>.
sql_to_timestamp(T) -> xmpp_util:decode_timestamp(T).
+
+%% REVIEW:
+%% * this code takes NODEID from Itemid2, and forgets about Nodeidx
+%% * this code assumes Payload only contains one xmlelement()
+%% * PUBLISHER is taken from Creation
+export(_Server) ->
+ [{pubsub_item,
+ fun(_Host, #pubsub_item{itemid = {Itemid1, NODEID},
+ %nodeidx = _Nodeidx,
+ creation = {{C1, C2, C3}, Cusr},
+ modification = {{M1, M2, M3}, _Musr},
+ payload = Payload}) ->
+ ITEMID = ejabberd_sql:escape(Itemid1),
+ CREATION = ejabberd_sql:escape(list_to_binary(
+ string:join([string:right(integer_to_list(I),6,$0)||I<-[C1,C2,C3]],":"))),
+ MODIFICATION = ejabberd_sql:escape(list_to_binary(
+ string:join([string:right(integer_to_list(I),6,$0)||I<-[M1,M2,M3]],":"))),
+ PUBLISHER = ejabberd_sql:escape(jid:encode(Cusr)),
+ [PayloadEl] = [El || {xmlel,_,_,_} = El <- Payload],
+ PAYLOAD = ejabberd_sql:escape(fxml:element_to_binary(PayloadEl)),
+ [?SQL("delete from pubsub_item where itemid=%(ITEMID)s;"),
+ ?SQL("insert into pubsub_item(itemid,nodeid,creation,modification,publisher,payload) \n"
+ " values (%(ITEMID)s, %(NODEID)d, %(CREATION)s,
+ %(MODIFICATION)s, %(PUBLISHER)s, %(PAYLOAD)s);")];
+ (_Host, _R) ->
+ []
+ end},
+%% REVIEW:
+%% * From the mnesia table, the #pubsub_state.items is not used in ODBC
+%% * Right now AFFILIATION is the first letter of Affiliation
+%% * Right now SUBSCRIPTIONS expects only one Subscription
+%% * Right now SUBSCRIPTIONS letter is the first letter of Subscription
+ {pubsub_state,
+ fun(_Host, #pubsub_state{stateid = {Jid, Stateid},
+ %nodeidx = Nodeidx,
+ items = _Items,
+ affiliation = Affiliation,
+ subscriptions = Subscriptions}) ->
+ STATEID = list_to_binary(integer_to_list(Stateid)),
+ JID = ejabberd_sql:escape(jid:encode(Jid)),
+ NODEID = <<"unknown">>, %% TODO: integer_to_list(Nodeidx),
+ AFFILIATION = list_to_binary(string:substr(atom_to_list(Affiliation),1,1)),
+ SUBSCRIPTIONS = list_to_binary(parse_subscriptions(Subscriptions)),
+ [?SQL("delete from pubsub_state where stateid=%(STATEID)s;"),
+ ?SQL("insert into pubsub_state(stateid,jid,nodeid,affiliation,subscriptions)\n"
+ " values (%(STATEID)s, %(JID)s, %(NODEID)s, %(AFFILIATION)s, %(SUBSCRIPTIONS)s);")];
+ (_Host, _R) ->
+ []
+ end},
+
+%% REVIEW:
+%% * Parents is not migrated to PARENTs
+%% * Probably some option VALs are not correctly represented in mysql
+ {pubsub_node,
+ fun(_Host, #pubsub_node{nodeid = {Hostid, Nodeid},
+ id = Id,
+ parents = _Parents,
+ type = Type,
+ owners = Owners,
+ options = Options}) ->
+ HOST = case Hostid of
+ {U,S,R} -> ejabberd_sql:escape(jid:encode({U,S,R}));
+ _ -> ejabberd_sql:escape(Hostid)
+ end,
+ NODE = ejabberd_sql:escape(Nodeid),
+ PARENT = <<"">>,
+ IdB = integer_to_binary(Id),
+ TYPE = ejabberd_sql:escape(<<Type/binary, "_odbc">>),
+ [?SQL("delete from pubsub_node where nodeid=%(Id)d;"),
+ ?SQL("insert into pubsub_node(host,node,nodeid,parent,type) \n"
+ " values (%(HOST)s, %(NODE)s, %(Id)d, %(PARENT)s, %(TYPE)s);"),
+ ?SQL("delete from pubsub_node_option where nodeid=%(Id)d;"),
+ [["insert into pubsub_node_option(nodeid,name,val)\n"
+ " values (", IdB, ", '", atom_to_list(Name), "', '",
+ io_lib:format("~p", [Val]), "');\n"] || {Name,Val} <- Options],
+ ?SQL("delete from pubsub_node_owner where nodeid=%(Id)d;"),
+ [["insert into pubsub_node_owner(nodeid,owner)\n"
+ " values (", IdB, ", '", jid:encode(Usr), "');\n"] || Usr <- Owners],"\n"];
+ (_Host, _R) ->
+ []
+ end}].
+
+parse_subscriptions([]) ->
+ "";
+parse_subscriptions([{State, Item}]) ->
+ STATE = case State of
+ subscribed -> "s"
+ end,
+ string:join([STATE, Item],":").
+
diff --git a/src/randoms.erl b/src/randoms.erl
index ad07b47c2..7686edcff 100644
--- a/src/randoms.erl
+++ b/src/randoms.erl
@@ -32,6 +32,20 @@
-define(THRESHOLD, 16#10000000000000000).
+-ifdef(RAND_UNIFORM).
+get_string() ->
+ R = rand:uniform(?THRESHOLD),
+ integer_to_binary(R).
+
+uniform() ->
+ rand:uniform().
+
+uniform(N) ->
+ rand:uniform(N).
+
+uniform(N, M) ->
+ rand:uniform(M-N+1) + N-1.
+-else.
get_string() ->
R = crypto:rand_uniform(0, ?THRESHOLD),
integer_to_binary(R).
@@ -44,6 +58,7 @@ uniform(N) ->
uniform(N, M) ->
crypto:rand_uniform(N, M+1).
+-endif.
-ifdef(STRONG_RAND_BYTES).
bytes(N) ->
diff --git a/test/ejabberd_SUITE.erl b/test/ejabberd_SUITE.erl
index 17465617b..97c56159a 100644
--- a/test/ejabberd_SUITE.erl
+++ b/test/ejabberd_SUITE.erl
@@ -431,6 +431,7 @@ db_tests(DB) when DB == mnesia; DB == redis ->
mam_tests:single_cases(),
carbons_tests:single_cases(),
csi_tests:single_cases(),
+ push_tests:single_cases(),
test_unregister]},
muc_tests:master_slave_cases(),
privacy_tests:master_slave_cases(),
@@ -441,7 +442,8 @@ db_tests(DB) when DB == mnesia; DB == redis ->
vcard_tests:master_slave_cases(),
announce_tests:master_slave_cases(),
carbons_tests:master_slave_cases(),
- csi_tests:master_slave_cases()];
+ csi_tests:master_slave_cases(),
+ push_tests:master_slave_cases()];
db_tests(_) ->
[{single_user, [sequence],
[test_register,
@@ -748,25 +750,25 @@ test_component_send(Config) ->
disconnect(Config).
s2s_dialback(Config) ->
- ejabberd_s2s:stop_all_connections(),
+ ejabberd_s2s:stop_s2s_connections(),
ejabberd_config:add_option(s2s_use_starttls, false),
ejabberd_config:add_option(domain_certfile, "self-signed-cert.pem"),
s2s_ping(Config).
s2s_optional(Config) ->
- ejabberd_s2s:stop_all_connections(),
+ ejabberd_s2s:stop_s2s_connections(),
ejabberd_config:add_option(s2s_use_starttls, optional),
ejabberd_config:add_option(domain_certfile, "self-signed-cert.pem"),
s2s_ping(Config).
s2s_required(Config) ->
- ejabberd_s2s:stop_all_connections(),
+ ejabberd_s2s:stop_s2s_connections(),
ejabberd_config:add_option(s2s_use_starttls, required),
ejabberd_config:add_option(domain_certfile, "self-signed-cert.pem"),
s2s_ping(Config).
s2s_required_trusted(Config) ->
- ejabberd_s2s:stop_all_connections(),
+ ejabberd_s2s:stop_s2s_connections(),
ejabberd_config:add_option(s2s_use_starttls, required),
ejabberd_config:add_option(domain_certfile, "cert.pem"),
s2s_ping(Config).
diff --git a/test/ejabberd_SUITE_data/ejabberd.yml b/test/ejabberd_SUITE_data/ejabberd.yml
index 89618c0c0..a648cb422 100644
--- a/test/ejabberd_SUITE_data/ejabberd.yml
+++ b/test/ejabberd_SUITE_data/ejabberd.yml
@@ -231,7 +231,11 @@ Welcome to this XMPP server."
mod_disco: []
mod_ping: []
mod_proxy65: []
+ mod_push: []
+ mod_push_keepalive: []
mod_s2s_dialback: []
+ mod_stream_mgmt:
+ resume_timeout: 3
mod_legacy_auth: []
mod_register:
welcome_message:
@@ -290,7 +294,11 @@ Welcome to this XMPP server."
mod_disco: []
mod_ping: []
mod_proxy65: []
+ mod_push: []
+ mod_push_keepalive: []
mod_s2s_dialback: []
+ mod_stream_mgmt:
+ resume_timeout: 3
mod_legacy_auth: []
mod_register:
welcome_message:
@@ -450,6 +458,8 @@ listen:
port: @@web_port@@
module: ejabberd_http
captcha: true
+ request_handlers:
+ "/api": mod_http_api
-
port: @@component_port@@
module: ejabberd_service
@@ -466,6 +476,7 @@ modules:
mod_proxy65: []
mod_legacy: []
mod_muc: []
+ mod_muc_admin: []
mod_register:
welcome_message:
subject: "Welcome!"
@@ -488,3 +499,8 @@ outgoing_s2s_port: @@s2s_port@@
shaper:
fast: 50000
normal: 10000
+
+api_permissions:
+ "public commands":
+ who: all
+ what: "*"
diff --git a/test/ejabberd_SUITE_data/extauth.py b/test/ejabberd_SUITE_data/extauth.py
index fa2c9efd0..263d6464e 100755
--- a/test/ejabberd_SUITE_data/extauth.py
+++ b/test/ejabberd_SUITE_data/extauth.py
@@ -3,23 +3,27 @@ import struct
def read():
(pkt_size,) = struct.unpack('>H', sys.stdin.read(2))
- pkt = sys.stdin.read(pkt_size).split(':')
- cmd = pkt[0]
- args_num = len(pkt) - 1
- if cmd == 'auth' and args_num >= 3:
- if pkt[1] == "wrong":
+ pkt = sys.stdin.read(pkt_size)
+ cmd = pkt.split(':')[0]
+ if cmd == 'auth':
+ u, s, p = pkt.split(':', 3)[1:]
+ if u == "wrong":
write(False)
else:
write(True)
- elif cmd == 'isuser' and args_num == 2:
+ elif cmd == 'isuser':
+ u, s = pkt.split(':', 2)[1:]
+ elif cmd == 'setpass':
+ u, s, p = pkt.split(':', 3)[1:]
write(True)
- elif cmd == 'setpass' and args_num >= 3:
+ elif cmd == 'tryregister':
+ u, s, p = pkt.split(':', 3)[1:]
write(True)
- elif cmd == 'tryregister' and args_num >= 3:
+ elif cmd == 'removeuser':
+ u, s = pkt.split(':', 2)[1:]
write(True)
- elif cmd == 'removeuser' and args_num == 2:
- write(True)
- elif cmd == 'removeuser3' and args_num >= 3:
+ elif cmd == 'removeuser3':
+ u, s, p = pkt.split(':', 3)[1:]
write(True)
else:
write(False)
diff --git a/test/muc_tests.erl b/test/muc_tests.erl
index 9ded2e0fe..bcceb6938 100644
--- a/test/muc_tests.erl
+++ b/test/muc_tests.erl
@@ -53,7 +53,8 @@ single_cases() ->
single_test(service_vcard),
single_test(configure_non_existent),
single_test(cancel_configure_non_existent),
- single_test(service_subscriptions)]}.
+ single_test(service_subscriptions),
+ single_test(set_room_affiliation)]}.
service_presence_error(Config) ->
Service = muc_jid(Config),
@@ -242,6 +243,32 @@ service_subscriptions(Config) ->
end, Rooms),
disconnect(Config).
+set_room_affiliation(Config) ->
+ #jid{server = RoomService} = muc_jid(Config),
+ RoomName = <<"set_room_affiliation">>,
+ RoomJID = jid:make(RoomName, RoomService),
+ MyJID = my_jid(Config),
+ PeerJID = jid:remove_resource(?config(slave, Config)),
+
+ ct:pal("joining room ~p", [RoomJID]),
+ ok = join_new(Config, RoomJID),
+
+ ct:pal("setting affiliation in room ~p to 'member' for ~p", [RoomJID, PeerJID]),
+ ServerHost = ?config(server_host, Config),
+ WebPort = ct:get_config(web_port, 5280),
+ RequestURL = "http://" ++ ServerHost ++ ":" ++ integer_to_list(WebPort) ++ "/api/set_room_affiliation",
+ Headers = [{"X-Admin", "true"}],
+ ContentType = "application/json",
+ Body = jiffy:encode(#{name => RoomName, service => RoomService, jid => jid:encode(PeerJID), affiliation => member}),
+ {ok, {{_, 200, _}, _, _}} = httpc:request(post, {RequestURL, Headers, ContentType, Body}, [], []),
+
+ #message{id = _, from = RoomJID, to = MyJID, sub_els = [
+ #muc_user{items = [
+ #muc_item{affiliation = member, role = none, jid = PeerJID}]}]} = recv_message(Config),
+
+ ok = leave(Config, RoomJID),
+ disconnect(Config).
+
%%%===================================================================
%%% Master-slave tests
%%%===================================================================
diff --git a/test/push_tests.erl b/test/push_tests.erl
new file mode 100644
index 000000000..b1f3a8b78
--- /dev/null
+++ b/test/push_tests.erl
@@ -0,0 +1,234 @@
+%%%-------------------------------------------------------------------
+%%% Author : Holger Weiss <holger@zedat.fu-berlin.de>
+%%% Created : 15 Jul 2017 by Holger Weiss <holger@zedat.fu-berlin.de>
+%%%
+%%%
+%%% ejabberd, Copyright (C) 2017 ProcessOne
+%%%
+%%% This program is free software; you can redistribute it and/or
+%%% modify it under the terms of the GNU General Public License as
+%%% published by the Free Software Foundation; either version 2 of the
+%%% License, or (at your option) any later version.
+%%%
+%%% This program is distributed in the hope that it will be useful,
+%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
+%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%%% General Public License for more details.
+%%%
+%%% You should have received a copy of the GNU General Public License along
+%%% with this program; if not, write to the Free Software Foundation, Inc.,
+%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+%%%
+%%%----------------------------------------------------------------------
+
+-module(push_tests).
+
+%% API
+-compile(export_all).
+-import(suite, [close_socket/1, connect/1, disconnect/1, get_event/1,
+ get_features/2, make_iq_result/1, my_jid/1, put_event/2, recv/1,
+ recv_iq/1, recv_message/1, self_presence/2, send/2, send_recv/2,
+ server_jid/1]).
+
+-include("suite.hrl").
+
+-define(PUSH_NODE, <<"d3v1c3">>).
+-define(PUSH_XDATA_FIELDS,
+ [#xdata_field{var = <<"FORM_TYPE">>,
+ values = [?NS_PUBSUB_PUBLISH_OPTIONS]},
+ #xdata_field{var = <<"secret">>,
+ values = [<<"c0nf1d3nt14l">>]}]).
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+%%%===================================================================
+%%% Single user tests
+%%%===================================================================
+single_cases() ->
+ {push_single, [sequence],
+ [single_test(feature_enabled),
+ single_test(unsupported_iq)]}.
+
+feature_enabled(Config) ->
+ BareMyJID = jid:remove_resource(my_jid(Config)),
+ Features = get_features(Config, BareMyJID),
+ true = lists:member(?NS_PUSH_0, Features),
+ disconnect(Config).
+
+unsupported_iq(Config) ->
+ PushJID = my_jid(Config),
+ lists:foreach(
+ fun(SubEl) ->
+ #iq{type = error} =
+ send_recv(Config, #iq{type = get, sub_els = [SubEl]})
+ end, [#push_enable{jid = PushJID}, #push_disable{jid = PushJID}]),
+ disconnect(Config).
+
+%%%===================================================================
+%%% Master-slave tests
+%%%===================================================================
+master_slave_cases() ->
+ {push_master_slave, [sequence],
+ [master_slave_test(sm),
+ master_slave_test(offline),
+ master_slave_test(mam)]}.
+
+sm_master(Config) ->
+ ct:comment("Waiting for the slave to close the socket"),
+ peer_down = get_event(Config),
+ ct:comment("Waiting a bit in order to test the keepalive feature"),
+ ct:sleep(5000), % Without mod_push_keepalive, the session would time out.
+ ct:comment("Sending message to the slave"),
+ send_test_message(Config),
+ ct:comment("Handling push notification"),
+ handle_notification(Config),
+ ct:comment("Receiving bounced message from the slave"),
+ #message{type = error} = recv_message(Config),
+ ct:comment("Closing the connection"),
+ disconnect(Config).
+
+sm_slave(Config) ->
+ ct:comment("Enabling push notifications"),
+ ok = enable_push(Config),
+ ct:comment("Enabling stream management"),
+ ok = enable_sm(Config),
+ ct:comment("Closing the socket"),
+ close_socket(Config).
+
+offline_master(Config) ->
+ ct:comment("Waiting for the slave to be ready"),
+ ready = get_event(Config),
+ ct:comment("Sending message to the slave"),
+ send_test_message(Config), % No push notification, slave is online.
+ ct:comment("Waiting for the slave to disconnect"),
+ peer_down = get_event(Config),
+ ct:comment("Sending message to offline storage"),
+ send_test_message(Config),
+ ct:comment("Handling push notification for offline message"),
+ handle_notification(Config),
+ ct:comment("Closing the connection"),
+ disconnect(Config).
+
+offline_slave(Config) ->
+ ct:comment("Re-enabling push notifications"),
+ ok = enable_push(Config),
+ ct:comment("Letting the master know that we're ready"),
+ put_event(Config, ready),
+ ct:comment("Receiving message from the master"),
+ recv_test_message(Config),
+ ct:comment("Closing the connection"),
+ disconnect(Config).
+
+mam_master(Config) ->
+ ct:comment("Waiting for the slave to be ready"),
+ ready = get_event(Config),
+ ct:comment("Sending message to the slave"),
+ send_test_message(Config),
+ ct:comment("Handling push notification for MAM message"),
+ handle_notification(Config),
+ ct:comment("Closing the connection"),
+ disconnect(Config).
+
+mam_slave(Config) ->
+ self_presence(Config, available),
+ ct:comment("Receiving message from offline storage"),
+ recv_test_message(Config),
+ %% Don't re-enable push notifications, otherwise the notification would be
+ %% suppressed while the slave is online.
+ ct:comment("Enabling MAM"),
+ ok = enable_mam(Config),
+ ct:comment("Letting the master know that we're ready"),
+ put_event(Config, ready),
+ ct:comment("Receiving message from the master"),
+ recv_test_message(Config),
+ ct:comment("Waiting for the master to disconnect"),
+ peer_down = get_event(Config),
+ ct:comment("Disabling push notifications"),
+ ok = disable_push(Config),
+ ct:comment("Closing the connection and cleaning up"),
+ clean(disconnect(Config)).
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+single_test(T) ->
+ list_to_atom("push_" ++ atom_to_list(T)).
+
+master_slave_test(T) ->
+ {list_to_atom("push_" ++ atom_to_list(T)), [parallel],
+ [list_to_atom("push_" ++ atom_to_list(T) ++ "_master"),
+ list_to_atom("push_" ++ atom_to_list(T) ++ "_slave")]}.
+
+enable_sm(Config) ->
+ send(Config, #sm_enable{xmlns = ?NS_STREAM_MGMT_3, resume = true}),
+ case recv(Config) of
+ #sm_enabled{resume = true} ->
+ ok;
+ #sm_failed{reason = Reason} ->
+ Reason
+ end.
+
+enable_mam(Config) ->
+ case send_recv(
+ Config, #iq{type = set, sub_els = [#mam_prefs{xmlns = ?NS_MAM_1,
+ default = always}]}) of
+ #iq{type = result} ->
+ ok;
+ #iq{type = error} = Err ->
+ xmpp:get_error(Err)
+ end.
+
+enable_push(Config) ->
+ %% Usually, the push JID would be a server JID (such as push.example.com).
+ %% We specify the peer's full user JID instead, so the push notifications
+ %% will be sent to the peer.
+ PushJID = ?config(peer, Config),
+ XData = #xdata{type = submit, fields = ?PUSH_XDATA_FIELDS},
+ case send_recv(
+ Config, #iq{type = set,
+ sub_els = [#push_enable{jid = PushJID,
+ node = ?PUSH_NODE,
+ xdata = XData}]}) of
+ #iq{type = result, sub_els = []} ->
+ ok;
+ #iq{type = error} = Err ->
+ xmpp:get_error(Err)
+ end.
+
+disable_push(Config) ->
+ PushJID = ?config(peer, Config),
+ case send_recv(
+ Config, #iq{type = set,
+ sub_els = [#push_disable{jid = PushJID,
+ node = ?PUSH_NODE}]}) of
+ #iq{type = result, sub_els = []} ->
+ ok;
+ #iq{type = error} = Err ->
+ xmpp:get_error(Err)
+ end.
+
+send_test_message(Config) ->
+ Peer = ?config(peer, Config),
+ Msg = #message{to = Peer, body = [#text{data = <<"test">>}]},
+ send(Config, Msg).
+
+recv_test_message(Config) ->
+ Peer = ?config(peer, Config),
+ #message{from = Peer,
+ body = [#text{data = <<"test">>}]} = recv_message(Config).
+
+handle_notification(Config) ->
+ From = server_jid(Config),
+ Item = #ps_item{xml_els = [xmpp:encode(#push_notification{})]},
+ Publish = #ps_publish{node = ?PUSH_NODE, items = [Item]},
+ XData = #xdata{type = submit, fields = ?PUSH_XDATA_FIELDS},
+ PubSub = #pubsub{publish = Publish, publish_options = XData},
+ IQ = #iq{type = set, from = From, sub_els = [PubSub]} = recv_iq(Config),
+ send(Config, make_iq_result(IQ)).
+
+clean(Config) ->
+ {U, S, _} = jid:tolower(my_jid(Config)),
+ mod_push:remove_user(U, S),
+ mod_mam:remove_user(U, S),
+ Config.