25
1
mirror of https://github.com/processone/ejabberd.git synced 2024-11-20 16:15:59 +01:00

Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Konstantinos Kallas 2017-08-19 13:42:05 +03:00
commit f581e391ac
65 changed files with 2414 additions and 813 deletions

View File

@ -4,6 +4,7 @@ otp_release:
- 17.5 - 17.5
- 18.3 - 18.3
- 19.2 - 19.2
- 20.0
services: services:
- riak - riak

View File

@ -1,7 +1,7 @@
FROM debian:jessie-slim FROM debian:jessie-slim
MAINTAINER Rafael Römhild <rafael@roemhild.de> MAINTAINER Rafael Römhild <rafael@roemhild.de>
ENV EJABBERD_BRANCH=17.04 \ ENV EJABBERD_BRANCH=17.08 \
EJABBERD_USER=ejabberd \ EJABBERD_USER=ejabberd \
EJABBERD_HTTPS=true \ EJABBERD_HTTPS=true \
EJABBERD_STARTTLS=true \ EJABBERD_STARTTLS=true \

5
README
View File

@ -116,11 +116,14 @@ To compile ejabberd you need:
needed on systems with GNU Libc. needed on systems with GNU Libc.
- ImageMagick's Convert program. Optional. For CAPTCHA challenges. - 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 ### 1. Compile and install on *nix systems
To compile ejabberd, execute the following commands. The first one is only 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 ./autogen.sh
./configure ./configure

View File

@ -3,8 +3,8 @@
AC_PREREQ(2.53) 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]) 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_MIN="6.4 (Erlang/OTP 17.5)"
REQUIRE_ERLANG_MAX="9.0.0 (No Max)" REQUIRE_ERLANG_MAX="100.0.0 (No Max)"
AC_CONFIG_MACRO_DIR([m4]) AC_CONFIG_MACRO_DIR([m4])

View File

@ -738,6 +738,8 @@ modules:
- "flat" - "flat"
- "hometree" - "hometree"
- "pep" # pep requires mod_caps - "pep" # pep requires mod_caps
mod_push: {}
mod_push_keepalive: {}
## mod_register: ## mod_register:
## ##
## Protect In-Band account registrations with CAPTCHA. ## Protect In-Band account registrations with CAPTCHA.

View File

@ -69,9 +69,7 @@ done
# define erl parameters # define erl parameters
ERLANG_OPTS="+K $POLL -smp $SMP +P $ERL_PROCESSES $ERL_OPTIONS" ERLANG_OPTS="+K $POLL -smp $SMP +P $ERL_PROCESSES $ERL_OPTIONS"
if [ "$FIREWALL_WINDOW" != "" ] ; then if [ "$FIREWALL_WINDOW" != "" ] ; then
ERLANG_OPTS="$ERLANG_OPTS -kernel \ ERLANG_OPTS="$ERLANG_OPTS -kernel inet_dist_listen_min ${FIREWALL_WINDOW%-*} inet_dist_listen_max ${FIREWALL_WINDOW#*-}"
inet_dist_listen_min ${FIREWALL_WINDOW%-*} \
inet_dist_listen_max ${FIREWALL_WINDOW#*-}"
fi fi
if [ "$INET_DIST_INTERFACE" != "" ] ; then 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) 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)

View File

@ -3,7 +3,7 @@ defmodule Ejabberd.Mixfile do
def project do def project do
[app: :ejabberd, [app: :ejabberd,
version: "17.6.0", version: "17.8.0",
description: description(), description: description(),
elixir: "~> 1.4", elixir: "~> 1.4",
elixirc_paths: ["lib"], elixirc_paths: ["lib"],

View File

@ -1,18 +1,22 @@
%{"cache_tab": {:hex, :cache_tab, "1.0.8", "eac8923f0f20c35e630317790c4d4c2629c5bc792753fa48eb5391bd39c80245", [:rebar3], [{:p1_utils, "1.0.9", [hex: :p1_utils, 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.0", "d633cd322c8efa0428082b00b7f902daf8caa166d45f9022bbc19a896d2e1e56", [:mix], []}, "distillery": {:hex, :distillery, "1.4.1", "546d851bf27ae8fe0727e10e4fc4e146ad836eecee138263a60431e688044ed3", [:mix], []},
"earmark": {:hex, :earmark, "1.2.2", "f718159d6b65068e8daeef709ccddae5f7fdc770707d82e7d126f584cd925b74", [:mix], []}, "earmark": {:hex, :earmark, "1.2.3", "206eb2e2ac1a794aa5256f3982de7a76bf4579ff91cb28d0e17ea2c9491e46a4", [: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]}]}, "eredis": {:hex, :eredis, "1.0.8", "ab4fda1c4ba7fbe6c19c26c249dc13da916d762502c4b4fa2df401a8d51c5364", [:rebar], []},
"ex_doc": {:hex, :ex_doc, "0.16.1", "b4b8a23602b4ce0e9a5a960a81260d1f7b29635b9652c67e95b0c2f7ccee5e81", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, optional: false]}]}, "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], []}, "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_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]}]}, "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], []}, "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]}]}, "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], []}, "jiffy": {:hex, :jiffy, "0.14.11", "919a87d491c5a6b5e3bbc27fafedc3a0761ca0b4c405394f121f582fd4e3f0e5", [:rebar3], []},
"lager": {:hex, :lager, "3.4.2", "150b9a17b23ae6d3265cc10dc360747621cf217b7a22b8cddf03b2909dbf7aa5", [:rebar3], [{:goldrush, "0.1.9", [hex: :goldrush, optional: false]}]}, "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_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], []}, "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]}]}, "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]}]}, "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.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]}]}} "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]}]}}

View File

@ -77,7 +77,7 @@
ezlib, ezlib,
iconv]}}. 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, {erl_opts, [nowarn_deprecated_function,
{i, "include"}, {i, "include"},
@ -93,6 +93,7 @@
{if_var_true, elixir, {d, 'ELIXIR_ENABLED'}}, {if_var_true, elixir, {d, 'ELIXIR_ENABLED'}},
{if_var_true, erlang_deprecated_types, {d, 'ERL_DEPRECATED_TYPES'}}, {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, {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, {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, short_name_hash, 1}, {d, 'SHORT_NAME_HASH'}},
%% {if_have_fun, {public_key, generate_key, 1}, {d, 'GENERATE_RSA_KEY'}}, %% {if_have_fun, {public_key, generate_key, 1}, {d, 'GENERATE_RSA_KEY'}},

View File

@ -261,12 +261,12 @@ try_register(User, Server, Password) ->
ok; ok;
(Mod, _) -> (Mod, _) ->
db_try_register( db_try_register(
User, Server, Password, Mod) LUser, LServer, Password, Mod)
end, {error, not_allowed}, end, {error, not_allowed},
auth_modules(LServer)) of auth_modules(LServer)) of
ok -> ok ->
ejabberd_hooks:run( ejabberd_hooks:run(
register_user, Server, [User, Server]); register_user, LServer, [LUser, LServer]);
{error, _} = Err -> {error, _} = Err ->
Err Err
end; end;

View File

@ -208,6 +208,8 @@ remove_user(User, Server) ->
{error, db_failure} {error, db_failure}
end. end.
need_transform(#reg_users_counter{}) ->
false;
need_transform(#passwd{us = {U, S}, password = Pass}) -> need_transform(#passwd{us = {U, S}, password = Pass}) ->
if is_binary(Pass) -> if is_binary(Pass) ->
case store_type(S) of case store_type(S) of

View File

@ -27,9 +27,7 @@
-protocol({xep, 124, '1.11'}). -protocol({xep, 124, '1.11'}).
-protocol({xep, 206, '1.4'}). -protocol({xep, 206, '1.4'}).
-define(GEN_FSM, p1_fsm). -behaviour(p1_fsm).
-behaviour(?GEN_FSM).
%% API %% API
-export([start/2, start/3, start_link/3]). -export([start/2, start/3, start_link/3]).
@ -137,18 +135,18 @@ start(#body{attrs = Attrs} = Body, IP, SID) ->
end. end.
start(StateName, State) -> start(StateName, State) ->
(?GEN_FSM):start_link(?MODULE, [StateName, State], p1_fsm:start_link(?MODULE, [StateName, State],
?FSMOPTS). ?FSMOPTS).
start_link(Body, IP, SID) -> start_link(Body, IP, SID) ->
(?GEN_FSM):start_link(?MODULE, [Body, IP, SID], p1_fsm:start_link(?MODULE, [Body, IP, SID],
?FSMOPTS). ?FSMOPTS).
send({http_bind, FsmRef, IP}, Packet) -> send({http_bind, FsmRef, IP}, Packet) ->
send_xml({http_bind, FsmRef, IP}, Packet). send_xml({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_xml, Packet},
?SEND_TIMEOUT) ?SEND_TIMEOUT)
of of
@ -160,12 +158,12 @@ send_xml({http_bind, FsmRef, _IP}, Packet) ->
setopts({http_bind, FsmRef, _IP}, Opts) -> setopts({http_bind, FsmRef, _IP}, Opts) ->
case lists:member({active, once}, Opts) of case lists:member({active, once}, Opts) of
true -> true ->
(?GEN_FSM):send_all_state_event(FsmRef, p1_fsm:send_all_state_event(FsmRef,
{activate, self()}); {activate, self()});
_ -> _ ->
case lists:member({active, false}, Opts) of case lists:member({active, false}, Opts) of
true -> true ->
case catch (?GEN_FSM):sync_send_all_state_event(FsmRef, case catch p1_fsm:sync_send_all_state_event(FsmRef,
deactivate_socket) deactivate_socket)
of of
{'EXIT', _} -> {error, einval}; {'EXIT', _} -> {error, einval};
@ -181,7 +179,7 @@ custom_receiver({http_bind, FsmRef, _IP}) ->
{receiver, ?MODULE, FsmRef}. {receiver, ?MODULE, FsmRef}.
become_controller(FsmRef, C2SPid) -> become_controller(FsmRef, C2SPid) ->
(?GEN_FSM):send_all_state_event(FsmRef, p1_fsm:send_all_state_event(FsmRef,
{become_controller, C2SPid}). {become_controller, C2SPid}).
change_controller({http_bind, FsmRef, _IP}, 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. reset_stream({http_bind, _FsmRef, _IP}) -> ok.
change_shaper({http_bind, FsmRef, _IP}, Shaper) -> change_shaper({http_bind, FsmRef, _IP}, Shaper) ->
(?GEN_FSM):send_all_state_event(FsmRef, p1_fsm:send_all_state_event(FsmRef,
{change_shaper, Shaper}). {change_shaper, Shaper}).
monitor({http_bind, FsmRef, _IP}) -> monitor({http_bind, FsmRef, _IP}) ->
erlang:monitor(process, FsmRef). erlang:monitor(process, FsmRef).
close({http_bind, FsmRef, _IP}) -> close({http_bind, FsmRef, _IP}) ->
catch (?GEN_FSM):sync_send_all_state_event(FsmRef, catch p1_fsm:sync_send_all_state_event(FsmRef,
close). close).
sockname(_Socket) -> {ok, {{0, 0, 0, 0}, 0}}. sockname(_Socket) -> {ok, {{0, 0, 0, 0}, 0}}.
@ -269,7 +267,7 @@ process_request(Data, IP, Type) ->
end. end.
process_request(Pid, Req, _IP, Type) -> 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) infinity)
of of
#body{} = Resp -> bosh_response(Resp, Type); #body{} = Resp -> bosh_response(Resp, Type);
@ -571,7 +569,7 @@ handle_sync_event({send_xml, El}, _From, StateName,
of of
{{value, {TRef, From, Body}}, Q} -> {{value, {TRef, From, Body}}, Q} ->
cancel_timer(TRef), cancel_timer(TRef),
(?GEN_FSM):send_event(self(), {Body, From}), p1_fsm:send_event(self(), {Body, From}),
State1#state{shaped_receivers = Q}; State1#state{shaped_receivers = Q};
_ -> State1 _ -> State1
end, end,
@ -598,7 +596,7 @@ handle_info({timeout, TRef, shaper_timeout}, StateName,
State) -> State) ->
case p1_queue:out(State#state.shaped_receivers) of case p1_queue:out(State#state.shaped_receivers) of
{{value, {TRef, From, Req}}, Q} -> {{value, {TRef, From, Req}}, Q} ->
(?GEN_FSM):send_event(self(), {Req, From}), p1_fsm:send_event(self(), {Req, From}),
{next_state, StateName, {next_state, StateName,
State#state{shaped_receivers = Q}}; State#state{shaped_receivers = Q}};
{{value, _}, _} -> {{value, _}, _} ->
@ -630,7 +628,7 @@ terminate(_Reason, _StateName, State) ->
mod_bosh:close_session(State#state.sid), mod_bosh:close_session(State#state.sid),
case State#state.c2s_pid of case State#state.c2s_pid of
C2SPid when is_pid(C2SPid) -> C2SPid when is_pid(C2SPid) ->
(?GEN_FSM):send_event(C2SPid, closed); p1_fsm:send_event(C2SPid, closed);
_ -> ok _ -> ok
end, end,
bounce_receivers(State, closed), bounce_receivers(State, closed),
@ -644,7 +642,7 @@ print_state(State) -> State.
route_els(#state{el_ibuf = Buf, c2s_pid = C2SPid} = State) -> route_els(#state{el_ibuf = Buf, c2s_pid = C2SPid} = State) ->
NewBuf = p1_queue:dropwhile( NewBuf = p1_queue:dropwhile(
fun(El) -> fun(El) ->
?GEN_FSM:send_event(C2SPid, El), p1_fsm:send_event(C2SPid, El),
true true
end, Buf), end, Buf),
State#state{el_ibuf = NewBuf}. State#state{el_ibuf = NewBuf}.
@ -653,7 +651,7 @@ route_els(State, Els) ->
case State#state.c2s_pid of case State#state.c2s_pid of
C2SPid when is_pid(C2SPid) -> C2SPid when is_pid(C2SPid) ->
lists:foreach(fun (El) -> lists:foreach(fun (El) ->
(?GEN_FSM):send_event(C2SPid, El) p1_fsm:send_event(C2SPid, El)
end, end,
Els), Els),
State; State;
@ -676,7 +674,7 @@ reply(State, Body, RID, From) ->
case catch gb_trees:take_smallest(Receivers) of case catch gb_trees:take_smallest(Receivers) of
{NextRID, {From1, Req}, Receivers1} {NextRID, {From1, Req}, Receivers1}
when NextRID == RID + 1 -> 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 = Receivers1};
_ -> State2#state{receivers = Receivers} _ -> State2#state{receivers = Receivers}
end. end.
@ -715,7 +713,7 @@ do_reply(State, From, Body, RID) ->
?DEBUG("send reply:~n** RequestID: ~p~n** Reply: " ?DEBUG("send reply:~n** RequestID: ~p~n** Reply: "
"~p~n** To: ~p~n** State: ~p", "~p~n** To: ~p~n** State: ~p",
[RID, Body, From, State]), [RID, Body, From, State]),
(?GEN_FSM):reply(From, Body), p1_fsm:reply(From, Body),
Responses = gb_trees:delete_any(RID, Responses = gb_trees:delete_any(RID,
State#state.responses), State#state.responses),
Responses1 = case gb_trees:size(Responses) of Responses1 = case gb_trees:size(Responses) of
@ -1053,7 +1051,7 @@ buf_out(Buf, I, Els) ->
end. end.
cancel_timer(TRef) when is_reference(TRef) -> cancel_timer(TRef) when is_reference(TRef) ->
(?GEN_FSM):cancel_timer(TRef); p1_fsm:cancel_timer(TRef);
cancel_timer(_) -> false. cancel_timer(_) -> false.
restart_timer(TRef, Timeout, Msg) -> restart_timer(TRef, Timeout, Msg) ->

View File

@ -47,7 +47,7 @@
process_terminated/2, process_info/2]). process_terminated/2, process_info/2]).
%% API %% API
-export([get_presence/1, set_presence/2, resend_presence/1, resend_presence/2, -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, reply/2, copy_state/2, set_timeout/2, route/2,
host_up/1, host_down/1]). host_up/1, host_down/1]).
@ -90,6 +90,10 @@ socket_type() ->
call(Ref, Msg, Timeout) -> call(Ref, Msg, Timeout) ->
xmpp_stream_in: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) -> reply(Ref, Reply) ->
xmpp_stream_in:reply(Ref, Reply). xmpp_stream_in:reply(Ref, Reply).
@ -293,14 +297,19 @@ process_terminated(State, _Reason) ->
%%%=================================================================== %%%===================================================================
%%% xmpp_stream_in callbacks %%% xmpp_stream_in callbacks
%%%=================================================================== %%%===================================================================
tls_options(#{lserver := LServer, tls_options := DefaultOpts}) -> tls_options(#{lserver := LServer, tls_options := DefaultOpts,
TLSOpts1 = case ejabberd_config:get_option( stream_encrypted := Encrypted}) ->
{c2s_certfile, LServer}, TLSOpts1 = case {Encrypted, proplists:get_value(certfile, DefaultOpts)} of
ejabberd_config:get_option( {true, CertFile} when CertFile /= undefined -> DefaultOpts;
{domain_certfile, LServer})) of {_, _} ->
undefined -> DefaultOpts; case ejabberd_config:get_option(
CertFile -> lists:keystore(certfile, 1, DefaultOpts, {domain_certfile, LServer},
{certfile, CertFile}) ejabberd_config:get_option(
{c2s_certfile, LServer})) of
undefined -> DefaultOpts;
CertFile -> lists:keystore(certfile, 1, DefaultOpts,
{certfile, CertFile})
end
end, end,
TLSOpts2 = case ejabberd_config:get_option( TLSOpts2 = case ejabberd_config:get_option(
{c2s_ciphers, LServer}) of {c2s_ciphers, LServer}) of

View File

@ -388,29 +388,24 @@ get_transfer_protocol(PortString) ->
get_port_listeners(PortNumber) -> get_port_listeners(PortNumber) ->
AllListeners = ejabberd_config:get_option(listen, []), AllListeners = ejabberd_config:get_option(listen, []),
lists:filter(fun (Listener) when is_list(Listener) -> lists:filter(
case proplists:get_value(port, Listener) of fun({{Port, _IP, _Transport}, _Module, _Opts}) ->
PortNumber -> true; Port == PortNumber
_ -> false end, AllListeners).
end;
(_) -> false
end,
AllListeners).
get_captcha_transfer_protocol([]) -> get_captcha_transfer_protocol([]) ->
throw(<<"The port number mentioned in captcha_host " throw(<<"The port number mentioned in captcha_host "
"is not a ejabberd_http listener with " "is not a ejabberd_http listener with "
"'captcha' option. Change the port number " "'captcha' option. Change the port number "
"or specify http:// in that option.">>); "or specify http:// in that option.">>);
get_captcha_transfer_protocol([Listener | Listeners]) when is_list(Listener) -> get_captcha_transfer_protocol([{_, ejabberd_http, Opts} | Listeners]) ->
case proplists:get_value(module, Listener) == ejabberd_http andalso case proplists:get_bool(captcha, Opts) of
proplists:get_bool(captcha, Listener) of
true -> true ->
case proplists:get_bool(tls, Listener) of case proplists:get_bool(tls, Opts) of
true -> https; true -> https;
false -> http false -> http
end; end;
false -> get_captcha_transfer_protocol(Listeners) false -> get_captcha_transfer_protocol(Listeners)
end; end;
get_captcha_transfer_protocol([_ | Listeners]) -> get_captcha_transfer_protocol([_ | Listeners]) ->
get_captcha_transfer_protocol(Listeners). get_captcha_transfer_protocol(Listeners).

View File

@ -28,7 +28,7 @@
-export([start/0, load_file/1, reload_file/0, read_file/1, -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_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_version/0, get_myhosts/0, get_mylang/0, get_lang/1,
get_ejabberd_config_path/0, is_using_elixir_config/0, get_ejabberd_config_path/0, is_using_elixir_config/0,
prepare_opt_val/4, transform_options/1, collect_options/1, 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/2, get_local_option/2,
get_global_option/3, get_local_option/3, get_global_option/3, get_local_option/3,
get_option/3]). get_option/3]).
-export([is_file_readable/1]).
-deprecated([{add_global_option, 2}, {add_local_option, 2}, -deprecated([{add_global_option, 2}, {add_local_option, 2},
{get_global_option, 2}, {get_local_option, 2}, {get_global_option, 2}, {get_local_option, 2},
{get_global_option, 3}, {get_local_option, 3}, {get_global_option, 3}, {get_local_option, 3},
{get_option, 3}]). {get_option, 3}, {is_file_readable, 1}]).
-include("ejabberd.hrl"). -include("ejabberd.hrl").
-include("logger.hrl"). -include("logger.hrl").

View File

@ -28,7 +28,7 @@
-author('ecestari@process-one.net'). -author('ecestari@process-one.net').
-behaviour(gen_fsm). -behaviour(p1_fsm).
-export([start/1, start_link/1, init/1, handle_event/3, -export([start/1, start_link/1, init/1, handle_event/3,
handle_sync_event/4, code_change/4, handle_info/3, handle_sync_event/4, code_change/4, handle_info/3,
@ -75,13 +75,13 @@
-export_type([ws_socket/0]). -export_type([ws_socket/0]).
start(WS) -> start(WS) ->
gen_fsm:start(?MODULE, [WS], ?FSMOPTS). p1_fsm:start(?MODULE, [WS], ?FSMOPTS).
start_link(WS) -> start_link(WS) ->
gen_fsm:start_link(?MODULE, [WS], ?FSMOPTS). p1_fsm:start_link(?MODULE, [WS], ?FSMOPTS).
send_xml({http_ws, FsmRef, _IP}, Packet) -> 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}, {send_xml, Packet},
15000) 15000)
of of
@ -93,7 +93,7 @@ send_xml({http_ws, FsmRef, _IP}, Packet) ->
setopts({http_ws, FsmRef, _IP}, Opts) -> setopts({http_ws, FsmRef, _IP}, Opts) ->
case lists:member({active, once}, Opts) of case lists:member({active, once}, Opts) of
true -> true ->
gen_fsm:send_all_state_event(FsmRef, p1_fsm:send_all_state_event(FsmRef,
{activate, self()}); {activate, self()});
_ -> ok _ -> ok
end. end.
@ -105,11 +105,11 @@ peername({http_ws, _FsmRef, IP}) -> {ok, IP}.
controlling_process(_Socket, _Pid) -> ok. controlling_process(_Socket, _Pid) -> ok.
become_controller(FsmRef, C2SPid) -> become_controller(FsmRef, C2SPid) ->
gen_fsm:send_all_state_event(FsmRef, p1_fsm:send_all_state_event(FsmRef,
{become_controller, C2SPid}). {become_controller, C2SPid}).
close({http_ws, FsmRef, _IP}) -> 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) -> socket_handoff(LocalPath, Request, Socket, SockMod, Buf, Opts) ->
ejabberd_websocket:socket_handoff(LocalPath, Request, Socket, SockMod, 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}}; StateData2#state{pong_expected = false}};
handle_info({timeout, Timer, _}, _StateName, handle_info({timeout, Timer, _}, _StateName,
#state{timer = Timer} = StateData) -> #state{timer = Timer} = StateData) ->
?DEBUG("Closing websocket connection from hitting inactivity timeout", []),
{stop, normal, StateData}; {stop, normal, StateData};
handle_info({timeout, Timer, _}, StateName, handle_info({timeout, Timer, _}, StateName,
#state{ping_timer = Timer, ws = {_, WsPid}} = StateData) -> #state{ping_timer = Timer, ws = {_, WsPid}} = StateData) ->
@ -253,6 +254,7 @@ handle_info({timeout, Timer, _}, StateName,
{next_state, StateName, {next_state, StateName,
StateData#state{ping_timer = PingTimer, pong_expected = true}}; StateData#state{ping_timer = PingTimer, pong_expected = true}};
true -> true ->
?DEBUG("Closing websocket connection from missing pongs", []),
{stop, normal, StateData} {stop, normal, StateData}
end; end;
handle_info(_, StateName, StateData) -> handle_info(_, StateName, StateData) ->

View File

@ -151,6 +151,9 @@ do_start() ->
application:set_env(lager, crash_log_size, LogRotateSize), application:set_env(lager, crash_log_size, LogRotateSize),
application:set_env(lager, crash_log_count, LogRotateCount), application:set_env(lager, crash_log_count, LogRotateCount),
ejabberd:start_app(lager), ejabberd:start_app(lager),
lists:foreach(fun(Handler) ->
lager:set_loghwm(Handler, LogRateLimit)
end, gen_event:which_handlers(lager_event)),
ok. ok.
%% @spec () -> ok %% @spec () -> ok

View File

@ -234,7 +234,7 @@ terminate(_Reason,
State) -> State) ->
close_stream(XMLStreamState), close_stream(XMLStreamState),
if C2SPid /= undefined -> if C2SPid /= undefined ->
gen_fsm:send_event(C2SPid, closed); p1_fsm:send_event(C2SPid, closed);
true -> ok true -> ok
end, end,
catch (State#state.sock_mod):close(State#state.socket), catch (State#state.sock_mod):close(State#state.socket),
@ -272,7 +272,7 @@ process_data([Element | Els],
element(1, Element) == xmlstreamend -> element(1, Element) == xmlstreamend ->
if C2SPid == undefined -> State; if C2SPid == undefined -> State;
true -> true ->
catch gen_fsm:send_event(C2SPid, catch p1_fsm:send_event(C2SPid,
element_wrapper(Element)), element_wrapper(Element)),
process_data(Els, State) process_data(Els, State)
end; end;

View File

@ -25,7 +25,7 @@
-module(ejabberd_regexp). -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}) -> exec({ReM, ReF, ReA}, {RgM, RgF, RgA}) ->
try apply(ReM, ReF, ReA) catch try apply(ReM, ReF, ReA) catch

View File

@ -39,7 +39,7 @@
remove_connection/2, start_connection/2, start_connection/3, remove_connection/2, start_connection/2, start_connection/3,
dirty_get_connections/0, allow_host/2, dirty_get_connections/0, allow_host/2,
incoming_s2s_number/0, outgoing_s2s_number/0, incoming_s2s_number/0, outgoing_s2s_number/0,
stop_all_connections/0, stop_s2s_connections/0,
clean_temporarily_blocked_table/0, clean_temporarily_blocked_table/0,
list_temporarily_blocked_hosts/0, list_temporarily_blocked_hosts/0,
external_host_overloaded/1, is_temporarly_blocked/1, external_host_overloaded/1, is_temporarly_blocked/1,
@ -199,9 +199,9 @@ dirty_get_connections() ->
-spec tls_options(binary(), [proplists:property()]) -> [proplists:property()]. -spec tls_options(binary(), [proplists:property()]) -> [proplists:property()].
tls_options(LServer, DefaultOpts) -> tls_options(LServer, DefaultOpts) ->
TLSOpts1 = case ejabberd_config:get_option( TLSOpts1 = case ejabberd_config:get_option(
{s2s_certfile, LServer}, {domain_certfile, LServer},
ejabberd_config:get_option( ejabberd_config:get_option(
{domain_certfile, LServer})) of {s2s_certfile, LServer})) of
undefined -> DefaultOpts; undefined -> DefaultOpts;
CertFile -> lists:keystore(certfile, 1, DefaultOpts, CertFile -> lists:keystore(certfile, 1, DefaultOpts,
{certfile, CertFile}) {certfile, CertFile})
@ -558,10 +558,10 @@ get_commands_spec() ->
module = ?MODULE, function = outgoing_s2s_number, module = ?MODULE, function = outgoing_s2s_number,
args = [], result = {s2s_outgoing, integer}}, args = [], result = {s2s_outgoing, integer}},
#ejabberd_commands{ #ejabberd_commands{
name = stop_all_connections, tags = [s2s], name = stop_s2s_connections, tags = [s2s],
desc = "Stop all outgoing and incoming connections", desc = "Stop all s2s outgoing and incoming connections",
policy = admin, policy = admin,
module = ?MODULE, function = stop_all_connections, module = ?MODULE, function = stop_s2s_connections,
args = [], result = {res, rescode}}]. args = [], result = {res, rescode}}].
%% TODO Move those stats commands to ejabberd stats command ? %% TODO Move those stats commands to ejabberd stats command ?
@ -578,8 +578,8 @@ supervisor_count(Supervisor) ->
length(Result) length(Result)
end. end.
-spec stop_all_connections() -> ok. -spec stop_s2s_connections() -> ok.
stop_all_connections() -> stop_s2s_connections() ->
lists:foreach( lists:foreach(
fun({_Id, Pid, _Type, _Module}) -> fun({_Id, Pid, _Type, _Module}) ->
supervisor:terminate_child(ejabberd_s2s_in_sup, Pid) 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()}]. -spec get_s2s_state(pid()) -> [{status, open | closed | error} | {s2s_pid, pid()}].
get_s2s_state(S2sPid) -> 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) get_state_infos)
of of
{state_infos, Is} -> [{status, open} | Is]; {state_infos, Is} -> [{status, open} | Is];

View File

@ -374,7 +374,7 @@ mk_bounce_error(_Lang, _State) ->
-spec get_delay() -> non_neg_integer(). -spec get_delay() -> non_neg_integer().
get_delay() -> get_delay() ->
MaxDelay = ejabberd_config:get_option(s2s_max_retry_delay, 300), MaxDelay = ejabberd_config:get_option(s2s_max_retry_delay, 300),
crypto:rand_uniform(1, MaxDelay). randoms:uniform(MaxDelay).
-spec set_idle_timeout(state()) -> state(). -spec set_idle_timeout(state()) -> state().
set_idle_timeout(#{on_route := send, server := LServer} = State) -> set_idle_timeout(#{on_route := send, server := LServer} = State) ->

View File

@ -66,6 +66,8 @@
user_resources/2, user_resources/2,
kick_user/2, kick_user/2,
get_session_pid/3, get_session_pid/3,
get_session_sid/3,
get_session_sids/2,
get_user_info/2, get_user_info/2,
get_user_info/3, get_user_info/3,
get_user_ip/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(). -spec get_session_pid(binary(), binary(), binary()) -> none | pid().
get_session_pid(User, Server, Resource) -> 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), LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server), LServer = jid:nameprep(Server),
LResource = jid:resourceprep(Resource), LResource = jid:resourceprep(Resource),
Mod = get_sm_backend(LServer), Mod = get_sm_backend(LServer),
case online(get_sessions(Mod, LUser, LServer, LResource)) of case online(get_sessions(Mod, LUser, LServer, LResource)) of
[#session{sid = {_, Pid}}] -> Pid; [#session{sid = SID}] -> SID;
_ -> none _ -> none
end. 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. -spec set_offline_info(sid(), binary(), binary(), binary(), info()) -> ok.
set_offline_info(SID, User, Server, Resource, Info) -> set_offline_info(SID, User, Server, Resource, Info) ->
@ -471,9 +490,13 @@ host_up(Host) ->
-spec host_down(binary()) -> ok. -spec host_down(binary()) -> ok.
host_down(Host) -> host_down(Host) ->
Mod = get_sm_backend(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( lists:foreach(
fun(#session{sid = {_, Pid}}) when node(Pid) == node() -> 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); ejabberd_c2s:stop(Pid);
(_) -> (_) ->
ok ok
@ -972,7 +995,7 @@ get_commands_spec() ->
args = [], result = {num_sessions, integer}}, args = [], result = {num_sessions, integer}},
#ejabberd_commands{name = user_resources, tags = [session], #ejabberd_commands{name = user_resources, tags = [session],
desc = "List user's connected resources", desc = "List user's connected resources",
policy = user, policy = admin,
module = ?MODULE, function = user_resources, module = ?MODULE, function = user_resources,
args = [{user, binary}, {host, binary}], args = [{user, binary}, {host, binary}],
args_desc = ["User name", "Server name"], args_desc = ["User name", "Server name"],

View File

@ -29,9 +29,7 @@
-author('alexey@process-one.net'). -author('alexey@process-one.net').
-define(GEN_FSM, p1_fsm). -behaviour(p1_fsm).
-behaviour(?GEN_FSM).
%% External exports %% External exports
-export([start/1, start_link/2, -export([start/1, start_link/2,
@ -113,11 +111,11 @@
%%% API %%% API
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
start(Host) -> start(Host) ->
(?GEN_FSM):start(ejabberd_sql, [Host], p1_fsm:start(ejabberd_sql, [Host],
fsm_limit_opts() ++ (?FSMOPTS)). fsm_limit_opts() ++ (?FSMOPTS)).
start_link(Host, StartInterval) -> start_link(Host, StartInterval) ->
(?GEN_FSM):start_link(ejabberd_sql, p1_fsm:start_link(ejabberd_sql,
[Host, StartInterval], [Host, StartInterval],
fsm_limit_opts() ++ (?FSMOPTS)). fsm_limit_opts() ++ (?FSMOPTS)).
@ -160,7 +158,7 @@ sql_call(Host, Msg) ->
case ejabberd_sql_sup:get_random_pid(Host) of case ejabberd_sql_sup:get_random_pid(Host) of
none -> {error, <<"Unknown Host">>}; none -> {error, <<"Unknown Host">>};
Pid -> 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)}, p1_time_compat:monotonic_time(milli_seconds)},
query_timeout(Host)) query_timeout(Host))
end; end;
@ -168,7 +166,7 @@ sql_call(Host, Msg) ->
end. end.
keep_alive(Host, PID) -> keep_alive(Host, PID) ->
(?GEN_FSM):sync_send_event(PID, p1_fsm:sync_send_event(PID,
{sql_cmd, {sql_query, ?KEEPALIVE_QUERY}, {sql_cmd, {sql_query, ?KEEPALIVE_QUERY},
p1_time_compat:monotonic_time(milli_seconds)}, p1_time_compat:monotonic_time(milli_seconds)},
query_timeout(Host)). query_timeout(Host)).
@ -280,7 +278,7 @@ init([Host, StartInterval]) ->
keep_alive, [Host, self()]) keep_alive, [Host, self()])
end, end,
[DBType | _] = db_opts(Host), [DBType | _] = db_opts(Host),
(?GEN_FSM):send_event(self(), connect), p1_fsm:send_event(self(), connect),
ejabberd_sql_sup:add_pid(Host, self()), ejabberd_sql_sup:add_pid(Host, self()),
QueueType = case ejabberd_config:get_option({sql_queue_type, Host}) of QueueType = case ejabberd_config:get_option({sql_queue_type, Host}) of
undefined -> undefined ->
@ -313,7 +311,7 @@ connecting(connect, #state{host = Host} = State) ->
PendingRequests = PendingRequests =
p1_queue:dropwhile( p1_queue:dropwhile(
fun(Req) -> fun(Req) ->
?GEN_FSM:send_event(self(), Req), p1_fsm:send_event(self(), Req),
true true
end, State#state.pending_requests), end, State#state.pending_requests),
State1 = State#state{db_ref = Ref, State1 = State#state{db_ref = Ref,
@ -325,7 +323,7 @@ connecting(connect, #state{host = Host} = State) ->
"Retry after: ~p seconds", "Retry after: ~p seconds",
[State#state.db_type, Reason, [State#state.db_type, Reason,
State#state.start_interval div 1000]), 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), connect),
{next_state, connecting, State} {next_state, connecting, State}
end; end;
@ -337,7 +335,7 @@ connecting(Event, State) ->
connecting({sql_cmd, {sql_query, ?KEEPALIVE_QUERY}, connecting({sql_cmd, {sql_query, ?KEEPALIVE_QUERY},
_Timestamp}, _Timestamp},
From, State) -> From, State) ->
(?GEN_FSM):reply(From, p1_fsm:reply(From,
{error, <<"SQL connection failed">>}), {error, <<"SQL connection failed">>}),
{next_state, connecting, State}; {next_state, connecting, State};
connecting({sql_cmd, Command, Timestamp} = Req, From, connecting({sql_cmd, Command, Timestamp} = Req, From,
@ -350,7 +348,7 @@ connecting({sql_cmd, Command, Timestamp} = Req, From,
catch error:full -> catch error:full ->
Q = p1_queue:dropwhile( Q = p1_queue:dropwhile(
fun({sql_cmd, _, To, _Timestamp}) -> fun({sql_cmd, _, To, _Timestamp}) ->
(?GEN_FSM):reply( p1_fsm:reply(
To, {error, <<"SQL connection failed">>}), To, {error, <<"SQL connection failed">>}),
true true
end, State#state.pending_requests), end, State#state.pending_requests),
@ -393,7 +391,7 @@ code_change(_OldVsn, StateName, State, _Extra) ->
%% monitoring the connection) %% monitoring the connection)
handle_info({'DOWN', _MonitorRef, process, _Pid, _Info}, handle_info({'DOWN', _MonitorRef, process, _Pid, _Info},
_StateName, State) -> _StateName, State) ->
(?GEN_FSM):send_event(self(), connect), p1_fsm:send_event(self(), connect),
{next_state, connecting, State}; {next_state, connecting, State};
handle_info(Info, StateName, State) -> handle_info(Info, StateName, State) ->
?WARNING_MSG("unexpected info in ~p: ~p", ?WARNING_MSG("unexpected info in ~p: ~p",
@ -734,16 +732,16 @@ sql_query_to_iolist(SQLQuery) ->
abort_on_driver_error({error, <<"query timed out">>} = abort_on_driver_error({error, <<"query timed out">>} =
Reply, Reply,
From) -> From) ->
(?GEN_FSM):reply(From, Reply), p1_fsm:reply(From, Reply),
{stop, timeout, get(?STATE_KEY)}; {stop, timeout, get(?STATE_KEY)};
abort_on_driver_error({error, abort_on_driver_error({error,
<<"Failed sending data on socket", _/binary>>} = <<"Failed sending data on socket", _/binary>>} =
Reply, Reply,
From) -> From) ->
(?GEN_FSM):reply(From, Reply), p1_fsm:reply(From, Reply),
{stop, closed, get(?STATE_KEY)}; {stop, closed, get(?STATE_KEY)};
abort_on_driver_error(Reply, From) -> abort_on_driver_error(Reply, From) ->
(?GEN_FSM):reply(From, Reply), p1_fsm:reply(From, Reply),
{next_state, session_established, get(?STATE_KEY)}. {next_state, session_established, get(?STATE_KEY)}.
%% == pure ODBC code %% == pure ODBC code

View File

@ -58,6 +58,7 @@ modules() ->
mod_offline, mod_offline,
mod_privacy, mod_privacy,
mod_private, mod_private,
mod_pubsub,
mod_roster, mod_roster,
mod_shared_roster, mod_shared_roster,
mod_vcard]. mod_vcard].
@ -80,8 +81,8 @@ export(Server, Output, Module) ->
case export(LServer, Table, IO, ConvertFun) of case export(LServer, Table, IO, ConvertFun) of
{atomic, ok} -> ok; {atomic, ok} -> ok;
{aborted, Reason} -> {aborted, Reason} ->
?ERROR_MSG("Failed export for module ~p: ~p", ?ERROR_MSG("Failed export for module ~p and table ~p: ~p",
[Module, Reason]) [Module, Table, Reason])
end end
end, Module:export(Server)), end, Module:export(Server)),
close_output(Output, IO). close_output(Output, IO).

View File

@ -63,7 +63,7 @@
%%% active_bind - sent bind() request and waiting for response %%% active_bind - sent bind() request and waiting for response
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
-behaviour(gen_fsm). -behaviour(p1_fsm).
-include("ejabberd.hrl"). -include("ejabberd.hrl").
-include("logger.hrl"). -include("logger.hrl").
@ -148,7 +148,7 @@
start_link(Name) -> start_link(Name) ->
Reg_name = misc:binary_to_atom(<<"eldap_", Reg_name = misc:binary_to_atom(<<"eldap_",
Name/binary>>), 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(), -spec start_link(binary(), [binary()], inet:port_number(), binary(),
binary(), tlsopts()) -> any(). binary(), tlsopts()) -> any().
@ -156,7 +156,7 @@ start_link(Name) ->
start_link(Name, Hosts, Port, Rootdn, Passwd, Opts) -> start_link(Name, Hosts, Port, Rootdn, Passwd, Opts) ->
Reg_name = misc:binary_to_atom(<<"eldap_", Reg_name = misc:binary_to_atom(<<"eldap_",
Name/binary>>), Name/binary>>),
gen_fsm:start_link({local, Reg_name}, ?MODULE, p1_fsm:start_link({local, Reg_name}, ?MODULE,
[Hosts, Port, Rootdn, Passwd, Opts], []). [Hosts, Port, Rootdn, Passwd, Opts], []).
-spec get_status(handle()) -> any(). -spec get_status(handle()) -> any().
@ -166,7 +166,7 @@ start_link(Name, Hosts, Port, Rootdn, Passwd, Opts) ->
%%% -------------------------------------------------------------------- %%% --------------------------------------------------------------------
get_status(Handle) -> get_status(Handle) ->
Handle1 = get_handle(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. %%% Shutdown connection (and process) asynchronous.
@ -175,7 +175,7 @@ get_status(Handle) ->
close(Handle) -> close(Handle) ->
Handle1 = get_handle(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 %%% Add an entry. The entry field MUST NOT exist for the AddRequest
@ -192,7 +192,7 @@ close(Handle) ->
%%% -------------------------------------------------------------------- %%% --------------------------------------------------------------------
add(Handle, Entry, Attributes) -> add(Handle, Entry, Attributes) ->
Handle1 = get_handle(Handle), Handle1 = get_handle(Handle),
gen_fsm:sync_send_event(Handle1, p1_fsm:sync_send_event(Handle1,
{add, Entry, add_attrs(Attributes)}, ?CALL_TIMEOUT). {add, Entry, add_attrs(Attributes)}, ?CALL_TIMEOUT).
%%% Do sanity check ! %%% Do sanity check !
@ -216,7 +216,7 @@ add_attrs(Attrs) ->
%%% -------------------------------------------------------------------- %%% --------------------------------------------------------------------
delete(Handle, Entry) -> delete(Handle, Entry) ->
Handle1 = get_handle(Handle), Handle1 = get_handle(Handle),
gen_fsm:sync_send_event(Handle1, {delete, Entry}, p1_fsm:sync_send_event(Handle1, {delete, Entry},
?CALL_TIMEOUT). ?CALL_TIMEOUT).
%%% -------------------------------------------------------------------- %%% --------------------------------------------------------------------
@ -234,7 +234,7 @@ delete(Handle, Entry) ->
modify(Handle, Object, Mods) -> modify(Handle, Object, Mods) ->
Handle1 = get_handle(Handle), Handle1 = get_handle(Handle),
gen_fsm:sync_send_event(Handle1, {modify, Object, Mods}, p1_fsm:sync_send_event(Handle1, {modify, Object, Mods},
?CALL_TIMEOUT). ?CALL_TIMEOUT).
%%% %%%
@ -274,7 +274,7 @@ m(Operation, Type, Values) ->
modify_dn(Handle, Entry, NewRDN, DelOldRDN, NewSup) -> modify_dn(Handle, Entry, NewRDN, DelOldRDN, NewSup) ->
Handle1 = get_handle(Handle), Handle1 = get_handle(Handle),
gen_fsm:sync_send_event(Handle1, p1_fsm:sync_send_event(Handle1,
{modify_dn, Entry, NewRDN, bool_p(DelOldRDN), {modify_dn, Entry, NewRDN, bool_p(DelOldRDN),
optional(NewSup)}, optional(NewSup)},
?CALL_TIMEOUT). ?CALL_TIMEOUT).
@ -283,7 +283,7 @@ modify_dn(Handle, Entry, NewRDN, DelOldRDN, NewSup) ->
modify_passwd(Handle, DN, Passwd) -> modify_passwd(Handle, DN, Passwd) ->
Handle1 = get_handle(Handle), Handle1 = get_handle(Handle),
gen_fsm:sync_send_event(Handle1, p1_fsm:sync_send_event(Handle1,
{modify_passwd, DN, Passwd}, ?CALL_TIMEOUT). {modify_passwd, DN, Passwd}, ?CALL_TIMEOUT).
%%% -------------------------------------------------------------------- %%% --------------------------------------------------------------------
@ -298,7 +298,7 @@ modify_passwd(Handle, DN, Passwd) ->
bind(Handle, RootDN, Passwd) -> bind(Handle, RootDN, Passwd) ->
Handle1 = get_handle(Handle), Handle1 = get_handle(Handle),
gen_fsm:sync_send_event(Handle1, {bind, RootDN, Passwd}, p1_fsm:sync_send_event(Handle1, {bind, RootDN, Passwd},
?CALL_TIMEOUT). ?CALL_TIMEOUT).
%%% Sanity checks ! %%% Sanity checks !
@ -356,7 +356,7 @@ search(Handle, L) when is_list(L) ->
call_search(Handle, A) -> call_search(Handle, A) ->
Handle1 = get_handle(Handle), Handle1 = get_handle(Handle),
gen_fsm:sync_send_event(Handle1, {search, A}, p1_fsm:sync_send_event(Handle1, {search, A},
?CALL_TIMEOUT). ?CALL_TIMEOUT).
-spec parse_search_args(search_args()) -> eldap_search(). -spec parse_search_args(search_args()) -> eldap_search().
@ -637,7 +637,7 @@ active(Event, From, S) ->
%%---------------------------------------------------------------------- %%----------------------------------------------------------------------
%% Func: handle_event/3 %% 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} | %% Returns: {next_state, NextStateName, NextStateData} |
%% {next_state, NextStateName, NextStateData, Timeout} | %% {next_state, NextStateName, NextStateData, Timeout} |
%% {stop, Reason, NewStateData} %% {stop, Reason, NewStateData}
@ -680,7 +680,7 @@ handle_info({Tag, _Socket, Data}, StateName, S)
case catch recvd_packet(Data, S) of case catch recvd_packet(Data, S) of
{response, Response, RequestType} -> {response, Response, RequestType} ->
NewS = case Response of 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 {ok, S1} -> S1
end, end,
if StateName == active_bind andalso if StateName == active_bind andalso
@ -709,7 +709,7 @@ handle_info({timeout, Timer, {cmd_timeout, Id}},
StateName, S) -> StateName, S) ->
case cmd_timeout(Timer, Id, S) of case cmd_timeout(Timer, Id, S) of
{reply, To, Reason, NewS} -> {reply, To, Reason, NewS} ->
gen_fsm:reply(To, Reason), p1_fsm:reply(To, Reason),
{next_state, StateName, NewS}; {next_state, StateName, NewS};
{error, _Reason} -> {next_state, StateName, S} {error, _Reason} -> {next_state, StateName, S}
end; end;

View File

@ -146,16 +146,29 @@ get_commands_spec() ->
%% -- public modules functions %% -- public modules functions
update() -> 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) -> Res = lists:foldl(fun({Package, Spec}, Acc) ->
Path = proplists:get_value(url, Spec, ""), Repo = proplists:get_value(url, Spec, ""),
Update = add_sources(Package, Path), Update = add_sources(Package, Repo),
?INFO_MSG("Update package ~s: ~p", [Package, Update]), ?INFO_MSG("Update package ~s: ~p", [Package, Update]),
case Update of case Update of
ok -> Acc; ok -> Acc;
Error -> [Error|Acc] Error -> [{Package, Repo, Error}|Acc]
end end
end, [], modules_spec(sources_dir(), "*")), end, Repos, modules_spec(sources_dir(), "*")),
case Res of case Res of
[] -> ok; [] -> ok;
[Error|_] -> Error [Error|_] -> Error
@ -547,7 +560,9 @@ compile_result(Results) ->
compile_options() -> compile_options() ->
[verbose, report_errors, report_warnings] [verbose, report_errors, report_warnings]
++ [{i, filename:join(app_dir(App), "include")} ++ [{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) -> app_dir(App) ->
case code:lib_dir(App) of case code:lib_dir(App) of
@ -562,6 +577,10 @@ app_dir(App) ->
Dir Dir
end. 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_erlang_file(Dest, File, compile_options()). compile_erlang_file(Dest, File, compile_options()).

View File

@ -34,7 +34,8 @@
stop_child/1, stop_child/2, config_reloaded/0]). stop_child/1, stop_child/2, config_reloaded/0]).
-export([start_module/2, start_module/3, -export([start_module/2, start_module/3,
stop_module/2, stop_module_keep_config/2, 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, get_module_opt/3, get_module_opt/4, get_module_opt_host/3,
loaded_modules/1, loaded_modules_with_opts/1, loaded_modules/1, loaded_modules_with_opts/1,
get_hosts/2, get_module_proc/2, is_loaded/2, is_loaded_elsewhere/2, 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), Val = get_opt(host, Opts, Default),
ejabberd_regexp:greplace(Val, <<"@HOST@">>, Host). 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. -spec get_validators(binary(), module(), opts()) -> dict:dict() | undef.
get_validators(Host, Module, Opts) -> get_validators(Host, Module, Opts) ->
try Module:mod_opt_type('') of try Module:mod_opt_type('') of

View File

@ -32,8 +32,8 @@
hex_to_bin/1, hex_to_base64/1, expand_keyword/3, hex_to_bin/1, hex_to_base64/1, expand_keyword/3,
atom_to_binary/1, binary_to_atom/1, tuple_to_binary/1, 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, 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, now_to_usec/1, usec_to_now/1, encode_pid/1, decode_pid/2,
try_read_file/1]). compile_exprs/2, join_atoms/2, try_read_file/1]).
%% Deprecated functions %% Deprecated functions
-export([decode_base64/1, encode_base64/1]). -export([decode_base64/1, encode_base64/1]).
@ -127,6 +127,18 @@ expr_to_term(Expr) ->
term_to_expr(Term) -> term_to_expr(Term) ->
list_to_binary(io_lib:print(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(I) when is_integer(I) -> I;
l2i(L) when is_binary(L) -> binary_to_integer(L). l2i(L) when is_binary(L) -> binary_to_integer(L).
@ -192,22 +204,12 @@ join_atoms(Atoms, Sep) ->
%% in configuration validators only. %% in configuration validators only.
-spec try_read_file(file:filename_all()) -> binary(). -spec try_read_file(file:filename_all()) -> binary().
try_read_file(Path) -> try_read_file(Path) ->
Res = case file:read_file_info(Path) of case file:open(Path, [read]) of
{ok, #file_info{type = Type, access = Access}} -> {ok, Fd} ->
case {Type, Access} of file:close(Fd),
{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 ->
iolist_to_binary(Path); iolist_to_binary(Path);
{error, Reason} -> {error, Why} ->
?ERROR_MSG("Failed to read ~s: ~s", [Path, Reason]), ?ERROR_MSG("Failed to read ~s: ~s", [Path, file:format_error(Why)]),
erlang:error(badarg) erlang:error(badarg)
end. end.

View File

@ -43,7 +43,7 @@
-include("xmpp.hrl"). -include("xmpp.hrl").
-record(state, {host = <<"">> :: binary()}). -record(state, {hosts = [] :: [binary()]}).
%%==================================================================== %%====================================================================
%% gen_mod API %% gen_mod API
@ -62,7 +62,9 @@ depends(_Host, _Opts) ->
[]. [].
mod_opt_type(host) -> fun iolist_to_binary/1; 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 %% gen_server callbacks
@ -77,10 +79,13 @@ mod_opt_type(_) -> [host].
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
init([Host, Opts]) -> init([Host, Opts]) ->
process_flag(trap_exit, true), process_flag(trap_exit, true),
MyHost = gen_mod:get_opt_host(Host, Opts, Hosts = gen_mod:get_opt_hosts(Host, Opts,
<<"echo.@HOST@">>), <<"echo.@HOST@">>),
ejabberd_router:register_route(MyHost, Host), lists:foreach(
{ok, #state{host = MyHost}}. fun(H) ->
ejabberd_router:register_route(H, Host)
end, Hosts),
{ok, #state{hosts = Hosts}}.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} | %% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
@ -101,17 +106,19 @@ handle_call(stop, _From, State) ->
%% Description: Handling cast messages %% Description: Handling cast messages
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
handle_cast({reload, Host, NewOpts, OldOpts}, State) -> handle_cast({reload, Host, NewOpts, OldOpts}, State) ->
NewMyHost = gen_mod:get_opt_host(Host, NewOpts, NewMyHosts = gen_mod:get_opt_hosts(Host, NewOpts,
<<"echo.@HOST@">>), <<"echo.@HOST@">>),
OldMyHost = gen_mod:get_opt_host(Host, OldOpts, OldMyHosts = gen_mod:get_opt_hosts(Host, OldOpts,
<<"echo.@HOST@">>), <<"echo.@HOST@">>),
if NewMyHost /= OldMyHost -> lists:foreach(
ejabberd_router:register_route(NewMyHost, Host), fun(H) ->
ejabberd_router:unregister_route(OldMyHost); ejabberd_router:unregister_route(H)
true -> end, OldMyHosts -- NewMyHosts),
ok lists:foreach(
end, fun(H) ->
{noreply, State#state{host = NewMyHost}}; ejabberd_router:register_route(H, Host)
end, NewMyHosts -- OldMyHosts),
{noreply, State#state{hosts = NewMyHosts}};
handle_cast(Msg, State) -> handle_cast(Msg, State) ->
?WARNING_MSG("unexpected cast: ~p", [Msg]), ?WARNING_MSG("unexpected cast: ~p", [Msg]),
{noreply, State}. {noreply, State}.
@ -147,7 +154,7 @@ handle_info(_Info, State) -> {noreply, State}.
%% The return value is ignored. %% The return value is ignored.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
terminate(_Reason, State) -> 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} %% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}

View File

@ -538,9 +538,17 @@ json_error(HTTPCode, JSONCode, Message) ->
log(Call, Args, {Addr, Port}) -> log(Call, Args, {Addr, Port}) ->
AddrS = misc:ip_to_list({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) -> 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() -> permission_addon() ->
Access = gen_mod:get_module_opt(global, ?MODULE, admin_ip_access, none), Access = gen_mod:get_module_opt(global, ?MODULE, admin_ip_access, none),

View File

@ -59,8 +59,13 @@
-define(HTTP_ERR_FILE_NOT_FOUND, -define(HTTP_ERR_FILE_NOT_FOUND,
{-1, 404, [], <<"Not found">>}). {-1, 404, [], <<"Not found">>}).
-define(REQUEST_AUTH_HEADERS,
[{<<"WWW-Authenticate">>, <<"Basic realm=\"ejabberd\"">>}]).
-define(HTTP_ERR_FORBIDDEN, -define(HTTP_ERR_FORBIDDEN,
{-1, 403, [], <<"Forbidden">>}). {-1, 403, [], <<"Forbidden">>}).
-define(HTTP_ERR_REQUEST_AUTH,
{-1, 401, ?REQUEST_AUTH_HEADERS, <<"Unauthorized">>}).
-define(DEFAULT_CONTENT_TYPE, -define(DEFAULT_CONTENT_TYPE,
<<"application/octet-stream">>). <<"application/octet-stream">>).
@ -317,12 +322,17 @@ serve(LocalPath, Auth, DocRoot, DirectoryIndices, CustomHeaders, DefaultContentT
false false
end, end,
case CanProceed of case CanProceed of
false ->
?HTTP_ERR_REQUEST_AUTH;
true -> true ->
FileName = filename:join(filename:split(DocRoot) ++ LocalPath), FileName = filename:join(filename:split(DocRoot) ++ LocalPath),
case file:read_file_info(FileName) of case file:read_file_info(FileName) of
{error, enoent} -> ?HTTP_ERR_FILE_NOT_FOUND; {error, enoent} ->
{error, enotdir} -> ?HTTP_ERR_FILE_NOT_FOUND; ?HTTP_ERR_FILE_NOT_FOUND;
{error, eacces} -> ?HTTP_ERR_FORBIDDEN; {error, enotdir} ->
?HTTP_ERR_FILE_NOT_FOUND;
{error, eacces} ->
?HTTP_ERR_FORBIDDEN;
{ok, #file_info{type = directory}} -> serve_index(FileName, {ok, #file_info{type = directory}} -> serve_index(FileName,
DirectoryIndices, DirectoryIndices,
CustomHeaders, CustomHeaders,

View File

@ -94,7 +94,7 @@
-record(state, -record(state,
{server_host :: binary(), {server_host :: binary(),
host :: binary(), hosts :: [binary()],
name :: binary(), name :: binary(),
access :: atom(), access :: atom(),
max_size :: pos_integer() | infinity, max_size :: pos_integer() | infinity,
@ -151,6 +151,8 @@ stop(ServerHost) ->
mod_opt_type(host) -> mod_opt_type(host) ->
fun iolist_to_binary/1; fun iolist_to_binary/1;
mod_opt_type(hosts) ->
fun (L) -> lists:map(fun iolist_to_binary/1, L) end;
mod_opt_type(name) -> mod_opt_type(name) ->
fun iolist_to_binary/1; fun iolist_to_binary/1;
mod_opt_type(access) -> mod_opt_type(access) ->
@ -194,7 +196,7 @@ mod_opt_type(rm_on_unregister) ->
mod_opt_type(thumbnail) -> mod_opt_type(thumbnail) ->
fun(B) when is_boolean(B) -> B end; fun(B) when is_boolean(B) -> B end;
mod_opt_type(_) -> 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, dir_mode, docroot, put_url, get_url, service_url, custom_headers,
rm_on_unregister, thumbnail]. rm_on_unregister, thumbnail].
@ -211,7 +213,7 @@ depends(_Host, _Opts) ->
init([ServerHost, Opts]) -> init([ServerHost, Opts]) ->
process_flag(trap_exit, true), 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">>), Name = gen_mod:get_opt(name, Opts, <<"HTTP File Upload">>),
Access = gen_mod:get_opt(access, Opts, local), Access = gen_mod:get_opt(access, Opts, local),
MaxSize = gen_mod:get_opt(max_size, Opts, 104857600), MaxSize = gen_mod:get_opt(max_size, Opts, 104857600),
@ -244,8 +246,11 @@ init([ServerHost, Opts]) ->
false -> false ->
ok ok
end, end,
ejabberd_router:register_route(Host, ServerHost), lists:foreach(
{ok, #state{server_host = ServerHost, host = Host, name = Name, fun(Host) ->
ejabberd_router:register_route(Host, ServerHost)
end, Hosts),
{ok, #state{server_host = ServerHost, hosts = Hosts, name = Name,
access = Access, max_size = MaxSize, access = Access, max_size = MaxSize,
secret_length = SecretLength, jid_in_url = JIDinURL, secret_length = SecretLength, jid_in_url = JIDinURL,
file_mode = FileMode, dir_mode = DirMode, file_mode = FileMode, dir_mode = DirMode,
@ -324,10 +329,9 @@ handle_info(Info, State) ->
-spec terminate(normal | shutdown | {shutdown, _} | _, state()) -> ok. -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]), ?DEBUG("Stopping HTTP upload process for ~s: ~p", [ServerHost, Reason]),
ejabberd_router:unregister_route(Host), lists:foreach(fun ejabberd_router:unregister_route/1, Hosts).
ok.
-spec code_change({down, _} | _, state(), _) -> {ok, state()}. -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(). -spec make_rand_char() -> char().
make_rand_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(). -spec map_int_to_char(0..61) -> char().

View File

@ -58,7 +58,7 @@
[<<"koi8-r">>, <<"iso8859-15">>, <<"iso8859-1">>, <<"iso8859-2">>, [<<"koi8-r">>, <<"iso8859-15">>, <<"iso8859-1">>, <<"iso8859-2">>,
<<"utf-8">>, <<"utf-8+latin-1">>]). <<"utf-8">>, <<"utf-8+latin-1">>]).
-record(state, {host = <<"">> :: binary(), -record(state, {hosts = [] :: [binary()],
server_host = <<"">> :: binary(), server_host = <<"">> :: binary(),
access = all :: atom()}). access = all :: atom()}).
@ -99,8 +99,7 @@ depends(_Host, _Opts) ->
init([Host, Opts]) -> init([Host, Opts]) ->
process_flag(trap_exit, true), process_flag(trap_exit, true),
ejabberd:start_app(iconv), ejabberd:start_app(iconv),
MyHost = gen_mod:get_opt_host(Host, Opts, MyHosts = gen_mod:get_opt_hosts(Host, Opts, <<"irc.@HOST@">>),
<<"irc.@HOST@">>),
Mod = gen_mod:db_mod(Host, Opts, ?MODULE), Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
Mod:init(Host, Opts), Mod:init(Host, Opts),
Access = gen_mod:get_opt(access, Opts, all), Access = gen_mod:get_opt(access, Opts, all),
@ -108,10 +107,13 @@ init([Host, Opts]) ->
[named_table, public, [named_table, public,
{keypos, #irc_connection.jid_server_host}]), {keypos, #irc_connection.jid_server_host}]),
IQDisc = gen_mod:get_opt(iqdisc, Opts, gen_iq_handler:iqdisc(Host)), IQDisc = gen_mod:get_opt(iqdisc, Opts, gen_iq_handler:iqdisc(Host)),
register_hooks(MyHost, IQDisc), lists:foreach(
ejabberd_router:register_route(MyHost, Host), fun(MyHost) ->
register_hooks(MyHost, IQDisc),
ejabberd_router:register_route(MyHost, Host)
end, MyHosts),
{ok, {ok,
#state{host = MyHost, server_host = Host, #state{hosts = MyHosts, server_host = Host,
access = Access}}. access = Access}}.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
@ -133,8 +135,8 @@ handle_call(stop, _From, State) ->
%% Description: Handling cast messages %% Description: Handling cast messages
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
handle_cast({reload, ServerHost, NewOpts, OldOpts}, State) -> handle_cast({reload, ServerHost, NewOpts, OldOpts}, State) ->
NewHost = gen_mod:get_opt_host(ServerHost, NewOpts, <<"irc.@HOST@">>), NewHosts = gen_mod:get_opt_hosts(ServerHost, NewOpts, <<"irc.@HOST@">>),
OldHost = gen_mod:get_opt_host(ServerHost, OldOpts, <<"irc.@HOST@">>), OldHosts = gen_mod:get_opt_hosts(ServerHost, OldOpts, <<"irc.@HOST@">>),
NewIQDisc = gen_mod:get_opt(iqdisc, NewOpts, gen_iq_handler:iqdisc(ServerHost)), NewIQDisc = gen_mod:get_opt(iqdisc, NewOpts, gen_iq_handler:iqdisc(ServerHost)),
OldIQDisc = gen_mod:get_opt(iqdisc, OldOpts, 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), NewMod = gen_mod:db_mod(ServerHost, NewOpts, ?MODULE),
@ -145,20 +147,26 @@ handle_cast({reload, ServerHost, NewOpts, OldOpts}, State) ->
true -> true ->
ok ok
end, end,
if (NewIQDisc /= OldIQDisc) or (NewHost /= OldHost) -> if (NewIQDisc /= OldIQDisc) ->
register_hooks(NewHost, NewIQDisc); lists:foreach(
true -> fun(NewHost) ->
ok register_hooks(NewHost, NewIQDisc)
end, end, NewHosts -- (NewHosts -- OldHosts));
if NewHost /= OldHost ->
ejabberd_router:register_route(NewHost, ServerHost),
ejabberd_router:unregister_route(OldHost),
unregister_hooks(OldHost);
true -> true ->
ok ok
end, 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), 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) -> handle_cast(Msg, State) ->
?WARNING_MSG("unexpected cast: ~p", [Msg]), ?WARNING_MSG("unexpected cast: ~p", [Msg]),
{noreply, State}. {noreply, State}.
@ -170,9 +178,10 @@ handle_cast(Msg, State) ->
%% Description: Handling all non call/cast messages %% Description: Handling all non call/cast messages
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
handle_info({route, Packet}, handle_info({route, Packet},
#state{host = Host, server_host = ServerHost, #state{server_host = ServerHost, access = Access} =
access = Access} =
State) -> State) ->
To = xmpp:get_to(Packet),
Host = To#jid.lserver,
case catch do_route(Host, ServerHost, Access, Packet) of case catch do_route(Host, ServerHost, Access, Packet) of
{'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]); {'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]);
_ -> ok _ -> ok
@ -187,9 +196,12 @@ handle_info(_Info, State) -> {noreply, State}.
%% cleaning up. When it returns, the gen_server terminates with Reason. %% cleaning up. When it returns, the gen_server terminates with Reason.
%% The return value is ignored. %% The return value is ignored.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
terminate(_Reason, #state{host = MyHost}) -> terminate(_Reason, #state{hosts = MyHosts}) ->
ejabberd_router:unregister_route(MyHost), lists:foreach(
unregister_hooks(MyHost). fun(MyHost) ->
ejabberd_router:unregister_route(MyHost),
unregister_hooks(MyHost)
end, MyHosts).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState} %% 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) -> mod_opt_type(default_encoding) ->
fun iolist_to_binary/1; fun iolist_to_binary/1;
mod_opt_type(host) -> 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(_) -> mod_opt_type(_) ->
[access, db_type, default_encoding, host]. [access, db_type, default_encoding, host, hosts].
-spec extract_ident(stanza()) -> binary(). -spec extract_ident(stanza()) -> binary().
extract_ident(Packet) -> extract_ident(Packet) ->

View File

@ -27,7 +27,7 @@
-author('alexey@process-one.net'). -author('alexey@process-one.net').
-behaviour(gen_fsm). -behaviour(p1_fsm).
%% External exports %% External exports
-export([start_link/12, start/13, route_chan/4, -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, start_link(From, Host, Server, Username, Encoding, Port,
Password, Ident, RemoteAddr, RealName, WebircPassword, Mod) -> Password, Ident, RemoteAddr, RealName, WebircPassword, Mod) ->
gen_fsm:start_link(?MODULE, p1_fsm:start_link(?MODULE,
[From, Host, Server, Username, Encoding, Port, Password, [From, Host, Server, Username, Encoding, Port, Password,
Ident, RemoteAddr, RealName, WebircPassword, Mod], Ident, RemoteAddr, RealName, WebircPassword, Mod],
?FSMOPTS). ?FSMOPTS).
@ -109,7 +109,7 @@ start_link(From, Host, Server, Username, Encoding, Port,
%%---------------------------------------------------------------------- %%----------------------------------------------------------------------
init([From, Host, Server, Username, Encoding, Port, init([From, Host, Server, Username, Encoding, Port,
Password, Ident, RemoteAddr, RealName, WebircPassword, Mod]) -> Password, Ident, RemoteAddr, RealName, WebircPassword, Mod]) ->
gen_fsm:send_event(self(), init), p1_fsm:send_event(self(), init),
{ok, open_socket, {ok, open_socket,
#state{mod = Mod, #state{mod = Mod,
encoding = Encoding, port = Port, password = Password, encoding = Encoding, port = Port, password = Password,
@ -628,11 +628,11 @@ handle_info({tcp, _Socket, Data}, StateName,
StateData#state{inbuf = NewBuf}}; StateData#state{inbuf = NewBuf}};
handle_info({tcp_closed, _Socket}, StateName, handle_info({tcp_closed, _Socket}, StateName,
StateData) -> StateData) ->
gen_fsm:send_event(self(), closed), p1_fsm:send_event(self(), closed),
{next_state, StateName, StateData}; {next_state, StateName, StateData};
handle_info({tcp_error, _Socket, _Reason}, StateName, handle_info({tcp_error, _Socket, _Reason}, StateName,
StateData) -> StateData) ->
gen_fsm:send_event(self(), closed), p1_fsm:send_event(self(), closed),
{next_state, StateName, StateData}. {next_state, StateName, StateData}.
%%---------------------------------------------------------------------- %%----------------------------------------------------------------------

View File

@ -434,8 +434,9 @@ message_is_archived(false, #{jid := JID}, Pkt) ->
delete_old_messages(TypeBin, Days) when TypeBin == <<"chat">>; delete_old_messages(TypeBin, Days) when TypeBin == <<"chat">>;
TypeBin == <<"groupchat">>; TypeBin == <<"groupchat">>;
TypeBin == <<"all">> -> TypeBin == <<"all">> ->
CurrentTime = p1_time_compat:system_time(micro_seconds),
Diff = Days * 24 * 60 * 60 * 1000000, 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), Type = misc:binary_to_atom(TypeBin),
DBTypes = lists:usort( DBTypes = lists:usort(
lists:map( lists:map(
@ -830,7 +831,7 @@ select(_LServer, JidRequestor, JidArchive, Query, RSM,
Msgs = Msgs =
lists:flatmap( lists:flatmap(
fun({Nick, Pkt, _HaveSubject, Now, _Size}) -> fun({Nick, Pkt, _HaveSubject, Now, _Size}) ->
TS = now_to_usec(Now), TS = misc:now_to_usec(Now),
case match_interval(Now, Start, End) and case match_interval(Now, Start, End) and
match_rsm(Now, RSM) of match_rsm(Now, RSM) of
true -> true ->
@ -979,24 +980,14 @@ match_interval(Now, Start, End) ->
(Now >= Start) and (Now =< End). (Now >= Start) and (Now =< End).
match_rsm(Now, #rsm_set{'after' = ID}) when is_binary(ID), ID /= <<"">> -> 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; Now > Now1;
match_rsm(Now, #rsm_set{before = ID}) when is_binary(ID), ID /= <<"">> -> 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; Now < Now1;
match_rsm(_Now, _) -> match_rsm(_Now, _) ->
true. 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(undefined) ->
[]; [];
get_jids(Js) -> get_jids(Js) ->

View File

@ -46,7 +46,7 @@
?NS_MIX_NODES_CONFIG]). ?NS_MIX_NODES_CONFIG]).
-record(state, {server_host :: binary(), -record(state, {server_host :: binary(),
host :: binary()}). hosts :: [binary()]}).
%%%=================================================================== %%%===================================================================
%%% API %%% API
@ -124,36 +124,39 @@ process_iq(#iq{lang = Lang} = IQ) ->
%%%=================================================================== %%%===================================================================
init([ServerHost, Opts]) -> init([ServerHost, Opts]) ->
process_flag(trap_exit, true), process_flag(trap_exit, true),
Host = gen_mod:get_opt_host(ServerHost, Opts, <<"mix.@HOST@">>), Hosts = gen_mod:get_opt_hosts(ServerHost, Opts, <<"mix.@HOST@">>),
IQDisc = gen_mod:get_opt(iqdisc, Opts, gen_iq_handler:iqdisc(Host)), IQDisc = gen_mod:get_opt(iqdisc, Opts, gen_iq_handler:iqdisc(ServerHost)),
ConfigTab = gen_mod:get_module_proc(Host, config), lists:foreach(
ets:new(ConfigTab, [named_table]), fun(Host) ->
ets:insert(ConfigTab, {plugins, [<<"mix">>]}), ConfigTab = gen_mod:get_module_proc(Host, config),
ejabberd_hooks:add(disco_local_items, Host, ?MODULE, disco_items, 100), ets:new(ConfigTab, [named_table]),
ejabberd_hooks:add(disco_local_features, Host, ?MODULE, disco_features, 100), ets:insert(ConfigTab, {plugins, [<<"mix">>]}),
ejabberd_hooks:add(disco_local_identity, Host, ?MODULE, disco_identity, 100), ejabberd_hooks:add(disco_local_items, Host, ?MODULE, disco_items, 100),
ejabberd_hooks:add(disco_sm_items, Host, ?MODULE, disco_items, 100), ejabberd_hooks:add(disco_local_features, Host, ?MODULE, disco_features, 100),
ejabberd_hooks:add(disco_sm_features, Host, ?MODULE, disco_features, 100), ejabberd_hooks:add(disco_local_identity, Host, ?MODULE, disco_identity, 100),
ejabberd_hooks:add(disco_sm_identity, Host, ?MODULE, disco_identity, 100), ejabberd_hooks:add(disco_sm_items, Host, ?MODULE, disco_items, 100),
ejabberd_hooks:add(disco_info, Host, ?MODULE, disco_info, 100), ejabberd_hooks:add(disco_sm_features, Host, ?MODULE, disco_features, 100),
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ejabberd_hooks:add(disco_sm_identity, Host, ?MODULE, disco_identity, 100),
?NS_DISCO_ITEMS, mod_disco, ejabberd_hooks:add(disco_info, Host, ?MODULE, disco_info, 100),
process_local_iq_items, IQDisc), gen_iq_handler:add_iq_handler(ejabberd_local, Host,
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS, mod_disco,
?NS_DISCO_INFO, mod_disco, process_local_iq_items, IQDisc),
process_local_iq_info, IQDisc), gen_iq_handler:add_iq_handler(ejabberd_local, Host,
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_DISCO_INFO, mod_disco,
?NS_DISCO_ITEMS, mod_disco, process_local_iq_info, IQDisc),
process_local_iq_items, IQDisc), gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_DISCO_ITEMS, mod_disco,
?NS_DISCO_INFO, mod_disco, process_local_iq_items, IQDisc),
process_local_iq_info, IQDisc), gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_DISCO_INFO, mod_disco,
?NS_PUBSUB, mod_pubsub, iq_sm, IQDisc), process_local_iq_info, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
?NS_MIX_0, ?MODULE, process_iq, IQDisc), ?NS_PUBSUB, mod_pubsub, iq_sm, IQDisc),
ejabberd_router:register_route(Host, ServerHost), gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
{ok, #state{server_host = ServerHost, host = 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) -> handle_call(_Request, _From, State) ->
Reply = ok, Reply = ok,
@ -180,22 +183,24 @@ handle_info({route, Packet}, State) ->
handle_info(_Info, State) -> handle_info(_Info, State) ->
{noreply, State}. {noreply, State}.
terminate(_Reason, #state{host = Host}) -> terminate(_Reason, #state{hosts = Hosts}) ->
ejabberd_hooks:delete(disco_local_items, Host, ?MODULE, disco_items, 100), lists:foreach(
ejabberd_hooks:delete(disco_local_features, Host, ?MODULE, disco_features, 100), fun(Host) ->
ejabberd_hooks:delete(disco_local_identity, Host, ?MODULE, disco_identity, 100), ejabberd_hooks:delete(disco_local_items, Host, ?MODULE, disco_items, 100),
ejabberd_hooks:delete(disco_sm_items, Host, ?MODULE, disco_items, 100), ejabberd_hooks:delete(disco_local_features, Host, ?MODULE, disco_features, 100),
ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE, disco_features, 100), ejabberd_hooks:delete(disco_local_identity, Host, ?MODULE, disco_identity, 100),
ejabberd_hooks:delete(disco_sm_identity, Host, ?MODULE, disco_identity, 100), ejabberd_hooks:delete(disco_sm_items, Host, ?MODULE, disco_items, 100),
ejabberd_hooks:delete(disco_info, Host, ?MODULE, disco_info, 100), ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE, disco_features, 100),
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS), ejabberd_hooks:delete(disco_sm_identity, Host, ?MODULE, disco_identity, 100),
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO), ejabberd_hooks:delete(disco_info, Host, ?MODULE, disco_info, 100),
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_DISCO_ITEMS), gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS),
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_DISCO_INFO), gen_iq_handler:remove_iq_handler(ejabberd_local, 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_DISCO_ITEMS),
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_MIX_0), gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_DISCO_INFO),
ejabberd_router:unregister_route(Host), gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_PUBSUB),
ok. gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_MIX_0),
ejabberd_router:unregister_route(Host)
end, Hosts).
code_change(_OldVsn, State, _Extra) -> code_change(_OldVsn, State, _Extra) ->
{ok, State}. {ok, State}.
@ -316,4 +321,6 @@ depends(_Host, _Opts) ->
mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1; mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
mod_opt_type(host) -> fun iolist_to_binary/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].

View File

@ -75,7 +75,7 @@
-include("mod_muc.hrl"). -include("mod_muc.hrl").
-record(state, -record(state,
{host = <<"">> :: binary(), {hosts = [] :: [binary()],
server_host = <<"">> :: binary(), server_host = <<"">> :: binary(),
access = {none, none, none, none} :: {atom(), atom(), atom(), atom()}, access = {none, none, none, none} :: {atom(), atom(), atom(), atom()},
history_size = 20 :: non_neg_integer(), 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. %% If Opts = default, the default room options are used.
%% Else use the passed options as defined in mod_muc_room. %% Else use the passed options as defined in mod_muc_room.
create_room(Host, Name, From, Nick, Opts) -> create_room(Host, Name, From, Nick, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?MODULE), ServerHost = ejabberd_router:host_of_route(Host),
gen_server:call(Proc, {create, Name, From, Nick, Opts}). Proc = gen_mod:get_module_proc(ServerHost, ?MODULE),
gen_server:call(Proc, {create, Name, Host, From, Nick, Opts}).
store_room(ServerHost, Host, Name, Opts) -> store_room(ServerHost, Host, Name, Opts) ->
LServer = jid:nameprep(ServerHost), LServer = jid:nameprep(ServerHost),
@ -225,22 +226,26 @@ get_online_rooms_by_user(ServerHost, LUser, LServer) ->
init([Host, Opts]) -> init([Host, Opts]) ->
process_flag(trap_exit, true), process_flag(trap_exit, true),
IQDisc = gen_mod:get_opt(iqdisc, Opts, gen_iq_handler:iqdisc(Host)), 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, history_size = HistorySize, queue_type = QueueType,
room_shaper = RoomShaper} = State = init_state(Host, Opts), room_shaper = RoomShaper} = State = init_state(Host, Opts),
Mod = gen_mod:db_mod(Host, Opts, ?MODULE), Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
RMod = gen_mod:ram_db_mod(Host, Opts, ?MODULE), RMod = gen_mod:ram_db_mod(Host, Opts, ?MODULE),
Mod:init(Host, [{host, MyHost}|Opts]), Mod:init(Host, [{hosts, MyHosts}|Opts]),
RMod:init(Host, [{host, MyHost}|Opts]), RMod:init(Host, [{hosts, MyHosts}|Opts]),
register_iq_handlers(MyHost, IQDisc), lists:foreach(
ejabberd_router:register_route(MyHost, Host), fun(MyHost) ->
load_permanent_rooms(MyHost, Host, Access, HistorySize, RoomShaper, QueueType), register_iq_handlers(MyHost, IQDisc),
ejabberd_router:register_route(MyHost, Host),
load_permanent_rooms(MyHost, Host, Access, HistorySize,
RoomShaper, QueueType)
end, MyHosts),
{ok, State}. {ok, State}.
handle_call(stop, _From, State) -> handle_call(stop, _From, State) ->
{stop, normal, ok, State}; {stop, normal, ok, State};
handle_call({create, Room, From, Nick, Opts}, _From, handle_call({create, Room, Host, From, Nick, Opts}, _From,
#state{host = Host, server_host = ServerHost, #state{server_host = ServerHost,
access = Access, default_room_opts = DefOpts, access = Access, default_room_opts = DefOpts,
history_size = HistorySize, queue_type = QueueType, history_size = HistorySize, queue_type = QueueType,
room_shaper = RoomShaper} = State) -> room_shaper = RoomShaper} = State) ->
@ -256,51 +261,59 @@ handle_call({create, Room, From, Nick, Opts}, _From,
Nick, NewOpts, QueueType), Nick, NewOpts, QueueType),
RMod = gen_mod:ram_db_mod(ServerHost, ?MODULE), RMod = gen_mod:ram_db_mod(ServerHost, ?MODULE),
RMod:register_online_room(ServerHost, Room, Host, Pid), RMod:register_online_room(ServerHost, Room, Host, Pid),
ejabberd_hooks:run(create_room, ServerHost, [ServerHost, Room, Host]),
{reply, ok, State}. {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)), NewIQDisc = gen_mod:get_opt(iqdisc, NewOpts, gen_iq_handler:iqdisc(ServerHost)),
OldIQDisc = gen_mod:get_opt(iqdisc, OldOpts, 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), NewMod = gen_mod:db_mod(ServerHost, NewOpts, ?MODULE),
NewRMod = gen_mod:ram_db_mod(ServerHost, NewOpts, ?MODULE), NewRMod = gen_mod:ram_db_mod(ServerHost, NewOpts, ?MODULE),
OldMod = gen_mod:db_mod(ServerHost, OldOpts, ?MODULE), OldMod = gen_mod:db_mod(ServerHost, OldOpts, ?MODULE),
OldRMod = gen_mod:ram_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 -> if NewMod /= OldMod ->
NewMod:init(ServerHost, [{host, NewHost}|NewOpts]); NewMod:init(ServerHost, [{hosts, NewHosts}|NewOpts]);
true -> true ->
ok ok
end, end,
if NewRMod /= OldRMod -> if NewRMod /= OldRMod ->
NewRMod:init(ServerHost, [{host, NewHost}|NewOpts]); NewRMod:init(ServerHost, [{hosts, NewHosts}|NewOpts]);
true -> true ->
ok ok
end, end,
if (NewIQDisc /= OldIQDisc) or (NewHost /= OldHost) -> if (NewIQDisc /= OldIQDisc) ->
register_iq_handlers(NewHost, NewIQDisc); lists:foreach(
true -> fun(NewHost) ->
ok register_iq_handlers(NewHost, NewIQDisc)
end, end, NewHosts -- (NewHosts -- OldHosts));
if NewHost /= OldHost ->
ejabberd_router:register_route(NewHost, ServerHost),
ejabberd_router:unregister_route(OldHost),
unregister_iq_handlers(OldHost);
true -> true ->
ok ok
end, 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}; {noreply, NewState};
handle_cast(Msg, State) -> handle_cast(Msg, State) ->
?WARNING_MSG("unexpected cast: ~p", [Msg]), ?WARNING_MSG("unexpected cast: ~p", [Msg]),
{noreply, State}. {noreply, State}.
handle_info({route, Packet}, handle_info({route, Packet},
#state{host = Host, server_host = ServerHost, #state{server_host = ServerHost,
access = Access, default_room_opts = DefRoomOpts, access = Access, default_room_opts = DefRoomOpts,
history_size = HistorySize, queue_type = QueueType, history_size = HistorySize, queue_type = QueueType,
max_rooms_discoitems = MaxRoomsDiscoItems, max_rooms_discoitems = MaxRoomsDiscoItems,
room_shaper = RoomShaper} = State) -> room_shaper = RoomShaper} = State) ->
From = xmpp:get_from(Packet), From = xmpp:get_from(Packet),
To = xmpp:get_to(Packet), To = xmpp:get_to(Packet),
Host = To#jid.lserver,
case catch do_route(Host, ServerHost, Access, HistorySize, RoomShaper, case catch do_route(Host, ServerHost, Access, HistorySize, RoomShaper,
From, To, Packet, DefRoomOpts, MaxRoomsDiscoItems, From, To, Packet, DefRoomOpts, MaxRoomsDiscoItems,
QueueType) of QueueType) of
@ -319,9 +332,12 @@ handle_info(Info, State) ->
?ERROR_MSG("unexpected info: ~p", [Info]), ?ERROR_MSG("unexpected info: ~p", [Info]),
{noreply, State}. {noreply, State}.
terminate(_Reason, #state{host = MyHost}) -> terminate(_Reason, #state{hosts = MyHosts}) ->
ejabberd_router:unregister_route(MyHost), lists:foreach(
unregister_iq_handlers(MyHost). fun(MyHost) ->
ejabberd_router:unregister_route(MyHost),
unregister_iq_handlers(MyHost)
end, MyHosts).
code_change(_OldVsn, State, _Extra) -> {ok, State}. code_change(_OldVsn, State, _Extra) -> {ok, State}.
@ -329,8 +345,8 @@ code_change(_OldVsn, State, _Extra) -> {ok, State}.
%%% Internal functions %%% Internal functions
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
init_state(Host, Opts) -> init_state(Host, Opts) ->
MyHost = gen_mod:get_opt_host(Host, Opts, MyHosts = gen_mod:get_opt_hosts(Host, Opts,
<<"conference.@HOST@">>), <<"conference.@HOST@">>),
Access = gen_mod:get_opt(access, Opts, all), Access = gen_mod:get_opt(access, Opts, all),
AccessCreate = gen_mod:get_opt(access_create, Opts, all), AccessCreate = gen_mod:get_opt(access_create, Opts, all),
AccessAdmin = gen_mod:get_opt(access_admin, Opts, none), 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, QueueType = gen_mod:get_opt(queue_type, Opts,
ejabberd_config:default_queue_type(Host)), ejabberd_config:default_queue_type(Host)),
RoomShaper = gen_mod:get_opt(room_shaper, Opts, none), RoomShaper = gen_mod:get_opt(room_shaper, Opts, none),
#state{host = MyHost, #state{hosts = MyHosts,
server_host = Host, server_host = Host,
access = {Access, AccessCreate, AccessAdmin, AccessPersistent}, access = {Access, AccessCreate, AccessAdmin, AccessPersistent},
default_room_opts = DefRoomOpts, default_room_opts = DefRoomOpts,
@ -667,7 +683,7 @@ iq_disco_items(_ServerHost, _Host, _From, Lang, _MaxRoomsDiscoItems, _Node, _RSM
{error, timeout | notfound}. {error, timeout | notfound}.
get_room_disco_item({Name, Host, Pid}, Query) -> get_room_disco_item({Name, Host, Pid}, Query) ->
RoomJID = jid:make(Name, Host), 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} -> {item, Desc} ->
{ok, #disco_item{jid = RoomJID, name = Desc}}; {ok, #disco_item{jid = RoomJID, name = Desc}};
false -> false ->
@ -683,7 +699,7 @@ get_subscribed_rooms(ServerHost, Host, From) ->
BareFrom = jid:remove_resource(From), BareFrom = jid:remove_resource(From),
lists:flatmap( lists:flatmap(
fun({Name, _, Pid}) -> 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)]; true -> [jid:make(Name, Host)];
false -> [] false -> []
end; end;
@ -765,7 +781,7 @@ process_iq_register_set(ServerHost, Host, From,
broadcast_service_message(ServerHost, Host, Msg) -> broadcast_service_message(ServerHost, Host, Msg) ->
lists:foreach( lists:foreach(
fun({_, _, Pid}) -> fun({_, _, Pid}) ->
gen_fsm:send_all_state_event( p1_fsm:send_all_state_event(
Pid, {service_message, Msg}) Pid, {service_message, Msg})
end, get_online_rooms(ServerHost, Host)). 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) -> mod_opt_type(history_size) ->
fun (I) when is_integer(I), I >= 0 -> I end; fun (I) when is_integer(I), I >= 0 -> I end;
mod_opt_type(host) -> 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(max_room_desc) -> mod_opt_type(max_room_desc) ->
fun (infinity) -> infinity; fun (infinity) -> infinity;
(I) when is_integer(I), I > 0 -> I (I) when is_integer(I), I > 0 -> I
@ -943,7 +961,7 @@ mod_opt_type({default_room_options, presence_broadcast}) ->
end; end;
mod_opt_type(_) -> mod_opt_type(_) ->
[access, access_admin, access_create, access_persistent, [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_room_desc, max_room_id, max_room_name,
max_rooms_discoitems, max_user_conferences, max_users, max_rooms_discoitems, max_user_conferences, max_users,
max_users_admin_threshold, max_users_presence, max_users_admin_threshold, max_users_presence,

View File

@ -597,7 +597,7 @@ muc_create_room(ServerHost, {Name, Host, _}, DefRoomOpts) ->
destroy_room(Name, Service) -> destroy_room(Name, Service) ->
case mod_muc:find_online_room(Name, Service) of case mod_muc:find_online_room(Name, Service) of
{ok, Pid} -> {ok, Pid} ->
gen_fsm:send_all_state_event(Pid, destroy), p1_fsm:send_all_state_event(Pid, destroy),
ok; ok;
error -> error ->
error error
@ -716,11 +716,11 @@ get_rooms(ServerHost) ->
end, Hosts). end, Hosts).
get_room_config(Room_pid) -> 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. R.
get_room_state(Room_pid) -> 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. R.
%%--------------- %%---------------
@ -786,7 +786,7 @@ find_serverhost(Host, ServerHosts) ->
ServerHost. ServerHost.
act_on_room(destroy, {N, H, Pid}, SH) -> 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.">>}), Pid, {destroy, <<"Room destroyed by rooms_unused_destroy.">>}),
mod_muc:room_destroyed(H, N, Pid, SH), mod_muc:room_destroyed(H, N, Pid, SH),
mod_muc:forget_room(SH, H, N); 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), {Option, Value} = format_room_option(OptionString, ValueString),
Config = get_room_config(Pid), Config = get_room_config(Pid),
Config2 = change_option(Option, Value, Config), 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 ok
end. end.
@ -983,7 +983,7 @@ get_room_affiliations(Name, Service) ->
case mod_muc:find_online_room(Name, Service) of case mod_muc:find_online_room(Name, Service) of
{ok, Pid} -> {ok, Pid} ->
%% Get the PID of the online room, then request its state %% 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), Affiliations = ?DICT:to_list(StateData#state.affiliations),
lists:map( lists:map(
fun({{Uname, Domain, _Res}, {Aff, Reason}}) when is_atom(Aff)-> 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 case mod_muc:find_online_room(Name, Service) of
{ok, Pid} -> {ok, Pid} ->
%% Get the PID for the online room so we can get the state of the room %% 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)), mod_muc:store_room(StateData#state.server_host, StateData#state.host, StateData#state.room, make_opts(StateData)),
ok; ok;
error -> error ->
@ -1035,7 +1035,7 @@ subscribe_room(User, Nick, Room, Nodes) ->
UserJID -> UserJID ->
case get_room_pid(Name, Host) of case get_room_pid(Name, Host) of
Pid when is_pid(Pid) -> Pid when is_pid(Pid) ->
case gen_fsm:sync_send_all_state_event( case p1_fsm:sync_send_all_state_event(
Pid, Pid,
{muc_subscribe, UserJID, Nick, NodeList}) of {muc_subscribe, UserJID, Nick, NodeList}) of
{ok, SubscribedNodes} -> {ok, SubscribedNodes} ->
@ -1062,7 +1062,7 @@ unsubscribe_room(User, Room) ->
UserJID -> UserJID ->
case get_room_pid(Name, Host) of case get_room_pid(Name, Host) of
Pid when is_pid(Pid) -> Pid when is_pid(Pid) ->
case gen_fsm:sync_send_all_state_event( case p1_fsm:sync_send_all_state_event(
Pid, Pid,
{muc_unsubscribe, UserJID}) of {muc_unsubscribe, UserJID}) of
ok -> ok ->
@ -1085,7 +1085,7 @@ unsubscribe_room(User, Room) ->
get_subscribers(Name, Host) -> get_subscribers(Name, Host) ->
case get_room_pid(Name, Host) of case get_room_pid(Name, Host) of
Pid when is_pid(Pid) -> 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]; [jid:encode(jid:remove_resource(J)) || J <- JIDList];
_ -> _ ->
throw({error, "The room does not exist"}) throw({error, "The room does not exist"})

View File

@ -1142,7 +1142,7 @@ get_room_state(RoomName, MucService) ->
-spec get_room_state(pid()) -> mod_muc_room:state(). -spec get_room_state(pid()) -> mod_muc_room:state().
get_room_state(RoomPid) -> 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), get_state),
R. R.

View File

@ -296,7 +296,7 @@ import(_LServer, <<"muc_registered">>,
%%% gen_server callbacks %%% gen_server callbacks
%%%=================================================================== %%%===================================================================
init([Host, Opts]) -> 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 case gen_mod:db_mod(Host, Opts, mod_muc) of
?MODULE -> ?MODULE ->
ejabberd_mnesia:create(?MODULE, muc_room, ejabberd_mnesia:create(?MODULE, muc_room,
@ -318,7 +318,10 @@ init([Host, Opts]) ->
{type, ordered_set}, {type, ordered_set},
{attributes, record_info(fields, muc_online_room)}]), {attributes, record_info(fields, muc_online_room)}]),
catch ets:new(muc_online_users, [bag, named_table, public, {keypos, 2}]), 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); mnesia:subscribe(system);
_ -> _ ->
ok ok

View File

@ -60,7 +60,7 @@ restore_room(_LServer, Host, Name) ->
forget_room(_LServer, Host, Name) -> forget_room(_LServer, Host, Name) ->
{atomic, ejabberd_riak:delete(muc_room, {Name, Host})}. {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), {LUser, LServer, _} = jid:tolower(JID),
LUS = {LUser, LServer}, LUS = {LUser, LServer},
case ejabberd_riak:get_by_index(muc_registered, case ejabberd_riak:get_by_index(muc_registered,

View File

@ -27,7 +27,7 @@
-author('alexey@process-one.net'). -author('alexey@process-one.net').
-behaviour(gen_fsm). -behaviour(p1_fsm).
%% External exports %% External exports
-export([start_link/10, -export([start_link/10,
@ -94,23 +94,23 @@
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
start(Host, ServerHost, Access, Room, HistorySize, RoomShaper, start(Host, ServerHost, Access, Room, HistorySize, RoomShaper,
Creator, Nick, DefRoomOpts, QueueType) -> 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], RoomShaper, Creator, Nick, DefRoomOpts, QueueType],
?FSMOPTS). ?FSMOPTS).
start(Host, ServerHost, Access, Room, HistorySize, RoomShaper, Opts, QueueType) -> 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], RoomShaper, Opts, QueueType],
?FSMOPTS). ?FSMOPTS).
start_link(Host, ServerHost, Access, Room, HistorySize, RoomShaper, start_link(Host, ServerHost, Access, Room, HistorySize, RoomShaper,
Creator, Nick, DefRoomOpts, QueueType) -> 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], RoomShaper, Creator, Nick, DefRoomOpts, QueueType],
?FSMOPTS). ?FSMOPTS).
start_link(Host, ServerHost, Access, Room, HistorySize, RoomShaper, Opts, QueueType) -> 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], RoomShaper, Opts, QueueType],
?FSMOPTS). ?FSMOPTS).
@ -703,7 +703,7 @@ terminate(Reason, _StateName, StateData) ->
-spec route(pid(), stanza()) -> ok. -spec route(pid(), stanza()) -> ok.
route(Pid, Packet) -> route(Pid, Packet) ->
#jid{lresource = Nick} = xmpp:get_to(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(). -spec process_groupchat_message(message(), state()) -> fsm_next().
process_groupchat_message(#message{from = From, lang = Lang} = Packet, StateData) -> process_groupchat_message(#message{from = From, lang = Lang} = Packet, StateData) ->
@ -4004,6 +4004,7 @@ tab_add_online_user(JID, StateData) ->
Room = StateData#state.room, Room = StateData#state.room,
Host = StateData#state.host, Host = StateData#state.host,
ServerHost = StateData#state.server_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). mod_muc:register_online_user(ServerHost, jid:tolower(JID), Room, Host).
-spec tab_remove_online_user(jid(), state()) -> any(). -spec tab_remove_online_user(jid(), state()) -> any().
@ -4011,6 +4012,7 @@ tab_remove_online_user(JID, StateData) ->
Room = StateData#state.room, Room = StateData#state.room,
Host = StateData#state.host, Host = StateData#state.host,
ServerHost = StateData#state.server_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). mod_muc:unregister_online_user(ServerHost, jid:tolower(JID), Room, Host).
-spec tab_count_user(jid(), state()) -> non_neg_integer(). -spec tab_count_user(jid(), state()) -> non_neg_integer().

View File

@ -998,8 +998,10 @@ build_service_limit_record(LimitOpts) ->
build_limit_record(LimitOptsR, remote)}. build_limit_record(LimitOptsR, remote)}.
get_from_limitopts(LimitOpts, SenderT) -> get_from_limitopts(LimitOpts, SenderT) ->
{SenderT, Result} = lists:keyfind(SenderT, 1, LimitOpts), case lists:keyfind(SenderT, 1, LimitOpts) of
Result. false -> [];
{SenderT, Result} -> Result
end.
build_remote_limit_record(LimitOpts, SenderT) -> build_remote_limit_record(LimitOpts, SenderT) ->
build_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) -> mod_opt_type({limits, Type}) when (Type == local) or (Type == remote) ->
fun(L) -> fun(L) ->
lists:map( lists:map(
fun ({message, infinite}) -> infinite; fun ({message, infinite} = O) -> O;
({presence, infinite}) -> infinite; ({presence, infinite} = O) -> O;
({message, I}) when is_integer(I) -> I; ({message, I} = O) when is_integer(I) -> O;
({presence, I}) when is_integer(I) -> I ({presence, I} = O) when is_integer(I) -> O
end, L) end, L)
end; end;
mod_opt_type(_) -> [access, host, {limits, local}, {limits, remote}]. mod_opt_type(_) -> [access, host, {limits, local}, {limits, remote}].

View File

@ -112,6 +112,8 @@ depends(_Host, _Opts) ->
mod_opt_type(access) -> fun acl:access_rules_validator/1; mod_opt_type(access) -> fun acl:access_rules_validator/1;
mod_opt_type(host) -> 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(hostname) -> fun iolist_to_binary/1; mod_opt_type(hostname) -> fun iolist_to_binary/1;
mod_opt_type(ip) -> mod_opt_type(ip) ->
fun (S) -> fun (S) ->
@ -131,7 +133,7 @@ mod_opt_type(ram_db_type) ->
mod_opt_type(Opt) -> mod_opt_type(Opt) ->
case mod_proxy65_stream:listen_opt_type(Opt) of case mod_proxy65_stream:listen_opt_type(Opt) of
Opts when is_list(Opts) -> Opts when is_list(Opts) ->
[access, host, hostname, ip, name, port, [access, host, hosts, hostname, ip, name, port,
max_connections, ram_db_type] ++ Opts; max_connections, ram_db_type] ++ Opts;
Fun -> Fun ->
Fun Fun

View File

@ -43,7 +43,7 @@
-define(PROCNAME, ejabberd_mod_proxy65_service). -define(PROCNAME, ejabberd_mod_proxy65_service).
-record(state, {myhost = <<"">> :: binary()}). -record(state, {myhosts = [] :: [binary()]}).
%%%------------------------ %%%------------------------
%%% gen_server callbacks %%% gen_server callbacks
@ -61,24 +61,27 @@ reload(Host, NewOpts, OldOpts) ->
init([Host, Opts]) -> init([Host, Opts]) ->
process_flag(trap_exit, true), process_flag(trap_exit, true),
IQDisc = gen_mod:get_opt(iqdisc, Opts, gen_iq_handler:iqdisc(Host)), IQDisc = gen_mod:get_opt(iqdisc, Opts, gen_iq_handler:iqdisc(Host)),
MyHost = gen_mod:get_opt_host(Host, Opts, <<"proxy.@HOST@">>), MyHosts = gen_mod:get_opt_hosts(Host, Opts, <<"proxy.@HOST@">>),
gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_INFO, lists:foreach(
?MODULE, process_disco_info, IQDisc), fun(MyHost) ->
gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_ITEMS, gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_INFO,
?MODULE, process_disco_items, IQDisc), ?MODULE, process_disco_info, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_VCARD, gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_ITEMS,
?MODULE, process_vcard, IQDisc), ?MODULE, process_disco_items, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_BYTESTREAMS, gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_VCARD,
?MODULE, process_bytestreams, IQDisc), ?MODULE, process_vcard, IQDisc),
ejabberd_router:register_route(MyHost, Host), gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_BYTESTREAMS,
{ok, #state{myhost = MyHost}}. ?MODULE, process_bytestreams, IQDisc),
ejabberd_router:register_route(MyHost, Host)
end, MyHosts),
{ok, #state{myhosts = MyHosts}}.
terminate(_Reason, #state{myhost = MyHost}) -> terminate(_Reason, #state{myhosts = MyHosts}) ->
ejabberd_router:unregister_route(MyHost), lists:foreach(
gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_INFO), fun(MyHost) ->
gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_ITEMS), ejabberd_router:unregister_route(MyHost),
gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_VCARD), unregister_handlers(MyHost)
gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_BYTESTREAMS). end, MyHosts).
handle_info({route, #iq{} = Packet}, State) -> handle_info({route, #iq{} = Packet}, State) ->
ejabberd_router:process_iq(Packet), ejabberd_router:process_iq(Packet),
@ -89,33 +92,29 @@ handle_call(_Request, _From, State) ->
{reply, ok, State}. {reply, ok, State}.
handle_cast({reload, ServerHost, NewOpts, OldOpts}, State) -> handle_cast({reload, ServerHost, NewOpts, OldOpts}, State) ->
NewHost = gen_mod:get_opt_host(ServerHost, NewOpts, <<"proxy.@HOST@">>), NewHosts = gen_mod:get_opt_hosts(ServerHost, NewOpts, <<"proxy.@HOST@">>),
OldHost = gen_mod:get_opt_host(ServerHost, OldOpts, <<"proxy.@HOST@">>), OldHosts = gen_mod:get_opt_hosts(ServerHost, OldOpts, <<"proxy.@HOST@">>),
NewIQDisc = gen_mod:get_opt(iqdisc, NewOpts, gen_iq_handler:iqdisc(ServerHost)), NewIQDisc = gen_mod:get_opt(iqdisc, NewOpts, gen_iq_handler:iqdisc(ServerHost)),
OldIQDisc = gen_mod:get_opt(iqdisc, OldOpts, gen_iq_handler:iqdisc(ServerHost)), OldIQDisc = gen_mod:get_opt(iqdisc, OldOpts, gen_iq_handler:iqdisc(ServerHost)),
if (NewIQDisc /= OldIQDisc) or (NewHost /= OldHost) -> if (NewIQDisc /= OldIQDisc) ->
gen_iq_handler:add_iq_handler(ejabberd_local, NewHost, ?NS_DISCO_INFO, lists:foreach(
?MODULE, process_disco_info, NewIQDisc), fun(NewHost) ->
gen_iq_handler:add_iq_handler(ejabberd_local, NewHost, ?NS_DISCO_ITEMS, register_handlers(NewHost, NewIQDisc)
?MODULE, process_disco_items, NewIQDisc), end, NewHosts -- (NewHosts -- OldHosts));
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 -> true ->
ok ok
end, end,
if NewHost /= OldHost -> lists:foreach(
ejabberd_router:register_route(NewHost, ServerHost), fun(NewHost) ->
ejabberd_router:unregister_route(OldHost), ejabberd_router:register_route(NewHost, ServerHost),
gen_iq_handler:remove_iq_handler(ejabberd_local, OldHost, ?NS_DISCO_INFO), register_handlers(NewHost, NewIQDisc)
gen_iq_handler:remove_iq_handler(ejabberd_local, OldHost, ?NS_DISCO_ITEMS), end, NewHosts -- OldHosts),
gen_iq_handler:remove_iq_handler(ejabberd_local, OldHost, ?NS_VCARD), lists:foreach(
gen_iq_handler:remove_iq_handler(ejabberd_local, OldHost, ?NS_BYTESTREAMS); fun(OldHost) ->
true -> ejabberd_router:unregister_route(OldHost),
ok unregister_handlers(OldHost)
end, end, OldHosts -- NewHosts),
{noreply, State#state{myhost = NewHost}}; {noreply, State#state{myhosts = NewHosts}};
handle_cast(Msg, State) -> handle_cast(Msg, State) ->
?WARNING_MSG("unexpected cast: ~p", [Msg]), ?WARNING_MSG("unexpected cast: ~p", [Msg]),
{noreply, State}. {noreply, State}.
@ -276,3 +275,19 @@ get_my_ip() ->
max_connections(ServerHost) -> max_connections(ServerHost) ->
gen_mod:get_module_opt(ServerHost, mod_proxy65, max_connections, infinity). 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).

View File

@ -26,7 +26,7 @@
-author('xram@jabber.ru'). -author('xram@jabber.ru').
-behaviour(gen_fsm). -behaviour(p1_fsm).
%% gen_fsm callbacks. %% gen_fsm callbacks.
-export([init/1, handle_event/3, handle_sync_event/4, -export([init/1, handle_event/3, handle_sync_event/4,
@ -75,7 +75,7 @@ start({gen_tcp, Socket}, Opts1) ->
[Socket, Host, Opts]). [Socket, Host, Opts]).
start_link(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]) -> init([Socket, Host, Opts]) ->
process_flag(trap_exit, true), process_flag(trap_exit, true),
@ -106,9 +106,9 @@ socket_type() -> raw.
stop(StreamPid) -> StreamPid ! stop. stop(StreamPid) -> StreamPid ! stop.
activate({P1, J1}, {P2, J2}) -> 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), get_socket),
gen_fsm:sync_send_all_state_event(P2, get_socket)} p1_fsm:sync_send_all_state_event(P2, get_socket)}
of of
{S1, S2} when is_port(S1), is_port(S2) -> {S1, S2} when is_port(S1), is_port(S2) ->
P1 ! {activate, P2, S2, J1, J2}, P1 ! {activate, P2, S2, J1, J2},
@ -197,7 +197,7 @@ handle_info({tcp, _S, Data}, StateName, StateData)
when StateName /= wait_for_activation -> when StateName /= wait_for_activation ->
erlang:cancel_timer(StateData#state.timer), erlang:cancel_timer(StateData#state.timer),
TRef = erlang:send_after(?WAIT_TIMEOUT, self(), stop), 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}}; {next_state, StateName, StateData#state{timer = TRef}};
%% Activation message. %% Activation message.
handle_info({activate, PeerPid, PeerSocket, IJid, TJid}, handle_info({activate, PeerPid, PeerSocket, IJid, TJid},

View File

@ -52,7 +52,7 @@
%% exports for hooks %% exports for hooks
-export([presence_probe/3, caps_add/3, caps_update/3, -export([presence_probe/3, caps_add/3, caps_update/3,
in_subscription/6, out_subscription/4, 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_identity/5, disco_local_features/5,
disco_local_items/5, disco_sm_identity/5, disco_local_items/5, disco_sm_identity/5,
disco_sm_features/5, disco_sm_items/5, disco_sm_features/5, disco_sm_items/5,
@ -89,11 +89,7 @@
%% API and gen_server callbacks %% API and gen_server callbacks
-export([start/2, stop/1, init/1, -export([start/2, stop/1, init/1,
handle_call/3, handle_cast/2, handle_info/2, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3, depends/2]). terminate/2, code_change/3, depends/2, export/1, mod_opt_type/1]).
-export([send_loop/1, mod_opt_type/1]).
-define(LOOPNAME, ejabberd_mod_pubsub_loop).
%%==================================================================== %%====================================================================
%% API %% API
@ -189,7 +185,7 @@
-record(state, -record(state,
{ {
server_host, server_host,
host, hosts,
access, access,
pep_mapping = [], pep_mapping = [],
ignore_pep_from_offline = true, ignore_pep_from_offline = true,
@ -205,7 +201,7 @@
-type(state() :: -type(state() ::
#state{ #state{
server_host :: binary(), server_host :: binary(),
host :: mod_pubsub:hostPubsub(), hosts :: [mod_pubsub:hostPubsub()],
access :: atom(), access :: atom(),
pep_mapping :: [{binary(), binary()}], pep_mapping :: [{binary(), binary()}],
ignore_pep_from_offline :: boolean(), ignore_pep_from_offline :: boolean(),
@ -243,43 +239,66 @@ stop(Host) ->
init([ServerHost, Opts]) -> init([ServerHost, Opts]) ->
process_flag(trap_exit, true), process_flag(trap_exit, true),
?DEBUG("pubsub init ~p ~p", [ServerHost, Opts]), ?DEBUG("pubsub init ~p ~p", [ServerHost, Opts]),
Host = gen_mod:get_opt_host(ServerHost, Opts, <<"pubsub.@HOST@">>), Hosts = gen_mod:get_opt_hosts(ServerHost, Opts, <<"pubsub.@HOST@">>),
ejabberd_router:register_route(Host, ServerHost),
Access = gen_mod:get_opt(access_createnode, Opts, all), Access = gen_mod:get_opt(access_createnode, Opts, all),
PepOffline = gen_mod:get_opt(ignore_pep_from_offline, Opts, true), 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), LastItemCache = gen_mod:get_opt(last_item_cache, Opts, false),
MaxItemsNode = gen_mod:get_opt(max_items_node, Opts, ?MAXITEMS), MaxItemsNode = gen_mod:get_opt(max_items_node, Opts, ?MAXITEMS),
MaxSubsNode = gen_mod:get_opt(max_subscriptions_node, Opts), 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, ejabberd_mnesia:create(?MODULE, pubsub_last_item,
[{ram_copies, [node()]}, [{ram_copies, [node()]},
{attributes, record_info(fields, pubsub_last_item)}]), {attributes, record_info(fields, pubsub_last_item)}]),
lists:foreach( AllPlugins =
fun(H) -> lists:flatmap(
T = gen_mod:get_module_proc(H, config), fun(Host) ->
ets:new(T, [set, named_table]), ejabberd_router:register_route(Host, ServerHost),
ets:insert(T, {nodetree, NodeTree}), case gen_mod:db_type(ServerHost, ?MODULE) of
ets:insert(T, {plugins, Plugins}), mnesia -> pubsub_index:init(Host, ServerHost, Opts);
ets:insert(T, {last_item_cache, LastItemCache}), _ -> ok
ets:insert(T, {max_items_node, MaxItemsNode}), end,
ets:insert(T, {max_subscriptions_node, MaxSubsNode}), {Plugins, NodeTree, PepMapping} = init_plugins(Host, ServerHost, Opts),
ets:insert(T, {default_node_config, DefaultNodeCfg}), DefaultModule = plugin(Host, hd(Plugins)),
ets:insert(T, {pep_mapping, PepMapping}), BaseOptions = DefaultModule:options(),
ets:insert(T, {ignore_pep_from_offline, PepOffline}), DefaultNodeCfg = filter_node_options(
ets:insert(T, {host, Host}), gen_mod:get_opt(default_node_config, Opts, []),
ets:insert(T, {access, Access}) BaseOptions),
end, [Host, ServerHost]), lists:foreach(
ejabberd_hooks:add(sm_remove_connection_hook, ServerHost, 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), ?MODULE, on_user_offline, 75),
ejabberd_hooks:add(disco_local_identity, ServerHost, ejabberd_hooks:add(disco_local_identity, ServerHost,
?MODULE, disco_local_identity, 75), ?MODULE, disco_local_identity, 75),
@ -297,19 +316,7 @@ init([ServerHost, Opts]) ->
?MODULE, remove_user, 50), ?MODULE, remove_user, 50),
ejabberd_hooks:add(c2s_handle_info, ServerHost, ejabberd_hooks:add(c2s_handle_info, ServerHost,
?MODULE, c2s_handle_info, 50), ?MODULE, c2s_handle_info, 50),
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO, case lists:member(?PEPNODE, AllPlugins) of
?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
true -> true ->
ejabberd_hooks:add(caps_add, ServerHost, ejabberd_hooks:add(caps_add, ServerHost,
?MODULE, caps_add, 80), ?MODULE, caps_add, 80),
@ -328,35 +335,16 @@ init([ServerHost, Opts]) ->
false -> false ->
ok ok
end, end,
{_, State} = init_send_loop(ServerHost),
{ok, State}.
init_send_loop(ServerHost) ->
NodeTree = config(ServerHost, nodetree), NodeTree = config(ServerHost, nodetree),
Plugins = config(ServerHost, plugins), Plugins = config(ServerHost, plugins),
LastItemCache = config(ServerHost, last_item_cache),
MaxItemsNode = config(ServerHost, max_items_node),
PepMapping = config(ServerHost, pep_mapping), 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), DBType = gen_mod:db_type(ServerHost, ?MODULE),
State = #state{host = Host, server_host = ServerHost, {ok, #state{hosts = Hosts, server_host = ServerHost,
access = Access, pep_mapping = PepMapping, access = Access, pep_mapping = PepMapping,
ignore_pep_from_offline = PepOffline, ignore_pep_from_offline = PepOffline,
last_item_cache = LastItemCache, last_item_cache = LastItemCache,
max_items_node = MaxItemsNode, nodetree = NodeTree, max_items_node = MaxItemsNode, nodetree = NodeTree,
plugins = Plugins, db_type = DBType}, 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}.
depends(ServerHost, Opts) -> depends(ServerHost, Opts) ->
Host = gen_mod:get_opt_host(ServerHost, Opts, <<"pubsub.@HOST@">>), Host = gen_mod:get_opt_host(ServerHost, Opts, <<"pubsub.@HOST@">>),
@ -406,94 +394,6 @@ terminate_plugins(Host, ServerHost, Plugins, TreePlugin) ->
TreePlugin:terminate(Host, ServerHost), TreePlugin:terminate(Host, ServerHost),
ok. 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 %% disco hooks handling functions
%% %%
@ -546,13 +446,11 @@ disco_identity(Host, Node, From) ->
case get_allowed_items_call(Host, Nidx, From, Type, case get_allowed_items_call(Host, Nidx, From, Type,
Options, Owners) of Options, Owners) of
{result, _} -> {result, _} ->
{result, [#identity{category = <<"pubsub">>, {result, [#identity{category = <<"pubsub">>, type = <<"pep">>},
type = <<"pep">>}, #identity{category = <<"pubsub">>, type = <<"leaf">>,
#identity{category = <<"pubsub">>,
type = <<"leaf">>,
name = case get_option(Options, title) of name = case get_option(Options, title) of
false -> <<>>; false -> <<>>;
[Title] -> Title Title -> Title
end}]}; end}]};
_ -> _ ->
{result, []} {result, []}
@ -586,8 +484,7 @@ disco_features(Host, Node, From) ->
Type, Options, Owners) of Type, Options, Owners) of
{result, _} -> {result, _} ->
{result, {result,
[?NS_PUBSUB | [?NS_PUBSUB | [feature(F) || F <- plugin_features(Host, <<"pep">>)]]};
[feature(F) || F <- plugin_features(Host, <<"pep">>)]]};
_ -> _ ->
{result, []} {result, []}
end end
@ -620,7 +517,7 @@ disco_items(Host, <<>>, From) ->
jid = jid:make(Host), jid = jid:make(Host),
name = case get_option(Options, title) of name = case get_option(Options, title) of
false -> <<>>; false -> <<>>;
[Title] -> Title Title -> Title
end} | Acc]; end} | Acc];
_ -> _ ->
Acc Acc
@ -655,12 +552,12 @@ disco_items(Host, Node, From) ->
end. end.
%% ------- %% -------
%% presence hooks handling functions %% presence and session hooks handling functions
%% %%
-spec caps_add(jid(), jid(), [binary()]) -> ok. -spec caps_add(jid(), jid(), [binary()]) -> ok.
caps_add(#jid{luser = U, lserver = S, lresource = R}, #jid{lserver = Host} = JID, _Features) caps_add(#jid{lserver = S1} = From, #jid{lserver = S2} = To, _Features)
when Host =/= S -> when S1 =/= S2 ->
%% When a remote contact goes online while the local user is offline, the %% 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 %% 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, %% 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 %% contact becomes available; the former is also executed when the local
%% user goes online (because that triggers the contact to send a presence %% user goes online (because that triggers the contact to send a presence
%% packet with CAPS). %% packet with CAPS).
presence(Host, {presence, U, S, [R], JID}); send_last_pep(To, From);
caps_add(_From, _To, _Feature) -> caps_add(_From, _To, _Feature) ->
ok. ok.
-spec caps_update(jid(), jid(), [binary()]) -> ok. -spec caps_update(jid(), jid(), [binary()]) -> ok.
caps_update(#jid{luser = U, lserver = S, lresource = R}, #jid{lserver = Host} = JID, _Features) -> caps_update(From, To, _Features) ->
presence(Host, {presence, U, S, [R], JID}). send_last_pep(To, From).
-spec presence_probe(jid(), jid(), pid()) -> ok. -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) -> presence_probe(#jid{luser = U, lserver = S}, #jid{luser = U, lserver = S}, _Pid) ->
%% ignore presence_probe from my other ressources %% ignore presence_probe from my other ressources
%% to not get duplicated last items
ok; ok;
presence_probe(#jid{luser = U, lserver = S, lresource = R}, #jid{lserver = S} = JID, _Pid) -> presence_probe(#jid{lserver = S} = From, #jid{lserver = S} = To, _Pid) ->
presence(S, {presence, U, S, [R], JID}); send_last_pep(To, From);
presence_probe(_Host, _JID, _Pid) -> presence_probe(_From, _To, _Pid) ->
%% ignore presence_probe from remote contacts, %% ignore presence_probe from remote contacts, those are handled via caps_add
%% those are handled via caps_add
ok. ok.
presence(ServerHost, Presence) -> -spec on_user_online(ejabberd_c2s:state()) -> ejabberd_c2s:state().
{SendLoop, _} = case whereis(gen_mod:get_module_proc(ServerHost, ?LOOPNAME)) of on_user_online(C2SState) ->
undefined -> init_send_loop(ServerHost); JID = maps:get(jid, C2SState),
Pid -> {Pid, undefined} send_last_items(JID),
end, C2SState.
SendLoop ! Presence,
ok. -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 %% subscription hooks handling functions
@ -708,14 +605,8 @@ presence(ServerHost, Presence) ->
-spec out_subscription( -spec out_subscription(
binary(), binary(), jid(), binary(), binary(), jid(),
subscribed | unsubscribed | subscribe | unsubscribe) -> boolean(). subscribed | unsubscribed | subscribe | unsubscribe) -> boolean().
out_subscription(User, Server, JID, subscribed) -> out_subscription(User, Server, To, subscribed) ->
Owner = jid:make(User, Server), send_last_pep(jid:make(User, Server), To),
{PUser, PServer, PResource} = jid:tolower(JID),
PResources = case PResource of
<<>> -> user_resources(PUser, PServer);
_ -> [PResource]
end,
presence(Server, {presence, PUser, PServer, PResources, Owner}),
true; true;
out_subscription(_, _, _, _) -> out_subscription(_, _, _, _) ->
true. true.
@ -864,7 +755,7 @@ handle_info(_Info, State) ->
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% @private %% @private
terminate(_Reason, 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 case lists:member(?PEPNODE, Plugins) of
true -> true ->
ejabberd_hooks:delete(caps_add, ServerHost, ejabberd_hooks:delete(caps_add, ServerHost,
@ -884,7 +775,9 @@ terminate(_Reason,
false -> false ->
ok ok
end, 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), ?MODULE, on_user_offline, 75),
ejabberd_hooks:delete(disco_local_identity, ServerHost, ejabberd_hooks:delete(disco_local_identity, ServerHost,
?MODULE, disco_local_identity, 75), ?MODULE, disco_local_identity, 75),
@ -902,20 +795,17 @@ terminate(_Reason,
?MODULE, remove_user, 50), ?MODULE, remove_user, 50),
ejabberd_hooks:delete(c2s_handle_info, ServerHost, ejabberd_hooks:delete(c2s_handle_info, ServerHost,
?MODULE, c2s_handle_info, 50), ?MODULE, c2s_handle_info, 50),
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO), lists:foreach(
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS), fun(Host) ->
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_PUBSUB), gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO),
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_PUBSUB_OWNER), 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_PUBSUB),
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_COMMANDS), gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_PUBSUB_OWNER),
case whereis(gen_mod:get_module_proc(ServerHost, ?LOOPNAME)) of gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_VCARD),
undefined -> gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_COMMANDS),
?ERROR_MSG("~s process is dead, pubsub was broken", [?LOOPNAME]); terminate_plugins(Host, ServerHost, Plugins, TreePlugin),
Pid -> ejabberd_router:unregister_route(Host)
Pid ! stop end, Hosts).
end,
terminate_plugins(Host, ServerHost, Plugins, TreePlugin),
ejabberd_router:unregister_route(Host).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState} %% 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 support subscriptions.</li>
%%<li>The node does not exist.</li> %%<li>The node does not exist.</li>
%%</ul> %%</ul>
-spec subscribe_node(host(), binary(), jid(), binary(), [{binary(), [binary()]}]) -> -spec subscribe_node(host(), binary(), jid(), jid(), [{binary(), [binary()]}]) ->
{result, pubsub()} | {error, stanza_error()}. {result, pubsub()} | {error, stanza_error()}.
subscribe_node(Host, Node, From, JID, Configuration) -> subscribe_node(Host, Node, From, JID, Configuration) ->
SubModule = subscription_plugin(Host), SubModule = subscription_plugin(Host),
@ -1795,7 +1685,7 @@ subscribe_node(Host, Node, From, JID, Configuration) ->
Nidx = TNode#pubsub_node.id, Nidx = TNode#pubsub_node.id,
Type = TNode#pubsub_node.type, Type = TNode#pubsub_node.type,
Options = TNode#pubsub_node.options, 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), ServerHost = serverhost(Host),
ejabberd_hooks:run(pubsub_subscribe_node, ServerHost, ejabberd_hooks:run(pubsub_subscribe_node, ServerHost,
[ServerHost, Host, Node, Subscriber, SubId]), [ServerHost, Host, Node, Subscriber, SubId]),
@ -2067,8 +1957,6 @@ purge_node(Host, Node, Owner) ->
%% @doc <p>Return the items of a given node.</p> %% @doc <p>Return the items of a given node.</p>
%% <p>The number of items to return is limited by MaxItems.</p> %% <p>The number of items to return is limited by MaxItems.</p>
%% <p>The permission are not checked in this function.</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(), -spec get_items(host(), binary(), jid(), binary(),
binary(), [binary()], undefined | rsm_set()) -> binary(), [binary()], undefined | rsm_set()) ->
{result, pubsub()} | {error, stanza_error()}. {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), {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]). 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 case node_action(Host, Type, get_last_items, [Nidx, LJID, Count]) of
{result, Items} -> Items; {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; end;
send_items(Host, Node, Nidx, Type, Options, LJID, Number) when Number > 0 -> get_last_items(_Host, _Type, _Nidx, _LJID, _Count) ->
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))).
%% @doc <p>Return the list of affiliations as an XMPP response.</p> %% @doc <p>Return the list of affiliations as an XMPP response.</p>
-spec get_affiliations(host(), binary(), jid(), [binary()]) -> -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, {result, Subs} = node_action(Host, PType,
get_entity_subscriptions_for_send_last, get_entity_subscriptions_for_send_last,
[Host, JID]), [Host, JID]),
[{Node, Sub, SubId, SubJID} [{Node, SubId, SubJID}
|| {Node, Sub, SubId, SubJID} <- Subs, || {Node, Sub, SubId, SubJID} <- Subs,
Sub =:= subscribed, (SubJID == LJID) or (SubJID == BJID)]; 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) -> get_subscriptions_for_send_last(Host, PType, _, JID, LJID, BJID) ->
{result, Subs} = node_action(Host, PType, {result, Subs} = node_action(Host, PType,
get_entity_subscriptions, get_entity_subscriptions,
[Host, JID]), [Host, JID]),
[{Node, Sub, SubId, SubJID} [{Node, SubId, SubJID}
|| {Node, Sub, SubId, SubJID} <- Subs, || {Node, Sub, SubId, SubJID} <- Subs,
Sub =:= subscribed, (SubJID == LJID) or (SubJID == BJID), Sub =:= subscribed, (SubJID == LJID) or (SubJID == BJID),
match_option(Node, send_last_published_item, on_sub_and_presence)]. 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) -> broadcast_stanza(Host, _Node, _Nidx, _Type, NodeOptions, SubsByDepth, NotifyType, BaseStanza, SHIM) ->
NotificationType = get_option(NodeOptions, notification_type, headline), NotificationType = get_option(NodeOptions, notification_type, headline),
BroadcastAll = get_option(NodeOptions, broadcast_all_resources), %% XXX this is not standard, but usefull BroadcastAll = get_option(NodeOptions, broadcast_all_resources), %% XXX this is not standard, but usefull
From = service_jid(Host), Stanza = add_message_type(
Stanza = add_message_type(BaseStanza, NotificationType), xmpp:set_from(BaseStanza, service_jid(Host)),
NotificationType),
%% Handles explicit subscriptions %% Handles explicit subscriptions
SubIDsByJID = subscribed_nodes_by_jid(NotifyType, SubsByDepth), SubIDsByJID = subscribed_nodes_by_jid(NotifyType, SubsByDepth),
lists:foreach(fun ({LJID, _NodeName, SubIDs}) -> lists:foreach(fun ({LJID, _NodeName, SubIDs}) ->
@ -2949,7 +2824,7 @@ broadcast_stanza(Host, _Node, _Nidx, _Type, NodeOptions, SubsByDepth, NotifyType
end, end,
lists:foreach(fun(To) -> lists:foreach(fun(To) ->
ejabberd_router:route( ejabberd_router:route(
xmpp:set_from_to(StanzaToSend, From, jid:make(To))) xmpp:set_to(StanzaToSend, jid:make(To)))
end, LJIDs) end, LJIDs)
end, SubIDsByJID). end, SubIDsByJID).
@ -2958,55 +2833,142 @@ broadcast_stanza({LUser, LServer, LResource}, Publisher, Node, Nidx, Type, NodeO
%% Handles implicit presence subscriptions %% Handles implicit presence subscriptions
SenderResource = user_resource(LUser, LServer, LResource), SenderResource = user_resource(LUser, LServer, LResource),
NotificationType = get_option(NodeOptions, notification_type, headline), 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 %% 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 %% Also, add "replyto" if entity has presence subscription to the account owner
%% See XEP-0163 1.1 section 4.3.1 %% See XEP-0163 1.1 section 4.3.1
ejabberd_sm:route(jid:make(LUser, LServer, SenderResource), ejabberd_sm:route(jid:make(LUser, LServer, SenderResource),
{pep_message, <<((Node))/binary, "+notify">>, {pep_message, <<((Node))/binary, "+notify">>,
jid:make(LUser, LServer),
add_extended_headers( add_extended_headers(
Stanza, extended_headers([Publisher]))}); Stanza, extended_headers([Publisher]))});
broadcast_stanza(Host, _Publisher, Node, Nidx, Type, NodeOptions, SubsByDepth, NotifyType, BaseStanza, SHIM) -> broadcast_stanza(Host, _Publisher, Node, Nidx, Type, NodeOptions, SubsByDepth, NotifyType, BaseStanza, SHIM) ->
broadcast_stanza(Host, 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(). -spec c2s_handle_info(ejabberd_c2s:state(), term()) -> ejabberd_c2s:state().
c2s_handle_info(#{server := Server} = C2SState, c2s_handle_info(#{lserver := LServer} = C2SState,
{pep_message, Feature, From, Packet}) -> {pep_message, Feature, Packet}) ->
LServer = jid:nameprep(Server), [maybe_send_pep_stanza(LServer, USR, Caps, Feature, Packet)
lists:foreach( || {USR, Caps} <- mod_caps:list_features(C2SState)],
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)),
{stop, C2SState}; {stop, C2SState};
c2s_handle_info(#{server := Server} = C2SState, c2s_handle_info(#{lserver := LServer} = C2SState,
{send_filtered, {pep_message, Feature}, From, To, Packet}) -> {pep_message, Feature, Packet, USR}) ->
LServer = jid:nameprep(Server), case mod_caps:get_user_caps(USR, C2SState) of
case mod_caps:get_user_caps(To, C2SState) of {ok, Caps} -> maybe_send_pep_stanza(LServer, USR, Caps, Feature, Packet);
{ok, Caps} -> error -> ok
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
end, end,
{stop, C2SState}; {stop, C2SState};
c2s_handle_info(C2SState, _) -> c2s_handle_info(C2SState, _) ->
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) -> subscribed_nodes_by_jid(NotifyType, SubsByDepth) ->
NodesToDeliver = fun (Depth, Node, Subs, Acc) -> NodesToDeliver = fun (Depth, Node, Subs, Acc) ->
NodeName = case Node#pubsub_node.nodeid of 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> %% @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 %% <p>Unlimited means that there is no limit in the number of items that can
%% be stored.</p> %% 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(). -spec max_items(host(), [{atom(), any()}]) -> non_neg_integer().
max_items(Host, Options) -> max_items(Host, Options) ->
case get_option(Options, persist_items) of case get_option(Options, persist_items) of
@ -3785,14 +3744,6 @@ subid_shim(SubIds) ->
extended_headers(Jids) -> extended_headers(Jids) ->
[#address{type = replyto, jid = Jid} || Jid <- 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. -spec purge_offline(ljid()) -> ok.
purge_offline(LJID) -> purge_offline(LJID) ->
Host = host(element(2, LJID)), Host = host(element(2, LJID)),
@ -3833,7 +3784,7 @@ purge_offline(LJID) ->
end end
end, lists:usort(lists:flatten(Affs))); end, lists:usort(lists:flatten(Affs)));
{Error, _} -> {Error, _} ->
?DEBUG("on_user_offline ~p", [Error]) ?ERROR_MSG("can not purge offline: ~p", [Error])
end. end.
-spec purge_offline(host(), ljid(), binary()) -> ok | {error, stanza_error()}. -spec purge_offline(host(), ljid(), binary()) -> ok | {error, stanza_error()}.
@ -3845,13 +3796,13 @@ purge_offline(Host, LJID, Node) ->
{result, {[], _}} -> {result, {[], _}} ->
ok; ok;
{result, {Items, _}} -> {result, {Items, _}} ->
{User, Server, _} = LJID, {User, Server, Resource} = LJID,
PublishModel = get_option(Options, publish_model), PublishModel = get_option(Options, publish_model),
ForceNotify = get_option(Options, notify_retract), ForceNotify = get_option(Options, notify_retract),
{_, NodeId} = Node#pubsub_node.nodeid, {_, NodeId} = Node#pubsub_node.nodeid,
lists:foreach(fun lists:foreach(fun
(#pubsub_item{itemid = {ItemId, _}, modification = {_, {U, S, _}}}) (#pubsub_item{itemid = {ItemId, _}, modification = {_, {U, S, R}}})
when (U == User) and (S == Server) -> when (U == User) and (S == Server) and (R == Resource) ->
case node_action(Host, Type, delete_item, [Nidx, {U, S, <<>>}, PublishModel, ItemId]) of case node_action(Host, Type, delete_item, [Nidx, {U, S, <<>>}, PublishModel, ItemId]) of
{result, {_, broadcast}} -> {result, {_, broadcast}} ->
broadcast_retract_items(Host, NodeId, Nidx, Type, Options, [ItemId], ForceNotify), broadcast_retract_items(Host, NodeId, Nidx, Type, Options, [ItemId], ForceNotify),
@ -3871,9 +3822,14 @@ purge_offline(Host, LJID, Node) ->
Error Error
end. end.
export(Server) ->
pubsub_db_sql:export(Server).
mod_opt_type(access_createnode) -> fun acl:access_rules_validator/1; 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(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
mod_opt_type(host) -> 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(ignore_pep_from_offline) -> mod_opt_type(ignore_pep_from_offline) ->
fun (A) when is_boolean(A) -> A end; fun (A) when is_boolean(A) -> A end;
mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1; mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
@ -3892,7 +3848,7 @@ mod_opt_type(pep_mapping) ->
mod_opt_type(plugins) -> mod_opt_type(plugins) ->
fun (A) when is_list(A) -> A end; fun (A) when is_list(A) -> A end;
mod_opt_type(_) -> mod_opt_type(_) ->
[access_createnode, db_type, host, [access_createnode, db_type, host, hosts,
ignore_pep_from_offline, iqdisc, last_item_cache, ignore_pep_from_offline, iqdisc, last_item_cache,
max_items_node, nodetree, pep_mapping, plugins, max_items_node, nodetree, pep_mapping, plugins,
max_subscriptions_node, default_node_config]. max_subscriptions_node, default_node_config].

596
src/mod_push.erl Normal file
View File

@ -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.

236
src/mod_push_keepalive.erl Normal file
View File

@ -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.

204
src/mod_push_mnesia.erl Normal file
View File

@ -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.

View File

@ -35,7 +35,6 @@
-module(mod_roster). -module(mod_roster).
-protocol({xep, 237, '1.3'}). -protocol({xep, 237, '1.3'}).
-protocol({xep, 321, '0.1'}).
-author('alexey@process-one.net'). -author('alexey@process-one.net').
@ -441,14 +440,13 @@ decode_item(Item, R, Managed) ->
end, end,
groups = Item#roster_item.groups}. 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) -> sub_els = [#roster_query{items = [QueryItem]}]} = IQ) ->
#jid{user = User, luser = LUser, lserver = LServer} = To, #jid{user = User, luser = LUser, lserver = LServer} = To,
Managed = {From#jid.luser, From#jid.lserver} /= {LUser, LServer},
LJID = jid:tolower(QueryItem#roster_item.jid), LJID = jid:tolower(QueryItem#roster_item.jid),
F = fun () -> F = fun () ->
Item = get_roster_item(LUser, LServer, LJID), 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, Item3 = ejabberd_hooks:run_fold(roster_process_item,
LServer, Item2, LServer, Item2,
[LServer]), [LServer]),
@ -1200,8 +1198,6 @@ mod_opt_type(access) ->
fun acl:access_rules_validator/1; fun acl:access_rules_validator/1;
mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end; 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(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) -> mod_opt_type(store_current_id) ->
fun (B) when is_boolean(B) -> B end; fun (B) when is_boolean(B) -> B end;
mod_opt_type(versioning) -> 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 -> mod_opt_type(O) when O == use_cache; O == cache_missed ->
fun (B) when is_boolean(B) -> B end; fun (B) when is_boolean(B) -> B end;
mod_opt_type(_) -> 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]. versioning, cache_life_time, cache_size, use_cache, cache_missed].

View File

@ -27,8 +27,7 @@
-ifndef(SIP). -ifndef(SIP).
-export([]). -export([]).
-else. -else.
-define(GEN_FSM, p1_fsm). -behaviour(p1_fsm).
-behaviour(?GEN_FSM).
%% API %% API
-export([start/2, start_link/2, route/3, route/4]). -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]). supervisor:start_child(mod_sip_proxy_sup, [LServer, Opts]).
start_link(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) -> 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) -> route(#sip{hdrs = Hdrs} = Req, LServer, Opts) ->
case proplists:get_bool(authenticated, Opts) of case proplists:get_bool(authenticated, Opts) of

View File

@ -33,6 +33,8 @@
c2s_unbinded_packet/2, c2s_closed/2, c2s_terminated/2, c2s_unbinded_packet/2, c2s_closed/2, c2s_terminated/2,
c2s_handle_send/3, c2s_handle_info/2, c2s_handle_call/3, c2s_handle_send/3, c2s_handle_info/2, c2s_handle_call/3,
c2s_handle_recv/3]). c2s_handle_recv/3]).
%% adjust pending session timeout
-export([get_resume_timeout/1, set_resume_timeout/2]).
-include("xmpp.hrl"). -include("xmpp.hrl").
-include("logger.hrl"). -include("logger.hrl").
@ -69,7 +71,12 @@ start(Host, _Opts) ->
ejabberd_hooks:add(c2s_terminated, Host, ?MODULE, c2s_terminated, 50). ejabberd_hooks:add(c2s_terminated, Host, ?MODULE, c2s_terminated, 50).
stop(Host) -> 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, ejabberd_hooks:delete(c2s_stream_started, Host, ?MODULE,
c2s_stream_started, 50), c2s_stream_started, 50),
ejabberd_hooks:delete(c2s_post_auth_features, Host, ?MODULE, 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)]), [jid:encode(JID)]),
State1 = Mod:close(State), State1 = Mod:close(State),
{stop, transition_to_pending(State1)}; {stop, transition_to_pending(State1)};
c2s_handle_info(#{mgmt_state := pending, jid := JID, mod := Mod} = State, c2s_handle_info(#{mgmt_state := pending,
{timeout, _, pending_timeout}) -> mgmt_pending_timer := TRef, jid := JID, mod := Mod} = State,
{timeout, TRef, pending_timeout}) ->
?DEBUG("Timed out waiting for resumption of stream for ~s", ?DEBUG("Timed out waiting for resumption of stream for ~s",
[jid:encode(JID)]), [jid:encode(JID)]),
Mod:stop(State#{mgmt_state => timeout}); Mod:stop(State#{mgmt_state => timeout});
@ -282,6 +290,20 @@ c2s_terminated(#{mgmt_state := MgmtState, mgmt_stanzas_in := In, sid := SID,
c2s_terminated(State, _Reason) -> c2s_terminated(State, _Reason) ->
State. 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 %%% Internal functions
%%%=================================================================== %%%===================================================================
@ -388,8 +410,6 @@ handle_resume(#{user := User, lserver := LServer, sockmod := SockMod,
previd = AttrId}), previd = AttrId}),
State3 = resend_unacked_stanzas(State2), State3 = resend_unacked_stanzas(State2),
State4 = send(State3, #sm_r{xmlns = AttrXmlns}), 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, []), State5 = ejabberd_hooks:run_fold(c2s_session_resumed, LServer, State4, []),
?INFO_MSG("(~s) Resumed session for ~s", ?INFO_MSG("(~s) Resumed session for ~s",
[SockMod:pp(Socket), jid:encode(JID)]), [SockMod:pp(Socket), jid:encode(JID)]),
@ -408,8 +428,8 @@ transition_to_pending(#{mgmt_state := active, jid := JID,
lserver := LServer, mgmt_timeout := Timeout} = State) -> lserver := LServer, mgmt_timeout := Timeout} = State) ->
State1 = cancel_ack_timer(State), State1 = cancel_ack_timer(State),
?INFO_MSG("Waiting for resumption of stream for ~s", [jid:encode(JID)]), ?INFO_MSG("Waiting for resumption of stream for ~s", [jid:encode(JID)]),
erlang:start_timer(timer:seconds(Timeout), self(), pending_timeout), TRef = erlang:start_timer(timer:seconds(Timeout), self(), pending_timeout),
State2 = State1#{mgmt_state => pending}, State2 = State1#{mgmt_state => pending, mgmt_pending_timer => TRef},
ejabberd_hooks:run_fold(c2s_session_pending, LServer, State2, []); ejabberd_hooks:run_fold(c2s_session_pending, LServer, State2, []);
transition_to_pending(State) -> transition_to_pending(State) ->
State. State.
@ -648,20 +668,33 @@ add_resent_delay_info(_State, El, _Time) ->
send(#{mod := Mod} = State, Pkt) -> send(#{mod := Mod} = State, Pkt) ->
Mod:send(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(). -spec cancel_ack_timer(state()) -> state().
cancel_ack_timer(#{mgmt_ack_timer := TRef} = State) -> cancel_ack_timer(#{mgmt_ack_timer := TRef} = State) ->
case erlang:cancel_timer(TRef) of cancel_timer(TRef),
false ->
receive {timeout, TRef, _} -> ok
after 0 -> ok
end;
_ ->
ok
end,
maps:remove(mgmt_ack_timer, State); maps:remove(mgmt_ack_timer, State);
cancel_ack_timer(State) -> cancel_ack_timer(State) ->
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. -spec bounce_message_queue() -> ok.
bounce_message_queue() -> bounce_message_queue() ->
receive {route, Pkt} -> receive {route, Pkt} ->

View File

@ -67,7 +67,7 @@
-optional_callbacks([use_cache/1, cache_nodes/1]). -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 %% gen_mod callbacks
@ -95,37 +95,40 @@ init([Host, Opts]) ->
?NS_VCARD, ?MODULE, process_sm_iq, IQDisc), ?NS_VCARD, ?MODULE, process_sm_iq, IQDisc),
ejabberd_hooks:add(disco_sm_features, Host, ?MODULE, ejabberd_hooks:add(disco_sm_features, Host, ?MODULE,
get_sm_features, 50), 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), Search = gen_mod:get_opt(search, Opts, false),
if Search -> if Search ->
ejabberd_hooks:add( lists:foreach(
disco_local_items, MyHost, ?MODULE, disco_items, 100), fun(MyHost) ->
ejabberd_hooks:add( ejabberd_hooks:add(
disco_local_features, MyHost, ?MODULE, disco_features, 100), disco_local_items, MyHost, ?MODULE, disco_items, 100),
ejabberd_hooks:add( ejabberd_hooks:add(
disco_local_identity, MyHost, ?MODULE, disco_identity, 100), disco_local_features, MyHost, ?MODULE, disco_features, 100),
gen_iq_handler:add_iq_handler( ejabberd_hooks:add(
ejabberd_local, MyHost, ?NS_SEARCH, ?MODULE, process_search, IQDisc), disco_local_identity, MyHost, ?MODULE, disco_identity, 100),
gen_iq_handler:add_iq_handler( gen_iq_handler:add_iq_handler(
ejabberd_local, MyHost, ?NS_VCARD, ?MODULE, process_vcard, IQDisc), ejabberd_local, MyHost, ?NS_SEARCH, ?MODULE, process_search, IQDisc),
gen_iq_handler:add_iq_handler( gen_iq_handler:add_iq_handler(
ejabberd_local, MyHost, ?NS_DISCO_ITEMS, mod_disco, ejabberd_local, MyHost, ?NS_VCARD, ?MODULE, process_vcard, IQDisc),
process_local_iq_items, IQDisc), gen_iq_handler:add_iq_handler(
gen_iq_handler:add_iq_handler( ejabberd_local, MyHost, ?NS_DISCO_ITEMS, mod_disco,
ejabberd_local, MyHost, ?NS_DISCO_INFO, mod_disco, process_local_iq_items, IQDisc),
process_local_iq_info, IQDisc), gen_iq_handler:add_iq_handler(
case Mod:is_search_supported(Host) of ejabberd_local, MyHost, ?NS_DISCO_INFO, mod_disco,
false -> process_local_iq_info, IQDisc),
?WARNING_MSG("vcard search functionality is " case Mod:is_search_supported(Host) of
"not implemented for ~s backend", false ->
[gen_mod:db_type(Host, Opts, ?MODULE)]); ?WARNING_MSG("vcard search functionality is "
true -> "not implemented for ~s backend",
ejabberd_router:register_route(MyHost, Host) [gen_mod:db_type(Host, Opts, ?MODULE)]);
end; true ->
ejabberd_router:register_route(MyHost, Host)
end
end, MyHosts);
true -> true ->
ok ok
end, end,
{ok, #state{host = MyHost, server_host = Host}}. {ok, #state{hosts = MyHosts, server_host = Host}}.
handle_call(_Call, _From, State) -> handle_call(_Call, _From, State) ->
{noreply, State}. {noreply, State}.
@ -144,21 +147,24 @@ handle_info(Info, State) ->
?WARNING_MSG("unexpected info: ~p", [Info]), ?WARNING_MSG("unexpected info: ~p", [Info]),
{noreply, State}. {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), 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_local, Host, ?NS_VCARD),
gen_iq_handler:remove_iq_handler(ejabberd_sm, 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), ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE, get_sm_features, 50),
Mod = gen_mod:db_mod(Host, ?MODULE), Mod = gen_mod:db_mod(Host, ?MODULE),
Mod:stop(Host), Mod:stop(Host),
ejabberd_router:unregister_route(MyHost), lists:foreach(
ejabberd_hooks:delete(disco_local_items, MyHost, ?MODULE, disco_items, 100), fun(MyHost) ->
ejabberd_hooks:delete(disco_local_features, MyHost, ?MODULE, disco_features, 100), ejabberd_router:unregister_route(MyHost),
ejabberd_hooks:delete(disco_local_identity, MyHost, ?MODULE, disco_identity, 100), ejabberd_hooks:delete(disco_local_items, MyHost, ?MODULE, disco_items, 100),
gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_SEARCH), ejabberd_hooks:delete(disco_local_features, MyHost, ?MODULE, disco_features, 100),
gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_VCARD), ejabberd_hooks:delete(disco_local_identity, MyHost, ?MODULE, disco_identity, 100),
gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_ITEMS), gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_SEARCH),
gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_INFO). 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) -> code_change(_OldVsn, State, _Extra) ->
{ok, State}. {ok, State}.
@ -527,6 +533,8 @@ mod_opt_type(allow_return_all) ->
fun (B) when is_boolean(B) -> B end; 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(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
mod_opt_type(host) -> 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(iqdisc) -> fun gen_iq_handler:check_type/1; mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
mod_opt_type(matches) -> mod_opt_type(matches) ->
fun (infinity) -> infinity; 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 -> mod_opt_type(O) when O == use_cache; O == cache_missed ->
fun (B) when is_boolean(B) -> B end; fun (B) when is_boolean(B) -> B end;
mod_opt_type(_) -> 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, search, search_all_hosts, cache_life_time, cache_size,
use_cache, cache_missed]. use_cache, cache_missed].

View File

@ -47,7 +47,7 @@
-record(state, -record(state,
{serverhost = <<"">> :: binary(), {serverhost = <<"">> :: binary(),
myhost = <<"">> :: binary(), myhosts = [] :: [binary()],
eldap_id = <<"">> :: binary(), eldap_id = <<"">> :: binary(),
search = false :: boolean(), search = false :: boolean(),
servers = [] :: [binary()], servers = [] :: [binary()],
@ -351,8 +351,7 @@ default_search_reported() ->
{<<"Organization Unit">>, <<"ORGUNIT">>}]. {<<"Organization Unit">>, <<"ORGUNIT">>}].
parse_options(Host, Opts) -> parse_options(Host, Opts) ->
MyHost = gen_mod:get_opt_host(Host, Opts, MyHosts = gen_mod:get_opt_hosts(Host, Opts, <<"vjud.@HOST@">>),
<<"vjud.@HOST@">>),
Search = gen_mod:get_opt(search, Opts, false), Search = gen_mod:get_opt(search, Opts, false),
Matches = gen_mod:get_opt(matches, Opts, 30), Matches = gen_mod:get_opt(matches, Opts, 30),
Eldap_ID = misc:atom_to_binary(gen_mod:get_module_proc(Host, ?PROCNAME)), Eldap_ID = misc:atom_to_binary(gen_mod:get_module_proc(Host, ?PROCNAME)),
@ -394,7 +393,7 @@ parse_options(Host, Opts) ->
end, end,
SearchReported) SearchReported)
++ UIDAttrs), ++ UIDAttrs),
#state{serverhost = Host, myhost = MyHost, #state{serverhost = Host, myhosts = MyHosts,
eldap_id = Eldap_ID, search = Search, eldap_id = Eldap_ID, search = Search,
servers = Cfg#eldap_config.servers, servers = Cfg#eldap_config.servers,
backups = Cfg#eldap_config.backups, backups = Cfg#eldap_config.backups,

View File

@ -119,7 +119,7 @@ create_node(Nidx, Owner) ->
delete_node(Nodes) -> delete_node(Nodes) ->
{result, {_, _, Result}} = node_flat:delete_node(Nodes), {result, {_, _, Result}} = node_flat:delete_node(Nodes),
{result, {[], Result}}. {result, {default, Result}}.
subscribe_node(Nidx, Sender, Subscriber, AccessModel, subscribe_node(Nidx, Sender, Subscriber, AccessModel,
SendLast, PresenceSubscription, RosterGroup, Options) -> SendLast, PresenceSubscription, RosterGroup, Options) ->

View File

@ -73,7 +73,7 @@ create_node(Nidx, Owner) ->
delete_node(Nodes) -> delete_node(Nodes) ->
{result, {_, _, Result}} = node_flat_sql:delete_node(Nodes), {result, {_, _, Result}} = node_flat_sql:delete_node(Nodes),
{result, {[], Result}}. {result, {default, Result}}.
subscribe_node(Nidx, Sender, Subscriber, AccessModel, subscribe_node(Nidx, Sender, Subscriber, AccessModel,
SendLast, PresenceSubscription, RosterGroup, Options) -> SendLast, PresenceSubscription, RosterGroup, Options) ->

View File

@ -50,7 +50,7 @@ from_dir(ProsodyDir) ->
convert_dir(Path, Host, SubDir) convert_dir(Path, Host, SubDir)
end, ["vcard", "accounts", "roster", end, ["vcard", "accounts", "roster",
"private", "config", "offline", "private", "config", "offline",
"privacy", "pubsub"]) "privacy", "pep", "pubsub"])
end, HostDirs); end, HostDirs);
{error, Why} = Err -> {error, Why} = Err ->
?ERROR_MSG("failed to list ~s: ~s", ?ERROR_MSG("failed to list ~s: ~s",
@ -67,12 +67,24 @@ convert_dir(Path, Host, Type) ->
lists:foreach( lists:foreach(
fun(File) -> fun(File) ->
FilePath = filename:join(Path, File), FilePath = filename:join(Path, File),
case eval_file(FilePath) of case Type of
{ok, Data} -> "pep" ->
Name = iolist_to_binary(filename:rootname(File)), case filelib:is_dir(FilePath) of
convert_data(Host, Type, Name, Data); true ->
Err -> JID = list_to_binary(File ++ "@" ++ Host),
Err 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
end, Files); end, Files);
{error, enoent} -> {error, enoent} ->
@ -212,44 +224,49 @@ convert_data(Host, "privacy", User, [Data]) ->
end end
end, Lists)}, end, Lists)},
mod_privacy:set_list(Priv); mod_privacy:set_list(Priv);
convert_data(PubSub, "pubsub", NodeId, [Data]) -> convert_data(HostStr, "pubsub", Node, [Data]) ->
Host = url_decode(PubSub), case decode_pubsub_host(HostStr) of
Node = url_decode(NodeId), Host when is_binary(Host);
Type = node_type(Host, Node), is_tuple(Host) ->
NodeData = convert_node_config(Host, Data), Type = node_type(Host),
DefaultConfig = mod_pubsub:config(Host, default_node_config, []), NodeData = convert_node_config(HostStr, Data),
Owner = proplists:get_value(owner, NodeData), DefaultConfig = mod_pubsub:config(Host, default_node_config, []),
Options = lists:foldl( Owner = proplists:get_value(owner, NodeData),
fun({_Opt, undefined}, Acc) -> Options = lists:foldl(
Acc; fun({_Opt, undefined}, Acc) ->
({Opt, Val}, Acc) -> Acc;
lists:keystore(Opt, 1, Acc, {Opt, Val}) ({Opt, Val}, Acc) ->
end, DefaultConfig, proplists:get_value(options, NodeData)), lists:keystore(Opt, 1, Acc, {Opt, Val})
case mod_pubsub:tree_action(Host, create_node, [Host, Node, Type, Owner, Options, []]) of end, DefaultConfig, proplists:get_value(options, NodeData)),
{ok, Nidx} -> case mod_pubsub:tree_action(Host, create_node, [Host, Node, Type, Owner, Options, []]) of
case mod_pubsub:node_action(Host, Type, create_node, [Nidx, Owner]) of {ok, Nidx} ->
{result, _} -> case mod_pubsub:node_action(Host, Type, create_node, [Nidx, Owner]) of
Access = open, % always allow subscriptions proplists:get_value(access_model, Options), {result, _} ->
Publish = open, % always allow publications proplists:get_value(publish_model, Options), Access = open, % always allow subscriptions proplists:get_value(access_model, Options),
MaxItems = proplists:get_value(max_items, Options), Publish = open, % always allow publications proplists:get_value(publish_model, Options),
Affiliations = proplists:get_value(affiliations, NodeData), MaxItems = proplists:get_value(max_items, Options),
Subscriptions = proplists:get_value(subscriptions, NodeData), Affiliations = proplists:get_value(affiliations, NodeData),
Items = proplists:get_value(items, NodeData), Subscriptions = proplists:get_value(subscriptions, NodeData),
[mod_pubsub:node_action(Host, Type, set_affiliation, Items = proplists:get_value(items, NodeData),
[Nidx, Entity, Aff]) [mod_pubsub:node_action(Host, Type, set_affiliation,
|| {Entity, Aff} <- Affiliations, Entity =/= Owner], [Nidx, Entity, Aff])
[mod_pubsub:node_action(Host, Type, subscribe_node, || {Entity, Aff} <- Affiliations, Entity =/= Owner],
[Nidx, jid:make(Entity), Entity, Access, never, [], [], []]) [mod_pubsub:node_action(Host, Type, subscribe_node,
|| Entity <- Subscriptions], [Nidx, jid:make(Entity), Entity, Access, never, [], [], []])
[mod_pubsub:node_action(Host, Type, publish_item, || Entity <- Subscriptions],
[Nidx, Publisher, Publish, MaxItems, ItemId, Payload, []]) [mod_pubsub:node_action(Host, Type, publish_item,
|| {ItemId, Publisher, Payload} <- Items]; [Nidx, Publisher, Publish, MaxItems, ItemId, Payload, []])
|| {ItemId, Publisher, Payload} <- Items];
Error ->
Error
end;
Error -> Error ->
?ERROR_MSG("failed to import pubsub node ~s on ~p:~n~p",
[Node, Host, NodeData]),
Error Error
end; end;
Error -> Error ->
?ERROR_MSG("failed to import pubsub node ~s on host ~s:~n~p", ?ERROR_MSG("failed to import pubsub node: ~p", [Error]),
[Node, Host, NodeData]),
Error Error
end; end;
convert_data(_Host, _Type, _User, _Data) -> convert_data(_Host, _Type, _User, _Data) ->
@ -383,10 +400,15 @@ url_decode(<<H, Tail/binary>>, Acc) ->
url_decode(<<>>, Acc) -> url_decode(<<>>, Acc) ->
Acc. Acc.
node_type(_Host, <<"urn:", _Tail/binary>>) -> <<"pep">>; decode_pubsub_host(Host) ->
node_type(_Host, <<"http:", _Tail/binary>>) -> <<"pep">>; try jid:decode(Host) of
node_type(_Host, <<"https:", _Tail/binary>>) -> <<"pep">>; #jid{luser = <<>>, lserver = LServer} -> LServer;
node_type(Host, _) -> hd(mod_pubsub:plugins(Host)). #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) -> max_items(Config, Default) ->
case round(proplists:get_value(<<"max_items">>, Config, Default)) of 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, []), Authors = proplists:get_value(<<"data_author">>, Data, []),
lists:flatmap( lists:flatmap(
fun({ItemId, Item}) -> 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 -> JID ->
[El] = deserialize(Item), [El] = deserialize(Item),
[{ItemId, JID, El#xmlel.children}] [{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(T, El#xmlel{children = [{xmlcdata, S}|Els]}, Acc);
deserialize([{_, L}|T], #xmlel{children = Els} = El, Acc) when is_list(L) -> deserialize([{_, L}|T], #xmlel{children = Els} = El, Acc) when is_list(L) ->
deserialize(T, El#xmlel{children = deserialize(L) ++ Els}, Acc); deserialize(T, El#xmlel{children = deserialize(L) ++ Els}, Acc);
deserialize([], El, Acc) -> deserialize([], #xmlel{children = Els} = El, Acc) ->
[El|Acc]. [El#xmlel{children = lists:reverse(Els)}|Acc].

View File

@ -25,12 +25,16 @@
-module(pubsub_db_sql). -module(pubsub_db_sql).
-compile([{parse_transform, ejabberd_sql_pt}]).
-author("pablo.polvorin@process-one.net"). -author("pablo.polvorin@process-one.net").
-include("pubsub.hrl"). -include("pubsub.hrl").
-include("ejabberd_sql_pt.hrl").
-export([add_subscription/1, read_subscription/1, -export([add_subscription/1, read_subscription/1,
delete_subscription/1, update_subscription/1]). delete_subscription/1, update_subscription/1]).
-export([export/1]).
%% TODO: Those -spec lines produce errors in old Erlang versions. %% 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. %% 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_boolean(B) -> B == <<"1">>.
sql_to_timestamp(T) -> xmpp_util:decode_timestamp(T). 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],":").

View File

@ -32,6 +32,20 @@
-define(THRESHOLD, 16#10000000000000000). -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() -> get_string() ->
R = crypto:rand_uniform(0, ?THRESHOLD), R = crypto:rand_uniform(0, ?THRESHOLD),
integer_to_binary(R). integer_to_binary(R).
@ -44,6 +58,7 @@ uniform(N) ->
uniform(N, M) -> uniform(N, M) ->
crypto:rand_uniform(N, M+1). crypto:rand_uniform(N, M+1).
-endif.
-ifdef(STRONG_RAND_BYTES). -ifdef(STRONG_RAND_BYTES).
bytes(N) -> bytes(N) ->

View File

@ -431,6 +431,7 @@ db_tests(DB) when DB == mnesia; DB == redis ->
mam_tests:single_cases(), mam_tests:single_cases(),
carbons_tests:single_cases(), carbons_tests:single_cases(),
csi_tests:single_cases(), csi_tests:single_cases(),
push_tests:single_cases(),
test_unregister]}, test_unregister]},
muc_tests:master_slave_cases(), muc_tests:master_slave_cases(),
privacy_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(), vcard_tests:master_slave_cases(),
announce_tests:master_slave_cases(), announce_tests:master_slave_cases(),
carbons_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(_) -> db_tests(_) ->
[{single_user, [sequence], [{single_user, [sequence],
[test_register, [test_register,
@ -748,25 +750,25 @@ test_component_send(Config) ->
disconnect(Config). disconnect(Config).
s2s_dialback(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(s2s_use_starttls, false),
ejabberd_config:add_option(domain_certfile, "self-signed-cert.pem"), ejabberd_config:add_option(domain_certfile, "self-signed-cert.pem"),
s2s_ping(Config). s2s_ping(Config).
s2s_optional(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(s2s_use_starttls, optional),
ejabberd_config:add_option(domain_certfile, "self-signed-cert.pem"), ejabberd_config:add_option(domain_certfile, "self-signed-cert.pem"),
s2s_ping(Config). s2s_ping(Config).
s2s_required(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(s2s_use_starttls, required),
ejabberd_config:add_option(domain_certfile, "self-signed-cert.pem"), ejabberd_config:add_option(domain_certfile, "self-signed-cert.pem"),
s2s_ping(Config). s2s_ping(Config).
s2s_required_trusted(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(s2s_use_starttls, required),
ejabberd_config:add_option(domain_certfile, "cert.pem"), ejabberd_config:add_option(domain_certfile, "cert.pem"),
s2s_ping(Config). s2s_ping(Config).

View File

@ -231,7 +231,11 @@ Welcome to this XMPP server."
mod_disco: [] mod_disco: []
mod_ping: [] mod_ping: []
mod_proxy65: [] mod_proxy65: []
mod_push: []
mod_push_keepalive: []
mod_s2s_dialback: [] mod_s2s_dialback: []
mod_stream_mgmt:
resume_timeout: 3
mod_legacy_auth: [] mod_legacy_auth: []
mod_register: mod_register:
welcome_message: welcome_message:
@ -290,7 +294,11 @@ Welcome to this XMPP server."
mod_disco: [] mod_disco: []
mod_ping: [] mod_ping: []
mod_proxy65: [] mod_proxy65: []
mod_push: []
mod_push_keepalive: []
mod_s2s_dialback: [] mod_s2s_dialback: []
mod_stream_mgmt:
resume_timeout: 3
mod_legacy_auth: [] mod_legacy_auth: []
mod_register: mod_register:
welcome_message: welcome_message:
@ -450,6 +458,8 @@ listen:
port: @@web_port@@ port: @@web_port@@
module: ejabberd_http module: ejabberd_http
captcha: true captcha: true
request_handlers:
"/api": mod_http_api
- -
port: @@component_port@@ port: @@component_port@@
module: ejabberd_service module: ejabberd_service
@ -466,6 +476,7 @@ modules:
mod_proxy65: [] mod_proxy65: []
mod_legacy: [] mod_legacy: []
mod_muc: [] mod_muc: []
mod_muc_admin: []
mod_register: mod_register:
welcome_message: welcome_message:
subject: "Welcome!" subject: "Welcome!"
@ -488,3 +499,8 @@ outgoing_s2s_port: @@s2s_port@@
shaper: shaper:
fast: 50000 fast: 50000
normal: 10000 normal: 10000
api_permissions:
"public commands":
who: all
what: "*"

View File

@ -3,23 +3,27 @@ import struct
def read(): def read():
(pkt_size,) = struct.unpack('>H', sys.stdin.read(2)) (pkt_size,) = struct.unpack('>H', sys.stdin.read(2))
pkt = sys.stdin.read(pkt_size).split(':') pkt = sys.stdin.read(pkt_size)
cmd = pkt[0] cmd = pkt.split(':')[0]
args_num = len(pkt) - 1 if cmd == 'auth':
if cmd == 'auth' and args_num >= 3: u, s, p = pkt.split(':', 3)[1:]
if pkt[1] == "wrong": if u == "wrong":
write(False) write(False)
else: else:
write(True) 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) write(True)
elif cmd == 'setpass' and args_num >= 3: elif cmd == 'tryregister':
u, s, p = pkt.split(':', 3)[1:]
write(True) write(True)
elif cmd == 'tryregister' and args_num >= 3: elif cmd == 'removeuser':
u, s = pkt.split(':', 2)[1:]
write(True) write(True)
elif cmd == 'removeuser' and args_num == 2: elif cmd == 'removeuser3':
write(True) u, s, p = pkt.split(':', 3)[1:]
elif cmd == 'removeuser3' and args_num >= 3:
write(True) write(True)
else: else:
write(False) write(False)

View File

@ -53,7 +53,8 @@ single_cases() ->
single_test(service_vcard), single_test(service_vcard),
single_test(configure_non_existent), single_test(configure_non_existent),
single_test(cancel_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_presence_error(Config) ->
Service = muc_jid(Config), Service = muc_jid(Config),
@ -242,6 +243,32 @@ service_subscriptions(Config) ->
end, Rooms), end, Rooms),
disconnect(Config). 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 %%% Master-slave tests
%%%=================================================================== %%%===================================================================

234
test/push_tests.erl Normal file
View File

@ -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.