From c69720a1abba84ca9e46d4f1113dc166d447b2ac Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Wed, 5 Jul 2017 16:18:31 +0200 Subject: [PATCH 01/64] Add integration test for set_room_affiliation --- test/ejabberd_SUITE_data/ejabberd.yml | 8 ++++++++ test/muc_tests.erl | 29 ++++++++++++++++++++++++++- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/test/ejabberd_SUITE_data/ejabberd.yml b/test/ejabberd_SUITE_data/ejabberd.yml index 89618c0c0..131fc2edf 100644 --- a/test/ejabberd_SUITE_data/ejabberd.yml +++ b/test/ejabberd_SUITE_data/ejabberd.yml @@ -450,6 +450,8 @@ listen: port: @@web_port@@ module: ejabberd_http captcha: true + request_handlers: + "/api": mod_http_api - port: @@component_port@@ module: ejabberd_service @@ -466,6 +468,7 @@ modules: mod_proxy65: [] mod_legacy: [] mod_muc: [] + mod_muc_admin: [] mod_register: welcome_message: subject: "Welcome!" @@ -488,3 +491,8 @@ outgoing_s2s_port: @@s2s_port@@ shaper: fast: 50000 normal: 10000 + +api_permissions: + "public commands": + who: all + what: "*" diff --git a/test/muc_tests.erl b/test/muc_tests.erl index 9ded2e0fe..bcceb6938 100644 --- a/test/muc_tests.erl +++ b/test/muc_tests.erl @@ -53,7 +53,8 @@ single_cases() -> single_test(service_vcard), single_test(configure_non_existent), single_test(cancel_configure_non_existent), - single_test(service_subscriptions)]}. + single_test(service_subscriptions), + single_test(set_room_affiliation)]}. service_presence_error(Config) -> Service = muc_jid(Config), @@ -242,6 +243,32 @@ service_subscriptions(Config) -> end, Rooms), disconnect(Config). +set_room_affiliation(Config) -> + #jid{server = RoomService} = muc_jid(Config), + RoomName = <<"set_room_affiliation">>, + RoomJID = jid:make(RoomName, RoomService), + MyJID = my_jid(Config), + PeerJID = jid:remove_resource(?config(slave, Config)), + + ct:pal("joining room ~p", [RoomJID]), + ok = join_new(Config, RoomJID), + + ct:pal("setting affiliation in room ~p to 'member' for ~p", [RoomJID, PeerJID]), + ServerHost = ?config(server_host, Config), + WebPort = ct:get_config(web_port, 5280), + RequestURL = "http://" ++ ServerHost ++ ":" ++ integer_to_list(WebPort) ++ "/api/set_room_affiliation", + Headers = [{"X-Admin", "true"}], + ContentType = "application/json", + Body = jiffy:encode(#{name => RoomName, service => RoomService, jid => jid:encode(PeerJID), affiliation => member}), + {ok, {{_, 200, _}, _, _}} = httpc:request(post, {RequestURL, Headers, ContentType, Body}, [], []), + + #message{id = _, from = RoomJID, to = MyJID, sub_els = [ + #muc_user{items = [ + #muc_item{affiliation = member, role = none, jid = PeerJID}]}]} = recv_message(Config), + + ok = leave(Config, RoomJID), + disconnect(Config). + %%%=================================================================== %%% Master-slave tests %%%=================================================================== From d6f1d3df5b5a75f618bcc6eeb6425bc47dfd84d2 Mon Sep 17 00:00:00 2001 From: Holger Weiss Date: Thu, 20 Jul 2017 20:22:50 +0200 Subject: [PATCH 02/64] Support XEP-0357: Push Notifications Closes #1379. --- ejabberd.yml.example | 1 + src/ejabberd_c2s.erl | 6 +- src/ejabberd_sm.erl | 20 +- src/misc.erl | 16 +- src/mod_mam.erl | 19 +- src/mod_push.erl | 590 ++++++++++++++++++++++++++ src/mod_push_mnesia.erl | 204 +++++++++ test/ejabberd_SUITE.erl | 4 +- test/ejabberd_SUITE_data/ejabberd.yml | 6 + test/push_tests.erl | 232 ++++++++++ 10 files changed, 1079 insertions(+), 19 deletions(-) create mode 100644 src/mod_push.erl create mode 100644 src/mod_push_mnesia.erl create mode 100644 test/push_tests.erl diff --git a/ejabberd.yml.example b/ejabberd.yml.example index 3f0e8d1c6..693a87f57 100644 --- a/ejabberd.yml.example +++ b/ejabberd.yml.example @@ -725,6 +725,7 @@ modules: - "flat" - "hometree" - "pep" # pep requires mod_caps + mod_push: {} ## mod_register: ## ## Protect In-Band account registrations with CAPTCHA. diff --git a/src/ejabberd_c2s.erl b/src/ejabberd_c2s.erl index 159cb4054..4b265d29d 100644 --- a/src/ejabberd_c2s.erl +++ b/src/ejabberd_c2s.erl @@ -47,7 +47,7 @@ process_terminated/2, process_info/2]). %% API -export([get_presence/1, set_presence/2, resend_presence/1, resend_presence/2, - open_session/1, call/3, send/2, close/1, close/2, stop/1, + open_session/1, call/3, cast/2, send/2, close/1, close/2, stop/1, reply/2, copy_state/2, set_timeout/2, route/2, host_up/1, host_down/1]). @@ -90,6 +90,10 @@ socket_type() -> call(Ref, Msg, Timeout) -> xmpp_stream_in:call(Ref, Msg, Timeout). +-spec cast(pid(), term()) -> ok. +cast(Ref, Msg) -> + xmpp_stream_in:cast(Ref, Msg). + reply(Ref, Reply) -> xmpp_stream_in:reply(Ref, Reply). diff --git a/src/ejabberd_sm.erl b/src/ejabberd_sm.erl index 344febb5d..302cfded4 100644 --- a/src/ejabberd_sm.erl +++ b/src/ejabberd_sm.erl @@ -66,6 +66,8 @@ user_resources/2, kick_user/2, get_session_pid/3, + get_session_sid/3, + get_session_sids/2, get_user_info/2, get_user_info/3, get_user_ip/3, @@ -292,15 +294,31 @@ close_session_unset_presence(SID, User, Server, -spec get_session_pid(binary(), binary(), binary()) -> none | pid(). get_session_pid(User, Server, Resource) -> + case get_session_sid(User, Server, Resource) of + {_, PID} -> PID; + none -> none + end. + +-spec get_session_sid(binary(), binary(), binary()) -> none | sid(). + +get_session_sid(User, Server, Resource) -> LUser = jid:nodeprep(User), LServer = jid:nameprep(Server), LResource = jid:resourceprep(Resource), Mod = get_sm_backend(LServer), case online(get_sessions(Mod, LUser, LServer, LResource)) of - [#session{sid = {_, Pid}}] -> Pid; + [#session{sid = SID}] -> SID; _ -> none end. +-spec get_session_sids(binary(), binary()) -> [sid()]. + +get_session_sids(User, Server) -> + LUser = jid:nodeprep(User), + LServer = jid:nameprep(Server), + Mod = get_sm_backend(LServer), + online(get_sessions(Mod, LUser, LServer)). + -spec set_offline_info(sid(), binary(), binary(), binary(), info()) -> ok. set_offline_info(SID, User, Server, Resource, Info) -> diff --git a/src/misc.erl b/src/misc.erl index 604a458af..2112cd90c 100644 --- a/src/misc.erl +++ b/src/misc.erl @@ -32,8 +32,8 @@ hex_to_bin/1, hex_to_base64/1, expand_keyword/3, atom_to_binary/1, binary_to_atom/1, tuple_to_binary/1, l2i/1, i2l/1, i2l/2, expr_to_term/1, term_to_expr/1, - encode_pid/1, decode_pid/2, compile_exprs/2, join_atoms/2, - try_read_file/1]). + now_to_usec/1, usec_to_now/1, encode_pid/1, decode_pid/2, + compile_exprs/2, join_atoms/2, try_read_file/1]). %% Deprecated functions -export([decode_base64/1, encode_base64/1]). @@ -127,6 +127,18 @@ expr_to_term(Expr) -> term_to_expr(Term) -> list_to_binary(io_lib:print(Term)). +-spec now_to_usec(erlang:timestamp()) -> non_neg_integer(). +now_to_usec({MSec, Sec, USec}) -> + (MSec*1000000 + Sec)*1000000 + USec. + +-spec usec_to_now(non_neg_integer()) -> erlang:timestamp(). +usec_to_now(Int) -> + Secs = Int div 1000000, + USec = Int rem 1000000, + MSec = Secs div 1000000, + Sec = Secs rem 1000000, + {MSec, Sec, USec}. + l2i(I) when is_integer(I) -> I; l2i(L) when is_binary(L) -> binary_to_integer(L). diff --git a/src/mod_mam.erl b/src/mod_mam.erl index eb2082fe2..30dd7dd57 100644 --- a/src/mod_mam.erl +++ b/src/mod_mam.erl @@ -434,8 +434,9 @@ message_is_archived(false, #{jid := JID}, Pkt) -> delete_old_messages(TypeBin, Days) when TypeBin == <<"chat">>; TypeBin == <<"groupchat">>; TypeBin == <<"all">> -> + CurrentTime = p1_time_compat:system_time(micro_seconds), Diff = Days * 24 * 60 * 60 * 1000000, - TimeStamp = usec_to_now(p1_time_compat:system_time(micro_seconds) - Diff), + TimeStamp = misc:usec_to_now(CurrentTime - Diff), Type = misc:binary_to_atom(TypeBin), DBTypes = lists:usort( lists:map( @@ -830,7 +831,7 @@ select(_LServer, JidRequestor, JidArchive, Query, RSM, Msgs = lists:flatmap( fun({Nick, Pkt, _HaveSubject, Now, _Size}) -> - TS = now_to_usec(Now), + TS = misc:now_to_usec(Now), case match_interval(Now, Start, End) and match_rsm(Now, RSM) of true -> @@ -979,24 +980,14 @@ match_interval(Now, Start, End) -> (Now >= Start) and (Now =< End). match_rsm(Now, #rsm_set{'after' = ID}) when is_binary(ID), ID /= <<"">> -> - Now1 = (catch usec_to_now(binary_to_integer(ID))), + Now1 = (catch misc:usec_to_now(binary_to_integer(ID))), Now > Now1; match_rsm(Now, #rsm_set{before = ID}) when is_binary(ID), ID /= <<"">> -> - Now1 = (catch usec_to_now(binary_to_integer(ID))), + Now1 = (catch misc:usec_to_now(binary_to_integer(ID))), Now < Now1; match_rsm(_Now, _) -> true. -now_to_usec({MSec, Sec, USec}) -> - (MSec*1000000 + Sec)*1000000 + USec. - -usec_to_now(Int) -> - Secs = Int div 1000000, - USec = Int rem 1000000, - MSec = Secs div 1000000, - Sec = Secs rem 1000000, - {MSec, Sec, USec}. - get_jids(undefined) -> []; get_jids(Js) -> diff --git a/src/mod_push.erl b/src/mod_push.erl new file mode 100644 index 000000000..f82226440 --- /dev/null +++ b/src/mod_push.erl @@ -0,0 +1,590 @@ +%%%---------------------------------------------------------------------- +%%% File : mod_push.erl +%%% Author : Holger Weiss +%%% Purpose : Push Notifications (XEP-0357) +%%% Created : 15 Jul 2017 by Holger Weiss +%%% +%%% +%%% 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]). + +-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). + +%%-------------------------------------------------------------------- +%% Internal functions. +%%-------------------------------------------------------------------- +-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. + +-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, + not lists:keyfind(TS, 1, SessIDs)]. + +%%-------------------------------------------------------------------- +%% Caching. +%%-------------------------------------------------------------------- +-spec init_cache(module(), binary(), gen_mod:opts()) -> ok. +init_cache(Mod, Host, Opts) -> + case use_cache(Mod, Host) of + true -> + CacheOpts = cache_opts(Host, Opts), + ets_cache:new(?PUSH_CACHE, CacheOpts); + false -> + ets_cache:delete(?PUSH_CACHE) + end. + +-spec cache_opts(binary(), gen_mod:opts()) -> [proplists:property()]. +cache_opts(Host, Opts) -> + MaxSize = gen_mod:get_opt( + cache_size, Opts, + ejabberd_config:cache_size(Host)), + CacheMissed = gen_mod:get_opt( + cache_missed, Opts, + ejabberd_config:cache_missed(Host)), + LifeTime = case gen_mod:get_opt( + cache_life_time, Opts, + ejabberd_config:cache_life_time(Host)) of + infinity -> infinity; + I -> timer:seconds(I) + end, + [{max_size, MaxSize}, {cache_missed, CacheMissed}, {life_time, LifeTime}]. + +-spec use_cache(module(), binary()) -> boolean(). +use_cache(Mod, Host) -> + case erlang:function_exported(Mod, use_cache, 1) of + true -> Mod:use_cache(Host); + false -> + gen_mod:get_module_opt( + Host, ?MODULE, use_cache, + ejabberd_config:use_cache(Host)) + end. + +-spec cache_nodes(module(), binary()) -> [node()]. +cache_nodes(Mod, Host) -> + case erlang:function_exported(Mod, cache_nodes, 1) of + true -> Mod:cache_nodes(Host); + false -> ejabberd_cluster:get_nodes() + end. diff --git a/src/mod_push_mnesia.erl b/src/mod_push_mnesia.erl new file mode 100644 index 000000000..04ea8d60a --- /dev/null +++ b/src/mod_push_mnesia.erl @@ -0,0 +1,204 @@ +%%%---------------------------------------------------------------------- +%%% File : mod_push_mnesia.erl +%%% Author : Holger Weiss +%%% Purpose : Mnesia backend for Push Notifications (XEP-0357) +%%% Created : 15 Jul 2017 by Holger Weiss +%%% +%%% +%%% ejabberd, Copyright (C) 2017 ProcessOne +%%% +%%% This program is free software; you can redistribute it and/or +%%% modify it under the terms of the GNU General Public License as +%%% published by the Free Software Foundation; either version 2 of the +%%% License, or (at your option) any later version. +%%% +%%% This program is distributed in the hope that it will be useful, +%%% but WITHOUT ANY WARRANTY; without even the implied warranty of +%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%%% General Public License for more details. +%%% +%%% You should have received a copy of the GNU General Public License along +%%% with this program; if not, write to the Free Software Foundation, Inc., +%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +%%% +%%%---------------------------------------------------------------------- + +-module(mod_push_mnesia). +-author('holger@zedat.fu-berlin.de'). + +-behavior(mod_push). + +%% API +-export([init/2, store_session/6, lookup_session/4, lookup_session/3, + lookup_sessions/3, lookup_sessions/2, lookup_sessions/1, + delete_session/3, delete_old_sessions/2]). + +-include_lib("stdlib/include/ms_transform.hrl"). +-include("logger.hrl"). +-include("xmpp.hrl"). + +-record(push_session, + {us = {<<"">>, <<"">>} :: {binary(), binary()}, + timestamp = p1_time_compat:timestamp() :: erlang:timestamp(), + service = {<<"">>, <<"">>, <<"">>} :: ljid(), + node = <<"">> :: binary(), + xdata = #xdata{} :: xdata()}). + +%%%------------------------------------------------------------------- +%%% API +%%%------------------------------------------------------------------- +init(_Host, _Opts) -> + ejabberd_mnesia:create(?MODULE, push_session, + [{disc_only_copies, [node()]}, + {type, bag}, + {attributes, record_info(fields, push_session)}]). + +store_session(LUser, LServer, TS, PushJID, Node, XData) -> + US = {LUser, LServer}, + PushLJID = jid:tolower(PushJID), + MaxSessions = ejabberd_sm:get_max_user_sessions(LUser, LServer), + F = fun() -> + if is_integer(MaxSessions) -> + enforce_max_sessions(US, MaxSessions - 1); + MaxSessions == infinity -> + ok + end, + mnesia:write(#push_session{us = US, + timestamp = TS, + service = PushLJID, + node = Node, + xdata = XData}) + end, + case mnesia:transaction(F) of + {atomic, ok} -> + {ok, {TS, PushLJID, Node, XData}}; + {aborted, E} -> + ?ERROR_MSG("Cannot store push session for ~s@~s: ~p", + [LUser, LServer, E]), + error + end. + +lookup_session(LUser, LServer, PushJID, Node) -> + PushLJID = jid:tolower(PushJID), + MatchSpec = ets:fun2ms( + fun(#push_session{us = {U, S}, service = P, node = N} = Rec) + when U == LUser, + S == LServer, + P == PushLJID, + N == Node -> + Rec + end), + case mnesia:dirty_select(push_session, MatchSpec) of + [#push_session{timestamp = TS, xdata = XData}] -> + {ok, {TS, PushLJID, Node, XData}}; + _ -> + ?DEBUG("No push session found for ~s@~s (~p, ~s)", + [LUser, LServer, PushJID, Node]), + error + end. + +lookup_session(LUser, LServer, TS) -> + MatchSpec = ets:fun2ms( + fun(#push_session{us = {U, S}, timestamp = T} = Rec) + when U == LUser, + S == LServer, + T == TS -> + Rec + end), + case mnesia:dirty_select(push_session, MatchSpec) of + [#push_session{service = PushLJID, node = Node, xdata = XData}] -> + {ok, {TS, PushLJID, Node, XData}}; + _ -> + ?DEBUG("No push session found for ~s@~s (~p)", + [LUser, LServer, TS]), + error + end. + +lookup_sessions(LUser, LServer, PushJID) -> + PushLJID = jid:tolower(PushJID), + MatchSpec = ets:fun2ms( + fun(#push_session{us = {U, S}, service = P, node = N} = Rec) + when U == LUser, + S == LServer, + P == PushLJID -> + Rec + end), + {ok, mnesia:dirty_select(push_session, MatchSpec)}. + +lookup_sessions(LUser, LServer) -> + Records = mnesia:dirty_read(push_session, {LUser, LServer}), + Clients = [{TS, PushLJID, Node, XData} + || #push_session{timestamp = TS, + service = PushLJID, + node = Node, + xdata = XData} <- Records], + {ok, Clients}. + +lookup_sessions(LServer) -> + MatchSpec = ets:fun2ms( + fun(#push_session{us = {_U, S}, + timestamp = TS, + service = PushLJID, + node = Node, + xdata = XData}) + when S == LServer -> + {TS, PushLJID, Node, XData} + end), + {ok, mnesia:dirty_select(push_session, MatchSpec)}. + +delete_session(LUser, LServer, TS) -> + MatchSpec = ets:fun2ms( + fun(#push_session{us = {U, S}, timestamp = T} = Rec) + when U == LUser, + S == LServer, + T == TS -> + Rec + end), + F = fun() -> + Recs = mnesia:select(push_session, MatchSpec), + lists:foreach(fun mnesia:delete_object/1, Recs) + end, + case mnesia:transaction(F) of + {atomic, ok} -> + ok; + {aborted, E} -> + ?ERROR_MSG("Cannot delete push seesion of ~s@~s: ~p", + [LUser, LServer, E]), + error + end. + +delete_old_sessions(_LServer, Time) -> + DelIfOld = fun(#push_session{timestamp = T} = Rec, ok) when T < Time -> + mnesia:delete_object(Rec); + (_Rec, ok) -> + ok + end, + F = fun() -> + mnesia:foldl(DelIfOld, ok, push_session) + end, + case mnesia:transaction(F) of + {atomic, ok} -> + ok; + {aborted, E} -> + ?ERROR_MSG("Cannot delete old push sessions: ~p", [E]), + error + end. + +%%-------------------------------------------------------------------- +%% Internal functions. +%%-------------------------------------------------------------------- +-spec enforce_max_sessions({binary(), binary()}, non_neg_integer()) -> ok. +enforce_max_sessions({U, S} = US, Max) -> + Recs = mnesia:wread({push_session, US}), + NumRecs = length(Recs), + if NumRecs > Max -> + NumOldRecs = NumRecs - Max, + Recs1 = lists:keysort(#push_session.timestamp, Recs), + Recs2 = lists:reverse(Recs1), + OldRecs = lists:sublist(Recs2, Max + 1, NumOldRecs), + ?INFO_MSG("Disabling ~B old push session(s) of ~s@~s", + [NumOldRecs, U, S]), + lists:foreach(fun(Rec) -> mnesia:delete_object(Rec) end, OldRecs); + true -> + ok + end. diff --git a/test/ejabberd_SUITE.erl b/test/ejabberd_SUITE.erl index 17465617b..539d8dc33 100644 --- a/test/ejabberd_SUITE.erl +++ b/test/ejabberd_SUITE.erl @@ -431,6 +431,7 @@ db_tests(DB) when DB == mnesia; DB == redis -> mam_tests:single_cases(), carbons_tests:single_cases(), csi_tests:single_cases(), + push_tests:single_cases(), test_unregister]}, muc_tests:master_slave_cases(), privacy_tests:master_slave_cases(), @@ -441,7 +442,8 @@ db_tests(DB) when DB == mnesia; DB == redis -> vcard_tests:master_slave_cases(), announce_tests:master_slave_cases(), carbons_tests:master_slave_cases(), - csi_tests:master_slave_cases()]; + csi_tests:master_slave_cases(), + push_tests:master_slave_cases()]; db_tests(_) -> [{single_user, [sequence], [test_register, diff --git a/test/ejabberd_SUITE_data/ejabberd.yml b/test/ejabberd_SUITE_data/ejabberd.yml index 89618c0c0..5ee287400 100644 --- a/test/ejabberd_SUITE_data/ejabberd.yml +++ b/test/ejabberd_SUITE_data/ejabberd.yml @@ -231,7 +231,10 @@ Welcome to this XMPP server." mod_disco: [] mod_ping: [] mod_proxy65: [] + mod_push: [] mod_s2s_dialback: [] + mod_stream_mgmt: + resume_timeout: 3 mod_legacy_auth: [] mod_register: welcome_message: @@ -290,7 +293,10 @@ Welcome to this XMPP server." mod_disco: [] mod_ping: [] mod_proxy65: [] + mod_push: [] mod_s2s_dialback: [] + mod_stream_mgmt: + resume_timeout: 3 mod_legacy_auth: [] mod_register: welcome_message: diff --git a/test/push_tests.erl b/test/push_tests.erl new file mode 100644 index 000000000..535671ee1 --- /dev/null +++ b/test/push_tests.erl @@ -0,0 +1,232 @@ +%%%------------------------------------------------------------------- +%%% Author : Holger Weiss +%%% Created : 15 Jul 2017 by Holger Weiss +%%% +%%% +%%% 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("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), + ct:comment("Re-enabling push notifications"), + ok = enable_push(Config), + 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. From 66510c1d787e3696ac8d04cde75148c9b162b905 Mon Sep 17 00:00:00 2001 From: Holger Weiss Date: Fri, 21 Jul 2017 01:07:36 +0200 Subject: [PATCH 03/64] Add mod_push_keepalive This module tries to keep pending stream management sessions of push clients alive (as long as the disconnected clients are reachable via push notifications). --- ejabberd.yml.example | 1 + src/mod_push.erl | 8 +- src/mod_push_keepalive.erl | 236 ++++++++++++++++++++++++++ src/mod_stream_mgmt.erl | 54 ++++-- test/ejabberd_SUITE_data/ejabberd.yml | 2 + test/push_tests.erl | 2 + 6 files changed, 290 insertions(+), 13 deletions(-) create mode 100644 src/mod_push_keepalive.erl diff --git a/ejabberd.yml.example b/ejabberd.yml.example index 693a87f57..922400d2d 100644 --- a/ejabberd.yml.example +++ b/ejabberd.yml.example @@ -726,6 +726,7 @@ modules: - "hometree" - "pep" # pep requires mod_caps mod_push: {} + mod_push_keepalive: {} ## mod_register: ## ## Protect In-Band account registrations with CAPTCHA. diff --git a/src/mod_push.erl b/src/mod_push.erl index f82226440..55db7c0e9 100644 --- a/src/mod_push.erl +++ b/src/mod_push.erl @@ -43,6 +43,9 @@ %% 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"). @@ -393,7 +396,7 @@ remove_user(LUser, LServer) -> delete_sessions(LUser, LServer, LookupFun, Mod). %%-------------------------------------------------------------------- -%% Internal functions. +%% Generate push notifications. %%-------------------------------------------------------------------- -spec notify(c2s_state()) -> ok. notify(#{jid := #jid{luser = LUser, lserver = LServer}, sid := {TS, _}}) -> @@ -433,6 +436,9 @@ notify(LServer, PushLJID, Node, XData, HandleResponse) -> 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) -> diff --git a/src/mod_push_keepalive.erl b/src/mod_push_keepalive.erl new file mode 100644 index 000000000..bde62fc67 --- /dev/null +++ b/src/mod_push_keepalive.erl @@ -0,0 +1,236 @@ +%%%---------------------------------------------------------------------- +%%% File : mod_push_keepalive.erl +%%% Author : Holger Weiss +%%% Purpose : Keep pending XEP-0198 sessions alive with XEP-0357 +%%% Created : 15 Jul 2017 by Holger Weiss +%%% +%%% +%%% ejabberd, Copyright (C) 2017 ProcessOne +%%% +%%% This program is free software; you can redistribute it and/or +%%% modify it under the terms of the GNU General Public License as +%%% published by the Free Software Foundation; either version 2 of the +%%% License, or (at your option) any later version. +%%% +%%% This program is distributed in the hope that it will be useful, +%%% but WITHOUT ANY WARRANTY; without even the implied warranty of +%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%%% General Public License for more details. +%%% +%%% You should have received a copy of the GNU General Public License along +%%% with this program; if not, write to the Free Software Foundation, Inc., +%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +%%% +%%%---------------------------------------------------------------------- + +-module(mod_push_keepalive). +-author('holger@zedat.fu-berlin.de'). + +-behavior(gen_mod). + +%% gen_mod callbacks. +-export([start/2, stop/1, reload/3, mod_opt_type/1, depends/2]). + +%% ejabberd_hooks callbacks. +-export([c2s_session_pending/1, c2s_session_resumed/1, c2s_copy_session/2, + c2s_handle_cast/2, c2s_handle_info/2, c2s_stanza/3]). + +-include("logger.hrl"). +-include("xmpp.hrl"). + +-define(PUSH_BEFORE_TIMEOUT_SECS, 120). + +-type c2s_state() :: ejabberd_c2s:state(). + +%%-------------------------------------------------------------------- +%% gen_mod callbacks. +%%-------------------------------------------------------------------- +-spec start(binary(), gen_mod:opts()) -> ok. +start(Host, Opts) -> + case gen_mod:get_opt(wake_on_start, Opts, false) of + true -> + wake_all(Host); + false -> + ok + end, + register_hooks(Host). + +-spec stop(binary()) -> ok. +stop(Host) -> + unregister_hooks(Host). + +-spec reload(binary(), gen_mod:opts(), gen_mod:opts()) -> ok. +reload(Host, NewOpts, OldOpts) -> + case gen_mod:is_equal_opt(wake_on_start, NewOpts, OldOpts, false) of + {false, true, _} -> + wake_all(Host); + _ -> + ok + end, + ok. + +-spec depends(binary(), gen_mod:opts()) -> [{module(), hard | soft}]. +depends(_Host, _Opts) -> + [{mod_push, hard}, + {mod_client_state, soft}, + {mod_stream_mgmt, soft}]. + +-spec mod_opt_type(atom()) -> fun((term()) -> term()) | [atom()]. +mod_opt_type(resume_timeout) -> + fun(I) when is_integer(I), I >= 0 -> I; + (undefined) -> undefined + end; +mod_opt_type(wake_on_start) -> + fun (B) when is_boolean(B) -> B end; +mod_opt_type(wake_on_timeout) -> + fun (B) when is_boolean(B) -> B end; +mod_opt_type(O) when O == cache_life_time; O == cache_size -> + fun(I) when is_integer(I), I > 0 -> I; + (infinity) -> infinity + end; +mod_opt_type(O) when O == use_cache; O == cache_missed -> + fun (B) when is_boolean(B) -> B end; +mod_opt_type(_) -> + [resume_timeout, wake_on_start, wake_on_timeout, db_type, cache_life_time, + cache_size, use_cache, cache_missed, iqdisc]. + +%%-------------------------------------------------------------------- +%% Register/unregister hooks. +%%-------------------------------------------------------------------- +-spec register_hooks(binary()) -> ok. +register_hooks(Host) -> + ejabberd_hooks:add(c2s_session_pending, Host, ?MODULE, + c2s_session_pending, 50), + ejabberd_hooks:add(c2s_session_resumed, Host, ?MODULE, + c2s_session_resumed, 50), + ejabberd_hooks:add(c2s_copy_session, Host, ?MODULE, + c2s_copy_session, 50), + ejabberd_hooks:add(c2s_handle_cast, Host, ?MODULE, + c2s_handle_cast, 40), + ejabberd_hooks:add(c2s_handle_info, Host, ?MODULE, + c2s_handle_info, 50), + ejabberd_hooks:add(c2s_handle_send, Host, ?MODULE, + c2s_stanza, 50). + +-spec unregister_hooks(binary()) -> ok. +unregister_hooks(Host) -> + ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE, + disco_sm_features, 50), + ejabberd_hooks:delete(c2s_session_pending, Host, ?MODULE, + c2s_session_pending, 50), + ejabberd_hooks:delete(c2s_session_resumed, Host, ?MODULE, + c2s_session_resumed, 50), + ejabberd_hooks:delete(c2s_copy_session, Host, ?MODULE, + c2s_copy_session, 50), + ejabberd_hooks:delete(c2s_handle_cast, Host, ?MODULE, + c2s_handle_cast, 40), + ejabberd_hooks:delete(c2s_handle_info, Host, ?MODULE, + c2s_handle_info, 50), + ejabberd_hooks:delete(c2s_handle_send, Host, ?MODULE, + c2s_stanza, 50). + +%%-------------------------------------------------------------------- +%% Hook callbacks. +%%-------------------------------------------------------------------- +-spec c2s_stanza(c2s_state(), xmpp_element() | xmlel(), term()) -> c2s_state(). +c2s_stanza(#{push_enabled := true, mgmt_state := pending} = State, + _Pkt, _SendResult) -> + maybe_restore_resume_timeout(State); +c2s_stanza(State, _Pkt, _SendResult) -> + State. + +-spec c2s_session_pending(c2s_state()) -> c2s_state(). +c2s_session_pending(#{push_enabled := true, mgmt_queue := Queue} = State) -> + case p1_queue:len(Queue) of + 0 -> + State1 = maybe_adjust_resume_timeout(State), + maybe_start_wakeup_timer(State1); + _ -> + State + end; +c2s_session_pending(State) -> + State. + +-spec c2s_session_resumed(c2s_state()) -> c2s_state(). +c2s_session_resumed(#{push_enabled := true} = State) -> + maybe_restore_resume_timeout(State); +c2s_session_resumed(State) -> + State. + +-spec c2s_copy_session(c2s_state(), c2s_state()) -> c2s_state(). +c2s_copy_session(State, #{push_enabled := true, + push_resume_timeout := ResumeTimeout, + push_wake_on_timeout := WakeOnTimeout}) -> + State#{push_resume_timeout => ResumeTimeout, + push_wake_on_timeout => WakeOnTimeout}; +c2s_copy_session(State, _) -> + State. + +-spec c2s_handle_cast(c2s_state(), any()) -> c2s_state(). +c2s_handle_cast(#{lserver := LServer} = State, push_enable) -> + ResumeTimeout = gen_mod:get_module_opt(LServer, ?MODULE, + resume_timeout, 86400), + WakeOnTimeout = gen_mod:get_module_opt(LServer, ?MODULE, + wake_on_timeout, true), + State#{push_resume_timeout => ResumeTimeout, + push_wake_on_timeout => WakeOnTimeout}; +c2s_handle_cast(State, push_disable) -> + State1 = maps:remove(push_resume_timeout, State), + maps:remove(push_wake_on_timeout, State1); +c2s_handle_cast(State, _Msg) -> + State. + +-spec c2s_handle_info(c2s_state(), any()) -> c2s_state() | {stop, c2s_state()}. +c2s_handle_info(#{push_enabled := true, mgmt_state := pending, + jid := JID} = State, {timeout, _, push_keepalive}) -> + ?INFO_MSG("Waking ~s before session times out", [jid:encode(JID)]), + mod_push:notify(State), + {stop, State}; +c2s_handle_info(State, _) -> + State. + +%%-------------------------------------------------------------------- +%% Internal functions. +%%-------------------------------------------------------------------- +-spec maybe_adjust_resume_timeout(c2s_state()) -> c2s_state(). +maybe_adjust_resume_timeout(#{push_resume_timeout := undefined} = State) -> + State; +maybe_adjust_resume_timeout(#{push_resume_timeout := Timeout} = State) -> + OrigTimeout = mod_stream_mgmt:get_resume_timeout(State), + ?DEBUG("Adjusting resume timeout to ~B seconds", [Timeout]), + State1 = mod_stream_mgmt:set_resume_timeout(State, Timeout), + State1#{push_resume_timeout_orig => OrigTimeout}. + +-spec maybe_restore_resume_timeout(c2s_state()) -> c2s_state(). +maybe_restore_resume_timeout(#{push_resume_timeout_orig := Timeout} = State) -> + ?DEBUG("Restoring resume timeout to ~B seconds", [Timeout]), + State1 = mod_stream_mgmt:set_resume_timeout(State, Timeout), + maps:remove(push_resume_timeout_orig, State1); +maybe_restore_resume_timeout(State) -> + State. + +-spec maybe_start_wakeup_timer(c2s_state()) -> c2s_state(). +maybe_start_wakeup_timer(#{push_wake_on_timeout := true, + push_resume_timeout := ResumeTimeout} = State) + when is_integer(ResumeTimeout), ResumeTimeout > ?PUSH_BEFORE_TIMEOUT_SECS -> + WakeTimeout = ResumeTimeout - ?PUSH_BEFORE_TIMEOUT_SECS, + ?DEBUG("Scheduling wake-up timer to fire in ~B seconds", [WakeTimeout]), + erlang:start_timer(timer:seconds(WakeTimeout), self(), push_keepalive), + State; +maybe_start_wakeup_timer(State) -> + State. + +-spec wake_all(binary()) -> ok | error. +wake_all(LServer) -> + ?INFO_MSG("Waking all push clients on ~s", [LServer]), + Mod = gen_mod:db_mod(LServer, mod_push), + case Mod:lookup_sessions(LServer) of + {ok, Sessions} -> + IgnoreResponse = fun(_) -> ok end, + lists:foreach(fun({_, PushLJID, Node, XData}) -> + mod_push:notify(LServer, PushLJID, Node, + XData, IgnoreResponse) + end, Sessions); + error -> + error + end. diff --git a/src/mod_stream_mgmt.erl b/src/mod_stream_mgmt.erl index 127eea3e8..068550906 100644 --- a/src/mod_stream_mgmt.erl +++ b/src/mod_stream_mgmt.erl @@ -33,6 +33,8 @@ c2s_unbinded_packet/2, c2s_closed/2, c2s_terminated/2, c2s_handle_send/3, c2s_handle_info/2, c2s_handle_call/3, c2s_handle_recv/3]). +%% adjust pending session timeout +-export([get_resume_timeout/1, set_resume_timeout/2]). -include("xmpp.hrl"). -include("logger.hrl"). @@ -235,8 +237,9 @@ c2s_handle_info(#{mgmt_ack_timer := TRef, jid := JID, mod := Mod} = State, [jid:encode(JID)]), State1 = Mod:close(State), {stop, transition_to_pending(State1)}; -c2s_handle_info(#{mgmt_state := pending, jid := JID, mod := Mod} = State, - {timeout, _, pending_timeout}) -> +c2s_handle_info(#{mgmt_state := pending, + mgmt_pending_timer := TRef, jid := JID, mod := Mod} = State, + {timeout, TRef, pending_timeout}) -> ?DEBUG("Timed out waiting for resumption of stream for ~s", [jid:encode(JID)]), Mod:stop(State#{mgmt_state => timeout}); @@ -282,6 +285,20 @@ c2s_terminated(#{mgmt_state := MgmtState, mgmt_stanzas_in := In, sid := SID, c2s_terminated(State, _Reason) -> State. +%%%=================================================================== +%%% Adjust pending session timeout +%%%=================================================================== +-spec get_resume_timeout(state()) -> non_neg_integer(). +get_resume_timeout(#{mgmt_timeout := Timeout}) -> + Timeout. + +-spec set_resume_timeout(state(), non_neg_integer()) -> state(). +set_resume_timeout(#{mgmt_timeout := Timeout} = State, Timeout) -> + State; +set_resume_timeout(State, Timeout) -> + State1 = restart_pending_timer(State, Timeout), + State1#{mgmt_timeout => Timeout}. + %%%=================================================================== %%% Internal functions %%%=================================================================== @@ -408,8 +425,8 @@ transition_to_pending(#{mgmt_state := active, jid := JID, lserver := LServer, mgmt_timeout := Timeout} = State) -> State1 = cancel_ack_timer(State), ?INFO_MSG("Waiting for resumption of stream for ~s", [jid:encode(JID)]), - erlang:start_timer(timer:seconds(Timeout), self(), pending_timeout), - State2 = State1#{mgmt_state => pending}, + TRef = erlang:start_timer(timer:seconds(Timeout), self(), pending_timeout), + State2 = State1#{mgmt_state => pending, mgmt_pending_timer => TRef}, ejabberd_hooks:run_fold(c2s_session_pending, LServer, State2, []); transition_to_pending(State) -> State. @@ -648,20 +665,33 @@ add_resent_delay_info(_State, El, _Time) -> send(#{mod := Mod} = State, Pkt) -> Mod:send(State, Pkt). +-spec restart_pending_timer(state(), non_neg_integer()) -> state(). +restart_pending_timer(#{mgmt_pending_timer := TRef} = State, NewTimeout) -> + cancel_timer(TRef), + NewTRef = erlang:start_timer(timer:seconds(NewTimeout), self(), + pending_timeout), + State#{mgmt_pending_timer => NewTRef}; +restart_pending_timer(State, _NewTimeout) -> + State. + -spec cancel_ack_timer(state()) -> state(). cancel_ack_timer(#{mgmt_ack_timer := TRef} = State) -> - case erlang:cancel_timer(TRef) of - false -> - receive {timeout, TRef, _} -> ok - after 0 -> ok - end; - _ -> - ok - end, + cancel_timer(TRef), maps:remove(mgmt_ack_timer, State); cancel_ack_timer(State) -> State. +-spec cancel_timer(reference()) -> ok. +cancel_timer(TRef) -> + case erlang:cancel_timer(TRef) of + false -> + receive {timeout, TRef, _} -> ok + after 0 -> ok + end; + _ -> + ok + end. + -spec bounce_message_queue() -> ok. bounce_message_queue() -> receive {route, Pkt} -> diff --git a/test/ejabberd_SUITE_data/ejabberd.yml b/test/ejabberd_SUITE_data/ejabberd.yml index 5ee287400..74cabf584 100644 --- a/test/ejabberd_SUITE_data/ejabberd.yml +++ b/test/ejabberd_SUITE_data/ejabberd.yml @@ -232,6 +232,7 @@ Welcome to this XMPP server." mod_ping: [] mod_proxy65: [] mod_push: [] + mod_push_keepalive: [] mod_s2s_dialback: [] mod_stream_mgmt: resume_timeout: 3 @@ -294,6 +295,7 @@ Welcome to this XMPP server." mod_ping: [] mod_proxy65: [] mod_push: [] + mod_push_keepalive: [] mod_s2s_dialback: [] mod_stream_mgmt: resume_timeout: 3 diff --git a/test/push_tests.erl b/test/push_tests.erl index 535671ee1..4b49cc8fe 100644 --- a/test/push_tests.erl +++ b/test/push_tests.erl @@ -77,6 +77,8 @@ master_slave_cases() -> 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"), From ea96615460dfcc3c9894086450379b6edf8b8329 Mon Sep 17 00:00:00 2001 From: Marco Adkins Date: Thu, 27 Jul 2017 09:30:56 -0400 Subject: [PATCH 04/64] Ability to filter passwords from the log in mod_http_api (#1888) * Ability to filter passwords from the log when creating users through the mod_http_api --- src/mod_http_api.erl | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/mod_http_api.erl b/src/mod_http_api.erl index 7be05dbf1..ef881d14b 100644 --- a/src/mod_http_api.erl +++ b/src/mod_http_api.erl @@ -538,9 +538,17 @@ json_error(HTTPCode, JSONCode, Message) -> log(Call, Args, {Addr, Port}) -> AddrS = misc:ip_to_list({Addr, Port}), - ?INFO_MSG("API call ~s ~p from ~s:~p", [Call, Args, AddrS, Port]); + ?INFO_MSG("API call ~s ~p from ~s:~p", [Call, hide_sensitive_args(Args), AddrS, Port]); log(Call, Args, IP) -> - ?INFO_MSG("API call ~s ~p (~p)", [Call, Args, IP]). + ?INFO_MSG("API call ~s ~p (~p)", [Call, hide_sensitive_args(Args), IP]). + +hide_sensitive_args(Args=[_H|_T]) -> + lists:map( fun({<<"password">>, Password}) -> {<<"password">>, ejabberd_config:may_hide_data(Password)}; + ({<<"newpass">>,NewPassword}) -> {<<"newpass">>, ejabberd_config:may_hide_data(NewPassword)}; + (E) -> E end, + Args); +hide_sensitive_args(NonListArgs) -> + NonListArgs. permission_addon() -> Access = gen_mod:get_module_opt(global, ?MODULE, admin_ip_access, none), From 58110e4bc152100078dbc0cb66d4a5257ae645e0 Mon Sep 17 00:00:00 2001 From: Christophe Romain Date: Thu, 27 Jul 2017 15:31:38 +0200 Subject: [PATCH 05/64] Update OTP version check by configure --- configure.ac | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/configure.ac b/configure.ac index 884db5d4e..edf54722c 100644 --- a/configure.ac +++ b/configure.ac @@ -3,8 +3,8 @@ AC_PREREQ(2.53) AC_INIT(ejabberd, m4_esyscmd([echo `git describe --tags 2>/dev/null || echo 0.0` | sed 's/-g.*//;s/-/./' | tr -d '\012']), [ejabberd@process-one.net], [ejabberd]) -REQUIRE_ERLANG_MIN="6.1 (Erlang/OTP 17.1)" -REQUIRE_ERLANG_MAX="9.0.0 (No Max)" +REQUIRE_ERLANG_MIN="6.4 (Erlang/OTP 17.5)" +REQUIRE_ERLANG_MAX="100.0.0 (No Max)" AC_CONFIG_MACRO_DIR([m4]) From b66dab13130229268e070a0021f95733cb3aabac Mon Sep 17 00:00:00 2001 From: Christophe Romain Date: Thu, 27 Jul 2017 17:02:06 +0200 Subject: [PATCH 06/64] Add muc related hooks --- src/mod_muc.erl | 3 ++- src/mod_muc_room.erl | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/mod_muc.erl b/src/mod_muc.erl index 69fc2d0dc..8b6d7b8b9 100644 --- a/src/mod_muc.erl +++ b/src/mod_muc.erl @@ -166,7 +166,7 @@ restore_room(ServerHost, Host, Name) -> forget_room(ServerHost, Host, Name) -> LServer = jid:nameprep(ServerHost), - ejabberd_hooks:run(remove_room, LServer, [LServer, Name, Host]), + ejabberd_hooks:run(destroy_room, LServer, [LServer, Name, Host]), Mod = gen_mod:db_mod(LServer, ?MODULE), Mod:forget_room(LServer, Host, Name). @@ -256,6 +256,7 @@ handle_call({create, Room, From, Nick, Opts}, _From, Nick, NewOpts, QueueType), RMod = gen_mod:ram_db_mod(ServerHost, ?MODULE), RMod:register_online_room(ServerHost, Room, Host, Pid), + ejabberd_hooks:run(create_room, ServerHost, [ServerHost, Room, Host]), {reply, ok, State}. handle_cast({reload, ServerHost, NewOpts, OldOpts}, #state{host = OldHost}) -> diff --git a/src/mod_muc_room.erl b/src/mod_muc_room.erl index ec1cffd6a..31dbbbfa7 100644 --- a/src/mod_muc_room.erl +++ b/src/mod_muc_room.erl @@ -4004,6 +4004,7 @@ tab_add_online_user(JID, StateData) -> Room = StateData#state.room, Host = StateData#state.host, ServerHost = StateData#state.server_host, + ejabberd_hooks:run(join_room, ServerHost, [ServerHost, Room, Host, JID]), mod_muc:register_online_user(ServerHost, jid:tolower(JID), Room, Host). -spec tab_remove_online_user(jid(), state()) -> any(). @@ -4011,6 +4012,7 @@ tab_remove_online_user(JID, StateData) -> Room = StateData#state.room, Host = StateData#state.host, ServerHost = StateData#state.server_host, + ejabberd_hooks:run(leave_room, ServerHost, [ServerHost, Room, Host, JID]), mod_muc:unregister_online_user(ServerHost, jid:tolower(JID), Room, Host). -spec tab_count_user(jid(), state()) -> non_neg_integer(). From 3ca62a797a7911e3a3022850a119a2739979f6d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Sautret?= Date: Thu, 1 Jun 2017 12:12:46 +0200 Subject: [PATCH 07/64] Fix nick bug with MUC on riak --- src/mod_muc_riak.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mod_muc_riak.erl b/src/mod_muc_riak.erl index 8dd1eddf8..42e644fdd 100644 --- a/src/mod_muc_riak.erl +++ b/src/mod_muc_riak.erl @@ -60,7 +60,7 @@ restore_room(_LServer, Host, Name) -> forget_room(_LServer, Host, Name) -> {atomic, ejabberd_riak:delete(muc_room, {Name, Host})}. -can_use_nick(LServer, Host, JID, Nick) -> +can_use_nick(_LServer, Host, JID, Nick) -> {LUser, LServer, _} = jid:tolower(JID), LUS = {LUser, LServer}, case ejabberd_riak:get_by_index(muc_registered, From 5c48ba460920f8594dcd12f7a441446ddfa8b472 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chmielowski?= Date: Tue, 4 Jul 2017 20:37:25 +0200 Subject: [PATCH 08/64] Set high water mark in lager for all backends --- src/ejabberd_logger.erl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/ejabberd_logger.erl b/src/ejabberd_logger.erl index 1e48732c8..eee9d3b83 100644 --- a/src/ejabberd_logger.erl +++ b/src/ejabberd_logger.erl @@ -151,6 +151,9 @@ do_start() -> application:set_env(lager, crash_log_size, LogRotateSize), application:set_env(lager, crash_log_count, LogRotateCount), ejabberd:start_app(lager), + lists:foreach(fun(Handler) -> + lager:set_loghwm(Handler, LogRateLimit) + end, gen_event:which_handlers(lager_event)), ok. %% @spec () -> ok From e5f64bc24af581b7e8ffd73e7490cc93423d960b Mon Sep 17 00:00:00 2001 From: Alexey Shchepin Date: Mon, 10 Jul 2017 16:05:09 +0300 Subject: [PATCH 09/64] Fix get_module_opt call in mod_block_strangers --- src/mod_block_strangers.erl | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/mod_block_strangers.erl b/src/mod_block_strangers.erl index 49d79e043..bc9cb5ce4 100644 --- a/src/mod_block_strangers.erl +++ b/src/mod_block_strangers.erl @@ -59,7 +59,10 @@ filter_packet({#message{} = Msg, State} = Acc) -> LBFrom = jid:remove_resource(LFrom), #{pres_a := PresA, jid := JID, lserver := LServer} = State, AllowLocalUsers = - gen_mod:get_module_opt(LServer, ?MODULE, allow_local_users, true), + gen_mod:get_module_opt( + LServer, ?MODULE, allow_local_users, + fun(B) when is_boolean(B) -> B end, + true), case (Msg#message.body == [] andalso Msg#message.subject == []) orelse (AllowLocalUsers andalso From 0cfec92c148125f3ef700a242a6ee905894637dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chmielowski?= Date: Thu, 13 Jul 2017 10:49:35 +0200 Subject: [PATCH 10/64] Generate log messages when websocket is closed due timeouts --- src/ejabberd_http_ws.erl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ejabberd_http_ws.erl b/src/ejabberd_http_ws.erl index f4a73cc39..2c44d6552 100644 --- a/src/ejabberd_http_ws.erl +++ b/src/ejabberd_http_ws.erl @@ -241,6 +241,7 @@ handle_info(PingPong, StateName, StateData) when PingPong == ping orelse StateData2#state{pong_expected = false}}; handle_info({timeout, Timer, _}, _StateName, #state{timer = Timer} = StateData) -> + ?DEBUG("Closing websocket connection from hitting inactivity timeout", []), {stop, normal, StateData}; handle_info({timeout, Timer, _}, StateName, #state{ping_timer = Timer, ws = {_, WsPid}} = StateData) -> @@ -253,6 +254,7 @@ handle_info({timeout, Timer, _}, StateName, {next_state, StateName, StateData#state{ping_timer = PingTimer, pong_expected = true}}; true -> + ?DEBUG("Closing websocket connection from missing pongs", []), {stop, normal, StateData} end; handle_info(_, StateName, StateData) -> From 35a11526f91dd8f60a9dd94c241261115af6ad9f Mon Sep 17 00:00:00 2001 From: Christophe Romain Date: Thu, 27 Jul 2017 18:23:10 +0200 Subject: [PATCH 11/64] Revert "Fix get_module_opt call in mod_block_strangers" This reverts commit e5f64bc24af581b7e8ffd73e7490cc93423d960b. --- src/mod_block_strangers.erl | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/mod_block_strangers.erl b/src/mod_block_strangers.erl index bc9cb5ce4..49d79e043 100644 --- a/src/mod_block_strangers.erl +++ b/src/mod_block_strangers.erl @@ -59,10 +59,7 @@ filter_packet({#message{} = Msg, State} = Acc) -> LBFrom = jid:remove_resource(LFrom), #{pres_a := PresA, jid := JID, lserver := LServer} = State, AllowLocalUsers = - gen_mod:get_module_opt( - LServer, ?MODULE, allow_local_users, - fun(B) when is_boolean(B) -> B end, - true), + gen_mod:get_module_opt(LServer, ?MODULE, allow_local_users, true), case (Msg#message.body == [] andalso Msg#message.subject == []) orelse (AllowLocalUsers andalso From 51fa43834032f8d7a3c289be00213070809ce5b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chmielowski?= Date: Fri, 28 Jul 2017 16:07:54 +0200 Subject: [PATCH 12/64] Request basic auth dialog from browser --- src/mod_http_fileserver.erl | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/mod_http_fileserver.erl b/src/mod_http_fileserver.erl index c2144042e..4e3cfd08b 100644 --- a/src/mod_http_fileserver.erl +++ b/src/mod_http_fileserver.erl @@ -59,8 +59,13 @@ -define(HTTP_ERR_FILE_NOT_FOUND, {-1, 404, [], <<"Not found">>}). +-define(REQUEST_AUTH_HEADERS, + [{<<"WWW-Authenticate">>, <<"Basic realm=\"ejabberd\"">>}]). + -define(HTTP_ERR_FORBIDDEN, {-1, 403, [], <<"Forbidden">>}). +-define(HTTP_ERR_REQUEST_AUTH, + {-1, 401, ?REQUEST_AUTH_HEADERS, <<"Unauthorized">>}). -define(DEFAULT_CONTENT_TYPE, <<"application/octet-stream">>). @@ -317,12 +322,17 @@ serve(LocalPath, Auth, DocRoot, DirectoryIndices, CustomHeaders, DefaultContentT false end, case CanProceed of + false -> + ?HTTP_ERR_REQUEST_AUTH; true -> FileName = filename:join(filename:split(DocRoot) ++ LocalPath), case file:read_file_info(FileName) of - {error, enoent} -> ?HTTP_ERR_FILE_NOT_FOUND; - {error, enotdir} -> ?HTTP_ERR_FILE_NOT_FOUND; - {error, eacces} -> ?HTTP_ERR_FORBIDDEN; + {error, enoent} -> + ?HTTP_ERR_FILE_NOT_FOUND; + {error, enotdir} -> + ?HTTP_ERR_FILE_NOT_FOUND; + {error, eacces} -> + ?HTTP_ERR_FORBIDDEN; {ok, #file_info{type = directory}} -> serve_index(FileName, DirectoryIndices, CustomHeaders, From 67918b17d32e24ee4e3254634cf3d319c7ef0702 Mon Sep 17 00:00:00 2001 From: Badlop Date: Mon, 31 Jul 2017 22:51:12 +0200 Subject: [PATCH 13/64] Fix extauth.py so support : in passwords (thanks to jmberg)(#1676) --- test/ejabberd_SUITE_data/extauth.py | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/test/ejabberd_SUITE_data/extauth.py b/test/ejabberd_SUITE_data/extauth.py index fa2c9efd0..263d6464e 100755 --- a/test/ejabberd_SUITE_data/extauth.py +++ b/test/ejabberd_SUITE_data/extauth.py @@ -3,23 +3,27 @@ import struct def read(): (pkt_size,) = struct.unpack('>H', sys.stdin.read(2)) - pkt = sys.stdin.read(pkt_size).split(':') - cmd = pkt[0] - args_num = len(pkt) - 1 - if cmd == 'auth' and args_num >= 3: - if pkt[1] == "wrong": + pkt = sys.stdin.read(pkt_size) + cmd = pkt.split(':')[0] + if cmd == 'auth': + u, s, p = pkt.split(':', 3)[1:] + if u == "wrong": write(False) else: write(True) - elif cmd == 'isuser' and args_num == 2: + elif cmd == 'isuser': + u, s = pkt.split(':', 2)[1:] + elif cmd == 'setpass': + u, s, p = pkt.split(':', 3)[1:] write(True) - elif cmd == 'setpass' and args_num >= 3: + elif cmd == 'tryregister': + u, s, p = pkt.split(':', 3)[1:] write(True) - elif cmd == 'tryregister' and args_num >= 3: + elif cmd == 'removeuser': + u, s = pkt.split(':', 2)[1:] write(True) - elif cmd == 'removeuser' and args_num == 2: - write(True) - elif cmd == 'removeuser3' and args_num >= 3: + elif cmd == 'removeuser3': + u, s, p = pkt.split(':', 3)[1:] write(True) else: write(False) From 7e6d1c24c2913146bae3fba4c1a9b741b094f0e4 Mon Sep 17 00:00:00 2001 From: Christophe Romain Date: Tue, 1 Aug 2017 13:33:14 +0200 Subject: [PATCH 14/64] Update spec from custom and allow modules dependencies (#1740) --- src/ext_mod.erl | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/src/ext_mod.erl b/src/ext_mod.erl index 80296f7b7..ecdfa04c3 100644 --- a/src/ext_mod.erl +++ b/src/ext_mod.erl @@ -146,16 +146,29 @@ get_commands_spec() -> %% -- public modules functions update() -> - add_sources(?REPOS), + Contrib = maps:put(?REPOS, [], maps:new()), + Jungles = lists:foldl(fun({Package, Spec}, Acc) -> + Repo = proplists:get_value(url, Spec, ""), + Mods = maps:get(Repo, Acc, []), + maps:put(Repo, [Package|Mods], Acc) + end, Contrib, modules_spec(sources_dir(), "*/*")), + Repos = maps:fold(fun(Repo, _Mods, Acc) -> + Update = add_sources(Repo), + ?INFO_MSG("Update packages from repo ~s: ~p", [Repo, Update]), + case Update of + ok -> Acc; + Error -> [{repository, Repo, Error}|Acc] + end + end, [], Jungles), Res = lists:foldl(fun({Package, Spec}, Acc) -> - Path = proplists:get_value(url, Spec, ""), - Update = add_sources(Package, Path), + Repo = proplists:get_value(url, Spec, ""), + Update = add_sources(Package, Repo), ?INFO_MSG("Update package ~s: ~p", [Package, Update]), case Update of ok -> Acc; - Error -> [Error|Acc] + Error -> [{Package, Repo, Error}|Acc] end - end, [], modules_spec(sources_dir(), "*")), + end, Repos, modules_spec(sources_dir(), "*")), case Res of [] -> ok; [Error|_] -> Error @@ -547,7 +560,9 @@ compile_result(Results) -> compile_options() -> [verbose, report_errors, report_warnings] ++ [{i, filename:join(app_dir(App), "include")} - || App <- [fast_xml, xmpp, p1_utils, ejabberd]]. + || App <- [fast_xml, xmpp, p1_utils, ejabberd]] + ++ [{i, filename:join(mod_dir(Mod), "include")} + || Mod <- installed()]. app_dir(App) -> case code:lib_dir(App) of @@ -562,6 +577,10 @@ app_dir(App) -> Dir end. +mod_dir({Package, Spec}) -> + Default = filename:join(modules_dir(), Package), + proplists:get_value(path, Spec, Default). + compile_erlang_file(Dest, File) -> compile_erlang_file(Dest, File, compile_options()). From 636d68e0a91ec2189723aba82fc8a69beb48d50a Mon Sep 17 00:00:00 2001 From: Christophe Romain Date: Tue, 1 Aug 2017 14:41:16 +0200 Subject: [PATCH 15/64] Fix PEP node identity (#1717) --- src/mod_pubsub.erl | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/mod_pubsub.erl b/src/mod_pubsub.erl index 1a620cb6b..ff2779ed4 100644 --- a/src/mod_pubsub.erl +++ b/src/mod_pubsub.erl @@ -546,14 +546,7 @@ disco_identity(Host, Node, From) -> case get_allowed_items_call(Host, Nidx, From, Type, Options, Owners) of {result, _} -> - {result, [#identity{category = <<"pubsub">>, - type = <<"pep">>}, - #identity{category = <<"pubsub">>, - type = <<"leaf">>, - name = case get_option(Options, title) of - false -> <<>>; - [Title] -> Title - end}]}; + {result, [#identity{category = <<"pubsub">>, type = <<"pep">>}]}; _ -> {result, []} end @@ -586,8 +579,7 @@ disco_features(Host, Node, From) -> Type, Options, Owners) of {result, _} -> {result, - [?NS_PUBSUB | - [feature(F) || F <- plugin_features(Host, <<"pep">>)]]}; + [?NS_PUBSUB | [feature(F) || F <- plugin_features(Host, <<"pep">>)]]}; _ -> {result, []} end From 0ba6c78ed01ae53dd54957eec5bab015b927d3da Mon Sep 17 00:00:00 2001 From: Christophe Romain Date: Tue, 1 Aug 2017 15:15:01 +0200 Subject: [PATCH 16/64] Fix disco#items on PEP service --- src/mod_pubsub.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mod_pubsub.erl b/src/mod_pubsub.erl index ff2779ed4..4b34e21fb 100644 --- a/src/mod_pubsub.erl +++ b/src/mod_pubsub.erl @@ -612,7 +612,7 @@ disco_items(Host, <<>>, From) -> jid = jid:make(Host), name = case get_option(Options, title) of false -> <<>>; - [Title] -> Title + Title -> Title end} | Acc]; _ -> Acc From f22bd6eb46e828792805872f63d8db2f2460ce96 Mon Sep 17 00:00:00 2001 From: Christophe Romain Date: Tue, 1 Aug 2017 15:40:34 +0200 Subject: [PATCH 17/64] Fix PEP node removal (#1839) --- src/node_pep.erl | 2 +- src/node_pep_sql.erl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/node_pep.erl b/src/node_pep.erl index 6dd7a68c4..cc0dd41fb 100644 --- a/src/node_pep.erl +++ b/src/node_pep.erl @@ -119,7 +119,7 @@ create_node(Nidx, Owner) -> delete_node(Nodes) -> {result, {_, _, Result}} = node_flat:delete_node(Nodes), - {result, {[], Result}}. + {result, {default, Result}}. subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup, Options) -> diff --git a/src/node_pep_sql.erl b/src/node_pep_sql.erl index 68a2ce946..b84c945bd 100644 --- a/src/node_pep_sql.erl +++ b/src/node_pep_sql.erl @@ -73,7 +73,7 @@ create_node(Nidx, Owner) -> delete_node(Nodes) -> {result, {_, _, Result}} = node_flat_sql:delete_node(Nodes), - {result, {[], Result}}. + {result, {default, Result}}. subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup, Options) -> From 5e26190b98cd6b328bb7bf124cc5e5be205eb1ad Mon Sep 17 00:00:00 2001 From: Christophe Romain Date: Tue, 1 Aug 2017 17:00:52 +0200 Subject: [PATCH 18/64] Fix PubSub send last published items (#1572) --- src/mod_pubsub.erl | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/mod_pubsub.erl b/src/mod_pubsub.erl index 4b34e21fb..32ce2897f 100644 --- a/src/mod_pubsub.erl +++ b/src/mod_pubsub.erl @@ -671,16 +671,14 @@ caps_update(#jid{luser = U, lserver = S, lresource = R}, #jid{lserver = Host} = presence(Host, {presence, U, S, [R], JID}). -spec presence_probe(jid(), jid(), pid()) -> ok. -presence_probe(#jid{luser = U, lserver = S, lresource = R} = JID, JID, Pid) -> - presence(S, {presence, JID, Pid}), - presence(S, {presence, U, S, [R], JID}); presence_probe(#jid{luser = U, lserver = S}, #jid{luser = U, lserver = S}, _Pid) -> %% ignore presence_probe from my other ressources %% to not get duplicated last items ok; -presence_probe(#jid{luser = U, lserver = S, lresource = R}, #jid{lserver = S} = JID, _Pid) -> - presence(S, {presence, U, S, [R], JID}); -presence_probe(_Host, _JID, _Pid) -> +presence_probe(#jid{lserver = S} = From, #jid{lserver = S} = To, Pid) -> + presence(S, {presence, From, Pid}), + presence(S, {presence, From#jid.luser, S, [From#jid.lresource], To}); +presence_probe(_From, _To, _Pid) -> %% ignore presence_probe from remote contacts, %% those are handled via caps_add ok. From 50511fcff7526546c65bba179b80d87632d504a5 Mon Sep 17 00:00:00 2001 From: Jing Sun Date: Wed, 2 Aug 2017 10:31:42 +0200 Subject: [PATCH 19/64] Fix spec for mod_pubsub:subscribe_node --- src/mod_pubsub.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mod_pubsub.erl b/src/mod_pubsub.erl index 32ce2897f..4a2e20f87 100644 --- a/src/mod_pubsub.erl +++ b/src/mod_pubsub.erl @@ -1713,7 +1713,7 @@ delete_node(Host, Node, Owner) -> %%
  • The node does not support subscriptions.
  • %%
  • The node does not exist.
  • %% --spec subscribe_node(host(), binary(), jid(), binary(), [{binary(), [binary()]}]) -> +-spec subscribe_node(host(), binary(), jid(), jid(), [{binary(), [binary()]}]) -> {result, pubsub()} | {error, stanza_error()}. subscribe_node(Host, Node, From, JID, Configuration) -> SubModule = subscription_plugin(Host), From 73509686f18bf0689abeb9bff024d08ff3d1d969 Mon Sep 17 00:00:00 2001 From: Christophe Romain Date: Wed, 2 Aug 2017 12:22:13 +0200 Subject: [PATCH 20/64] Fix getting cached last item (#1814) --- src/mod_pubsub.erl | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/mod_pubsub.erl b/src/mod_pubsub.erl index 4a2e20f87..263b33ab9 100644 --- a/src/mod_pubsub.erl +++ b/src/mod_pubsub.erl @@ -2142,9 +2142,14 @@ get_allowed_items_call(Host, Nidx, From, Type, Options, Owners, RSM) -> node_call(Host, Type, get_items, [Nidx, From, AccessModel, PS, RG, undefined, RSM]). get_last_items(Host, Type, Nidx, LJID, Count) -> - case node_action(Host, Type, get_last_items, [Nidx, LJID, Count]) of - {result, Items} -> Items; - _ -> [] + case get_cached_item(Host, Nidx) of + undefined -> + case node_action(Host, Type, get_last_items, [Nidx, LJID, Count]) of + {result, Items} -> Items; + _ -> [] + end; + LastItem -> + [LastItem] end. %% @doc

    Resend the items of a node to the user.

    From 9edcbadd606d85bf2e32c71e4c33f4ac63bb67ee Mon Sep 17 00:00:00 2001 From: Christophe Romain Date: Wed, 2 Aug 2017 14:36:32 +0200 Subject: [PATCH 21/64] Fix clustering table reg_users_counter (#1889) --- src/ejabberd_auth_mnesia.erl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ejabberd_auth_mnesia.erl b/src/ejabberd_auth_mnesia.erl index 9c2152578..690152674 100644 --- a/src/ejabberd_auth_mnesia.erl +++ b/src/ejabberd_auth_mnesia.erl @@ -208,6 +208,8 @@ remove_user(User, Server) -> {error, db_failure} end. +need_transform(#reg_users_counter{}) -> + false; need_transform(#passwd{us = {U, S}, password = Pass}) -> if is_binary(Pass) -> case store_type(S) of From 5dcc97c85a70c48e91838a9e2f88e4aca5b7f161 Mon Sep 17 00:00:00 2001 From: Badlop Date: Thu, 3 Aug 2017 10:52:44 +0200 Subject: [PATCH 22/64] Preliminary export PubSub data from Mnesia tables to SQL file (#1571) --- src/ejd2sql.erl | 5 ++- src/mod_pubsub.erl | 96 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+), 2 deletions(-) diff --git a/src/ejd2sql.erl b/src/ejd2sql.erl index c4f00a551..c801eb973 100644 --- a/src/ejd2sql.erl +++ b/src/ejd2sql.erl @@ -58,6 +58,7 @@ modules() -> mod_offline, mod_privacy, mod_private, + mod_pubsub, mod_roster, mod_shared_roster, mod_vcard]. @@ -80,8 +81,8 @@ export(Server, Output, Module) -> case export(LServer, Table, IO, ConvertFun) of {atomic, ok} -> ok; {aborted, Reason} -> - ?ERROR_MSG("Failed export for module ~p: ~p", - [Module, Reason]) + ?ERROR_MSG("Failed export for module ~p and table ~p: ~p", + [Module, Table, Reason]) end end, Module:export(Server)), close_output(Output, IO). diff --git a/src/mod_pubsub.erl b/src/mod_pubsub.erl index 263b33ab9..9f2c66aaa 100644 --- a/src/mod_pubsub.erl +++ b/src/mod_pubsub.erl @@ -39,6 +39,8 @@ -protocol({xep, 163, '1.2'}). -protocol({xep, 248, '0.2'}). +-compile([{parse_transform, ejabberd_sql_pt}]). + -include("ejabberd.hrl"). -include("logger.hrl"). -include("xmpp.hrl"). @@ -92,6 +94,11 @@ terminate/2, code_change/3, depends/2]). -export([send_loop/1, mod_opt_type/1]). +-export([export/1]). + +-include("ejabberd_sql_pt.hrl"). + +-define(PROCNAME, ejabberd_mod_pubsub). -define(LOOPNAME, ejabberd_mod_pubsub_loop). @@ -3866,6 +3873,95 @@ purge_offline(Host, LJID, Node) -> Error end. +%% 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(jlib:jid_to_string({U,S,R})); + _ -> ejabberd_sql:escape(Hostid) + end, + NODE = ejabberd_sql:escape(Nodeid), + PARENT = <<"">>, + IdB = integer_to_binary(Id), + TYPE = ejabberd_sql:escape(<>), + [?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, ", '", jlib:jid_to_string(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],":"). + mod_opt_type(access_createnode) -> fun acl:access_rules_validator/1; mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end; mod_opt_type(host) -> fun iolist_to_binary/1; From 1274bcdba98ddd2222f972eb84a7496cd3e22ece Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chmielowski?= Date: Thu, 3 Aug 2017 13:48:57 +0200 Subject: [PATCH 23/64] Change policy of user_resources command This fixes issue #1908 --- src/ejabberd_sm.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ejabberd_sm.erl b/src/ejabberd_sm.erl index 333edffa6..c47bfcead 100644 --- a/src/ejabberd_sm.erl +++ b/src/ejabberd_sm.erl @@ -972,7 +972,7 @@ get_commands_spec() -> args = [], result = {num_sessions, integer}}, #ejabberd_commands{name = user_resources, tags = [session], desc = "List user's connected resources", - policy = user, + policy = admin, module = ?MODULE, function = user_resources, args = [{user, binary}, {host, binary}], args_desc = ["User name", "Server name"], From 96c0483533598178610631ac8a8d82c426a7fa72 Mon Sep 17 00:00:00 2001 From: Christophe Romain Date: Thu, 3 Aug 2017 14:54:06 +0200 Subject: [PATCH 24/64] Fix Xref from 5dcc97c --- src/mod_pubsub.erl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mod_pubsub.erl b/src/mod_pubsub.erl index 9f2c66aaa..4cbb22750 100644 --- a/src/mod_pubsub.erl +++ b/src/mod_pubsub.erl @@ -3933,7 +3933,7 @@ export(_Server) -> owners = Owners, options = Options}) -> HOST = case Hostid of - {U,S,R} -> ejabberd_sql:escape(jlib:jid_to_string({U,S,R})); + {U,S,R} -> ejabberd_sql:escape(jid:encode({U,S,R})); _ -> ejabberd_sql:escape(Hostid) end, NODE = ejabberd_sql:escape(Nodeid), @@ -3949,7 +3949,7 @@ export(_Server) -> 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, ", '", jlib:jid_to_string(Usr), "');\n"] || Usr <- Owners],"\n"]; + " values (", IdB, ", '", jid:encode(Usr), "');\n"] || Usr <- Owners],"\n"]; (_Host, _R) -> [] end}]. From ce0beb550c2690f25e33b7f6466bba4b3e4653d8 Mon Sep 17 00:00:00 2001 From: Christophe Romain Date: Thu, 3 Aug 2017 15:34:01 +0200 Subject: [PATCH 25/64] Move pubsub sql export to pubsub_db_sql module --- src/mod_pubsub.erl | 99 ++----------------------------------------- src/pubsub_db_sql.erl | 94 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+), 96 deletions(-) diff --git a/src/mod_pubsub.erl b/src/mod_pubsub.erl index 4cbb22750..630882b11 100644 --- a/src/mod_pubsub.erl +++ b/src/mod_pubsub.erl @@ -39,8 +39,6 @@ -protocol({xep, 163, '1.2'}). -protocol({xep, 248, '0.2'}). --compile([{parse_transform, ejabberd_sql_pt}]). - -include("ejabberd.hrl"). -include("logger.hrl"). -include("xmpp.hrl"). @@ -91,14 +89,9 @@ %% API and gen_server callbacks -export([start/2, stop/1, init/1, handle_call/3, handle_cast/2, handle_info/2, - terminate/2, code_change/3, depends/2]). + terminate/2, code_change/3, depends/2, export/1]). -export([send_loop/1, mod_opt_type/1]). --export([export/1]). - --include("ejabberd_sql_pt.hrl"). - --define(PROCNAME, ejabberd_mod_pubsub). -define(LOOPNAME, ejabberd_mod_pubsub_loop). @@ -3873,94 +3866,8 @@ purge_offline(Host, LJID, Node) -> Error end. -%% 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(<>), - [?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],":"). +export(Server) -> + pubsub_db_sql:export(Server). mod_opt_type(access_createnode) -> fun acl:access_rules_validator/1; mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end; diff --git a/src/pubsub_db_sql.erl b/src/pubsub_db_sql.erl index ae7a9fde6..ae28184db 100644 --- a/src/pubsub_db_sql.erl +++ b/src/pubsub_db_sql.erl @@ -25,12 +25,16 @@ -module(pubsub_db_sql). +-compile([{parse_transform, ejabberd_sql_pt}]). + -author("pablo.polvorin@process-one.net"). -include("pubsub.hrl"). +-include("ejabberd_sql_pt.hrl"). -export([add_subscription/1, read_subscription/1, delete_subscription/1, update_subscription/1]). +-export([export/1]). %% TODO: Those -spec lines produce errors in old Erlang versions. %% They can be enabled again in ejabberd 3.0 because it uses R12B or higher. @@ -139,3 +143,93 @@ sql_to_integer(N) -> binary_to_integer(N). sql_to_boolean(B) -> B == <<"1">>. sql_to_timestamp(T) -> xmpp_util:decode_timestamp(T). + +%% REVIEW: +%% * this code takes NODEID from Itemid2, and forgets about Nodeidx +%% * this code assumes Payload only contains one xmlelement() +%% * PUBLISHER is taken from Creation +export(_Server) -> + [{pubsub_item, + fun(_Host, #pubsub_item{itemid = {Itemid1, NODEID}, + %nodeidx = _Nodeidx, + creation = {{C1, C2, C3}, Cusr}, + modification = {{M1, M2, M3}, _Musr}, + payload = Payload}) -> + ITEMID = ejabberd_sql:escape(Itemid1), + CREATION = ejabberd_sql:escape(list_to_binary( + string:join([string:right(integer_to_list(I),6,$0)||I<-[C1,C2,C3]],":"))), + MODIFICATION = ejabberd_sql:escape(list_to_binary( + string:join([string:right(integer_to_list(I),6,$0)||I<-[M1,M2,M3]],":"))), + PUBLISHER = ejabberd_sql:escape(jid:encode(Cusr)), + [PayloadEl] = [El || {xmlel,_,_,_} = El <- Payload], + PAYLOAD = ejabberd_sql:escape(fxml:element_to_binary(PayloadEl)), + [?SQL("delete from pubsub_item where itemid=%(ITEMID)s;"), + ?SQL("insert into pubsub_item(itemid,nodeid,creation,modification,publisher,payload) \n" + " values (%(ITEMID)s, %(NODEID)d, %(CREATION)s, + %(MODIFICATION)s, %(PUBLISHER)s, %(PAYLOAD)s);")]; + (_Host, _R) -> + [] + end}, +%% REVIEW: +%% * From the mnesia table, the #pubsub_state.items is not used in ODBC +%% * Right now AFFILIATION is the first letter of Affiliation +%% * Right now SUBSCRIPTIONS expects only one Subscription +%% * Right now SUBSCRIPTIONS letter is the first letter of Subscription + {pubsub_state, + fun(_Host, #pubsub_state{stateid = {Jid, Stateid}, + %nodeidx = Nodeidx, + items = _Items, + affiliation = Affiliation, + subscriptions = Subscriptions}) -> + STATEID = list_to_binary(integer_to_list(Stateid)), + JID = ejabberd_sql:escape(jid:encode(Jid)), + NODEID = <<"unknown">>, %% TODO: integer_to_list(Nodeidx), + AFFILIATION = list_to_binary(string:substr(atom_to_list(Affiliation),1,1)), + SUBSCRIPTIONS = list_to_binary(parse_subscriptions(Subscriptions)), + [?SQL("delete from pubsub_state where stateid=%(STATEID)s;"), + ?SQL("insert into pubsub_state(stateid,jid,nodeid,affiliation,subscriptions)\n" + " values (%(STATEID)s, %(JID)s, %(NODEID)s, %(AFFILIATION)s, %(SUBSCRIPTIONS)s);")]; + (_Host, _R) -> + [] + end}, + +%% REVIEW: +%% * Parents is not migrated to PARENTs +%% * Probably some option VALs are not correctly represented in mysql + {pubsub_node, + fun(_Host, #pubsub_node{nodeid = {Hostid, Nodeid}, + id = Id, + parents = _Parents, + type = Type, + owners = Owners, + options = Options}) -> + HOST = case Hostid of + {U,S,R} -> ejabberd_sql:escape(jid:encode({U,S,R})); + _ -> ejabberd_sql:escape(Hostid) + end, + NODE = ejabberd_sql:escape(Nodeid), + PARENT = <<"">>, + IdB = integer_to_binary(Id), + TYPE = ejabberd_sql:escape(<>), + [?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],":"). + From 06450f4a824c2d7bd66f208901cdc652881a9185 Mon Sep 17 00:00:00 2001 From: Christophe Romain Date: Thu, 3 Aug 2017 15:48:23 +0200 Subject: [PATCH 26/64] Keep disco#info on PEP compatible with XEP-0060 (#1717) --- src/mod_pubsub.erl | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/mod_pubsub.erl b/src/mod_pubsub.erl index 630882b11..d682eb5f3 100644 --- a/src/mod_pubsub.erl +++ b/src/mod_pubsub.erl @@ -546,7 +546,12 @@ disco_identity(Host, Node, From) -> case get_allowed_items_call(Host, Nidx, From, Type, Options, Owners) of {result, _} -> - {result, [#identity{category = <<"pubsub">>, type = <<"pep">>}]}; + {result, [#identity{category = <<"pubsub">>, type = <<"pep">>}, + #identity{category = <<"pubsub">>, type = <<"leaf">>, + name = case get_option(Options, title) of + false -> <<>>; + Title -> Title + end}]}; _ -> {result, []} end From 65f409480483e9c6270dae1746448a47a82e79f6 Mon Sep 17 00:00:00 2001 From: Christophe Romain Date: Thu, 3 Aug 2017 16:55:56 +0200 Subject: [PATCH 27/64] Prepare packaging for 17.08 --- Dockerfile | 2 +- mix.exs | 2 +- mix.lock | 20 ++++++++++++-------- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/Dockerfile b/Dockerfile index 6de6f5783..e5f3d78f3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ FROM debian:jessie-slim MAINTAINER Rafael Römhild -ENV EJABBERD_BRANCH=17.04 \ +ENV EJABBERD_BRANCH=17.08 \ EJABBERD_USER=ejabberd \ EJABBERD_HTTPS=true \ EJABBERD_STARTTLS=true \ diff --git a/mix.exs b/mix.exs index 025552f45..24b74f87c 100644 --- a/mix.exs +++ b/mix.exs @@ -3,7 +3,7 @@ defmodule Ejabberd.Mixfile do def project do [app: :ejabberd, - version: "17.6.0", + version: "17.8.0", description: description(), elixir: "~> 1.4", elixirc_paths: ["lib"], diff --git a/mix.lock b/mix.lock index 40eda1930..4b67809e5 100644 --- a/mix.lock +++ b/mix.lock @@ -1,18 +1,22 @@ -%{"cache_tab": {:hex, :cache_tab, "1.0.8", "eac8923f0f20c35e630317790c4d4c2629c5bc792753fa48eb5391bd39c80245", [:rebar3], [{:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}]}, - "distillery": {:hex, :distillery, "1.4.0", "d633cd322c8efa0428082b00b7f902daf8caa166d45f9022bbc19a896d2e1e56", [:mix], []}, - "earmark": {:hex, :earmark, "1.2.2", "f718159d6b65068e8daeef709ccddae5f7fdc770707d82e7d126f584cd925b74", [:mix], []}, - "esip": {:hex, :esip, "1.0.12", "e0505afe74bb362b0ea486e2a64b3c1934b1eb541a7b3e990b23045e4bdc07d4", [:rebar3], [{:fast_tls, "1.0.12", [hex: :fast_tls, optional: false]}, {:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}, {:stun, "1.0.11", [hex: :stun, optional: false]}]}, - "ex_doc": {:hex, :ex_doc, "0.16.1", "b4b8a23602b4ce0e9a5a960a81260d1f7b29635b9652c67e95b0c2f7ccee5e81", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, optional: false]}]}, +%{"cache_tab": {:hex, :cache_tab, "1.0.10", "dd6aba8951ba15cab4ad483d997f8eefdb0cb00225971d0629c730d107a2bed6", [:rebar3], [{:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}]}, + "distillery": {:hex, :distillery, "1.4.1", "546d851bf27ae8fe0727e10e4fc4e146ad836eecee138263a60431e688044ed3", [:mix], []}, + "earmark": {:hex, :earmark, "1.2.3", "206eb2e2ac1a794aa5256f3982de7a76bf4579ff91cb28d0e17ea2c9491e46a4", [:mix], []}, + "eredis": {:hex, :eredis, "1.0.8", "ab4fda1c4ba7fbe6c19c26c249dc13da916d762502c4b4fa2df401a8d51c5364", [:rebar], []}, + "esip": {:hex, :esip, "1.0.15", "82c8b0178618c10b1ac9690841d94025c982d63f8cd6c8f8bf920cf33e301658", [:rebar3], [{:fast_tls, "1.0.15", [hex: :fast_tls, optional: false]}, {:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}, {:stun, "1.0.14", [hex: :stun, optional: false]}]}, + "ex_doc": {:hex, :ex_doc, "0.16.2", "3b3e210ebcd85a7c76b4e73f85c5640c011d2a0b2f06dcdf5acdb2ae904e5084", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, optional: false]}]}, "ezlib": {:hex, :ezlib, "1.0.2", "22004ecf553a7d831404394d5642712e2aede90522e22bd6ccc089ca410ee098", [:rebar3], []}, - "fast_tls": {:hex, :fast_tls, "1.0.12", "861b591f23103142782c5b72de8898673a37acd78646c50dbda978e1e1c5b463", [:rebar3], [{:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}]}, + "fast_tls": {:hex, :fast_tls, "1.0.15", "96546e6a8b8384fbbcddf435c4c42cf2c0a3dc1858c3c9c2e62a74ae1ddd526a", [:rebar3], [{:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}]}, "fast_xml": {:hex, :fast_xml, "1.1.23", "1e7b311d3353806ee832d7630fef57713987cea40a7020669cf057d537de4721", [:rebar3], [{:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}]}, "fast_yaml": {:hex, :fast_yaml, "1.0.10", "ce5d52b77cb21968c8b73aa29b39f56a4ffd7e1e11f853d5597e7277858f155e", [:rebar3], [{:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}]}, "goldrush": {:hex, :goldrush, "0.1.9", "f06e5d5f1277da5c413e84d5a2924174182fb108dabb39d5ec548b27424cd106", [:rebar3], []}, "iconv": {:hex, :iconv, "1.0.5", "ae871aa11c854695db37e48fd5e5583b02e106126fbdf21bb53448f5a47c092b", [:rebar3], [{:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}]}, "jiffy": {:hex, :jiffy, "0.14.11", "919a87d491c5a6b5e3bbc27fafedc3a0761ca0b4c405394f121f582fd4e3f0e5", [:rebar3], []}, "lager": {:hex, :lager, "3.4.2", "150b9a17b23ae6d3265cc10dc360747621cf217b7a22b8cddf03b2909dbf7aa5", [:rebar3], [{:goldrush, "0.1.9", [hex: :goldrush, optional: false]}]}, + "p1_mysql": {:hex, :p1_mysql, "1.0.3", "e2cc26f2e8d17c3885a9c2fee3ff64fcac5915896f50ab6f6aa9b0da1eed341c", [:rebar3], []}, "p1_oauth2": {:hex, :p1_oauth2, "0.6.1", "4e021250cc198c538b097393671a41e7cebf463c248980320e038fe0316eb56b", [:rebar3], []}, + "p1_pgsql": {:hex, :p1_pgsql, "1.1.3", "ce94c83e9605c88d5f541b8f4b49edff3dc2bbacd1b6409c4cad0fbf7bef2ac4", [:rebar3], []}, "p1_utils": {:hex, :p1_utils, "1.0.9", "c33c230efbeb4dcc02911161e3cb1a93231a92df15e3fc97de655a9271a26d9f", [:rebar3], []}, + "sqlite3": {:hex, :sqlite3, "1.1.5", "794738b6d07b6d36ec6d42492cb9d629bad9cf3761617b8b8d728e765db19840", [:rebar3], []}, "stringprep": {:hex, :stringprep, "1.0.9", "9182ba39931cd1db528b8883cad0d63530abe2bf21835d26cec2f9af8bc00be0", [:rebar3], [{:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}]}, - "stun": {:hex, :stun, "1.0.11", "386cb3e3543e17a6351028a43e047c2172225d035c826a72fcb67672da9874e5", [:rebar3], [{:fast_tls, "1.0.12", [hex: :fast_tls, optional: false]}, {:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}]}, - "xmpp": {:hex, :xmpp, "1.1.11", "8c49964d0d48b81080d2c5700fcf6cc19950ae9dc60a71bd3ff3d4620336d052", [:rebar3], [{:fast_xml, "1.1.23", [hex: :fast_xml, optional: false]}, {:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}, {:stringprep, "1.0.9", [hex: :stringprep, optional: false]}]}} + "stun": {:hex, :stun, "1.0.14", "6dc2080c25a72f7087301dc7333c1ea7d27ea4d88efaa379fc2b5924f3b17006", [:rebar3], [{:fast_tls, "1.0.15", [hex: :fast_tls, optional: false]}, {:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}]}, + "xmpp": {:hex, :xmpp, "1.1.14", "e186f5208e7a448a4af784a8d2cb87cefe99dd49b24623e25d38115b23a50e12", [:rebar3], [{:fast_xml, "1.1.23", [hex: :fast_xml, optional: false]}, {:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}, {:stringprep, "1.0.9", [hex: :stringprep, optional: false]}]}} From 7815164216145e2299c17aad26b1215ccd614c5f Mon Sep 17 00:00:00 2001 From: Christophe Romain Date: Fri, 4 Aug 2017 10:34:13 +0200 Subject: [PATCH 28/64] Add minor details (#1353) --- README | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README b/README index 67e062267..87b684f14 100644 --- a/README +++ b/README @@ -116,11 +116,14 @@ To compile ejabberd you need: needed on systems with GNU Libc. - ImageMagick's Convert program. Optional. For CAPTCHA challenges. +If your system splits packages in libraries and development headers, you must +install the development packages also. ### 1. Compile and install on *nix systems To compile ejabberd, execute the following commands. The first one is only -necessary if your source tree didn't come with a `configure` script. +necessary if your source tree didn't come with a `configure` script (In this +case you need autoconf installed). ./autogen.sh ./configure From 3d185c0fb8b0625a60c7ca1dd5156410b9b50b0a Mon Sep 17 00:00:00 2001 From: Christophe Romain Date: Fri, 4 Aug 2017 11:53:32 +0200 Subject: [PATCH 29/64] Fix missing validation from 633b68db1 (#1900) --- src/ejabberd_auth.erl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ejabberd_auth.erl b/src/ejabberd_auth.erl index 251e36ff7..b34925ff0 100644 --- a/src/ejabberd_auth.erl +++ b/src/ejabberd_auth.erl @@ -261,12 +261,12 @@ try_register(User, Server, Password) -> ok; (Mod, _) -> db_try_register( - User, Server, Password, Mod) + LUser, LServer, Password, Mod) end, {error, not_allowed}, auth_modules(LServer)) of ok -> ejabberd_hooks:run( - register_user, Server, [User, Server]); + register_user, LServer, [LUser, LServer]); {error, _} = Err -> Err end; From 0516a3dc0ebddb72726b0408c83a0e73b3896fb4 Mon Sep 17 00:00:00 2001 From: Badlop Date: Fri, 4 Aug 2017 12:26:07 +0200 Subject: [PATCH 30/64] Remove unused 'managers' option, related to the deferred XEP-0321 (#1443) --- src/mod_roster.erl | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/mod_roster.erl b/src/mod_roster.erl index 28bc06171..cb9b0f7bf 100644 --- a/src/mod_roster.erl +++ b/src/mod_roster.erl @@ -35,7 +35,6 @@ -module(mod_roster). -protocol({xep, 237, '1.3'}). --protocol({xep, 321, '0.1'}). -author('alexey@process-one.net'). @@ -444,11 +443,10 @@ decode_item(Item, R, Managed) -> process_iq_set(#iq{from = From, to = To, sub_els = [#roster_query{items = [QueryItem]}]} = IQ) -> #jid{user = User, luser = LUser, lserver = LServer} = To, - Managed = {From#jid.luser, From#jid.lserver} /= {LUser, LServer}, LJID = jid:tolower(QueryItem#roster_item.jid), F = fun () -> Item = get_roster_item(LUser, LServer, LJID), - Item2 = decode_item(QueryItem, Item, Managed), + Item2 = decode_item(QueryItem, Item, false), Item3 = ejabberd_hooks:run_fold(roster_process_item, LServer, Item2, [LServer]), @@ -1200,8 +1198,6 @@ mod_opt_type(access) -> fun acl:access_rules_validator/1; mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end; mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1; -mod_opt_type(managers) -> - fun (B) when is_list(B) -> B end; mod_opt_type(store_current_id) -> fun (B) when is_boolean(B) -> B end; mod_opt_type(versioning) -> @@ -1213,5 +1209,5 @@ mod_opt_type(O) when O == cache_life_time; O == cache_size -> mod_opt_type(O) when O == use_cache; O == cache_missed -> fun (B) when is_boolean(B) -> B end; mod_opt_type(_) -> - [access, db_type, iqdisc, managers, store_current_id, + [access, db_type, iqdisc, store_current_id, versioning, cache_life_time, cache_size, use_cache, cache_missed]. From 101e808124a0bc6bb97f92de4e1961b544c1dd7b Mon Sep 17 00:00:00 2001 From: Badlop Date: Fri, 4 Aug 2017 13:09:17 +0200 Subject: [PATCH 31/64] Fix warning in previous commit --- src/mod_roster.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mod_roster.erl b/src/mod_roster.erl index cb9b0f7bf..7bc5f7de7 100644 --- a/src/mod_roster.erl +++ b/src/mod_roster.erl @@ -440,7 +440,7 @@ decode_item(Item, R, Managed) -> end, groups = Item#roster_item.groups}. -process_iq_set(#iq{from = From, to = To, +process_iq_set(#iq{from = _From, to = To, sub_els = [#roster_query{items = [QueryItem]}]} = IQ) -> #jid{user = User, luser = LUser, lserver = LServer} = To, LJID = jid:tolower(QueryItem#roster_item.jid), From aa9eb001d06e7bd9d82104ae3769089105fe3d7a Mon Sep 17 00:00:00 2001 From: Mathias Ertl Date: Fri, 4 Aug 2017 18:49:33 +0200 Subject: [PATCH 32/64] fix FIREWALL_WINDOW option --- ejabberdctl.template | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ejabberdctl.template b/ejabberdctl.template index edf9bea0d..57ada34dc 100755 --- a/ejabberdctl.template +++ b/ejabberdctl.template @@ -69,9 +69,7 @@ done # define erl parameters ERLANG_OPTS="+K $POLL -smp $SMP +P $ERL_PROCESSES $ERL_OPTIONS" if [ "$FIREWALL_WINDOW" != "" ] ; then - ERLANG_OPTS="$ERLANG_OPTS -kernel \ - inet_dist_listen_min ${FIREWALL_WINDOW%-*} \ - inet_dist_listen_max ${FIREWALL_WINDOW#*-}" + ERLANG_OPTS="$ERLANG_OPTS -kernel inet_dist_listen_min ${FIREWALL_WINDOW%-*} inet_dist_listen_max ${FIREWALL_WINDOW#*-}/" fi if [ "$INET_DIST_INTERFACE" != "" ] ; then INET_DIST_INTERFACE2=$("$ERL" -noshell -eval 'case inet:parse_address("'$INET_DIST_INTERFACE'") of {ok,IP} -> io:format("~p",[IP]); _ -> ok end.' -s erlang halt) From e1aaa1c99dd543b7a72ed6f62336accb85516214 Mon Sep 17 00:00:00 2001 From: Holger Weiss Date: Sat, 5 Aug 2017 18:59:32 +0200 Subject: [PATCH 33/64] ejabberd_c2s: Fix priority of 'certfile' option Use the 'certfile' listener option rather than a 'domain_certfile' for ejabberd_c2s listeners that have "tls: true" configured. A 'domain_certfile' should only be preferred for STARTTLS connections. Closes #1911. --- src/ejabberd_c2s.erl | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/ejabberd_c2s.erl b/src/ejabberd_c2s.erl index 4b265d29d..a0be2e118 100644 --- a/src/ejabberd_c2s.erl +++ b/src/ejabberd_c2s.erl @@ -297,14 +297,19 @@ process_terminated(State, _Reason) -> %%%=================================================================== %%% xmpp_stream_in callbacks %%%=================================================================== -tls_options(#{lserver := LServer, tls_options := DefaultOpts}) -> - TLSOpts1 = case ejabberd_config:get_option( - {c2s_certfile, LServer}, - ejabberd_config:get_option( - {domain_certfile, LServer})) of - undefined -> DefaultOpts; - CertFile -> lists:keystore(certfile, 1, DefaultOpts, - {certfile, CertFile}) +tls_options(#{lserver := LServer, tls_options := DefaultOpts, + stream_encrypted := Encrypted}) -> + TLSOpts1 = case {Encrypted, proplists:get_value(certfile, DefaultOpts)} of + {true, CertFile} when CertFile /= undefined -> DefaultOpts; + {_, _} -> + case ejabberd_config:get_option( + {c2s_certfile, LServer}, + ejabberd_config:get_option( + {domain_certfile, LServer})) of + undefined -> DefaultOpts; + CertFile -> lists:keystore(certfile, 1, DefaultOpts, + {certfile, CertFile}) + end end, TLSOpts2 = case ejabberd_config:get_option( {c2s_ciphers, LServer}) of From 92532a0d66763e213e88c5c333dc6ead3a7ec3b5 Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Sat, 5 Aug 2017 20:58:21 +0300 Subject: [PATCH 34/64] Replace gen_fsm with p1_fsm to avoid warnings in OTP20+ --- src/ejabberd_bosh.erl | 38 ++++++++++++++++++-------------------- src/ejabberd_http_ws.erl | 14 +++++++------- src/ejabberd_receiver.erl | 4 ++-- src/ejabberd_s2s.erl | 2 +- src/ejabberd_sql.erl | 30 ++++++++++++++---------------- src/eldap.erl | 30 +++++++++++++++--------------- src/mod_irc_connection.erl | 10 +++++----- src/mod_muc.erl | 6 +++--- src/mod_muc_admin.erl | 20 ++++++++++---------- src/mod_muc_log.erl | 2 +- src/mod_muc_room.erl | 12 ++++++------ src/mod_proxy65_stream.erl | 10 +++++----- src/mod_sip_proxy.erl | 7 +++---- 13 files changed, 90 insertions(+), 95 deletions(-) diff --git a/src/ejabberd_bosh.erl b/src/ejabberd_bosh.erl index 0755067e7..1df6681ff 100644 --- a/src/ejabberd_bosh.erl +++ b/src/ejabberd_bosh.erl @@ -27,9 +27,7 @@ -protocol({xep, 124, '1.11'}). -protocol({xep, 206, '1.4'}). --define(GEN_FSM, p1_fsm). - --behaviour(?GEN_FSM). +-behaviour(p1_fsm). %% API -export([start/2, start/3, start_link/3]). @@ -137,18 +135,18 @@ start(#body{attrs = Attrs} = Body, IP, SID) -> end. start(StateName, State) -> - (?GEN_FSM):start_link(?MODULE, [StateName, State], + p1_fsm:start_link(?MODULE, [StateName, State], ?FSMOPTS). start_link(Body, IP, SID) -> - (?GEN_FSM):start_link(?MODULE, [Body, IP, SID], + p1_fsm:start_link(?MODULE, [Body, IP, SID], ?FSMOPTS). send({http_bind, FsmRef, IP}, Packet) -> send_xml({http_bind, FsmRef, IP}, Packet). send_xml({http_bind, FsmRef, _IP}, Packet) -> - case catch (?GEN_FSM):sync_send_all_state_event(FsmRef, + case catch p1_fsm:sync_send_all_state_event(FsmRef, {send_xml, Packet}, ?SEND_TIMEOUT) of @@ -160,12 +158,12 @@ send_xml({http_bind, FsmRef, _IP}, Packet) -> setopts({http_bind, FsmRef, _IP}, Opts) -> case lists:member({active, once}, Opts) of true -> - (?GEN_FSM):send_all_state_event(FsmRef, + p1_fsm:send_all_state_event(FsmRef, {activate, self()}); _ -> case lists:member({active, false}, Opts) of true -> - case catch (?GEN_FSM):sync_send_all_state_event(FsmRef, + case catch p1_fsm:sync_send_all_state_event(FsmRef, deactivate_socket) of {'EXIT', _} -> {error, einval}; @@ -181,7 +179,7 @@ custom_receiver({http_bind, FsmRef, _IP}) -> {receiver, ?MODULE, FsmRef}. become_controller(FsmRef, C2SPid) -> - (?GEN_FSM):send_all_state_event(FsmRef, + p1_fsm:send_all_state_event(FsmRef, {become_controller, C2SPid}). change_controller({http_bind, FsmRef, _IP}, C2SPid) -> @@ -190,14 +188,14 @@ change_controller({http_bind, FsmRef, _IP}, C2SPid) -> reset_stream({http_bind, _FsmRef, _IP}) -> ok. change_shaper({http_bind, FsmRef, _IP}, Shaper) -> - (?GEN_FSM):send_all_state_event(FsmRef, + p1_fsm:send_all_state_event(FsmRef, {change_shaper, Shaper}). monitor({http_bind, FsmRef, _IP}) -> erlang:monitor(process, FsmRef). close({http_bind, FsmRef, _IP}) -> - catch (?GEN_FSM):sync_send_all_state_event(FsmRef, + catch p1_fsm:sync_send_all_state_event(FsmRef, close). sockname(_Socket) -> {ok, {{0, 0, 0, 0}, 0}}. @@ -269,7 +267,7 @@ process_request(Data, IP, Type) -> end. process_request(Pid, Req, _IP, Type) -> - case catch (?GEN_FSM):sync_send_event(Pid, Req, + case catch p1_fsm:sync_send_event(Pid, Req, infinity) of #body{} = Resp -> bosh_response(Resp, Type); @@ -571,7 +569,7 @@ handle_sync_event({send_xml, El}, _From, StateName, of {{value, {TRef, From, Body}}, Q} -> cancel_timer(TRef), - (?GEN_FSM):send_event(self(), {Body, From}), + p1_fsm:send_event(self(), {Body, From}), State1#state{shaped_receivers = Q}; _ -> State1 end, @@ -598,7 +596,7 @@ handle_info({timeout, TRef, shaper_timeout}, StateName, State) -> case p1_queue:out(State#state.shaped_receivers) of {{value, {TRef, From, Req}}, Q} -> - (?GEN_FSM):send_event(self(), {Req, From}), + p1_fsm:send_event(self(), {Req, From}), {next_state, StateName, State#state{shaped_receivers = Q}}; {{value, _}, _} -> @@ -630,7 +628,7 @@ terminate(_Reason, _StateName, State) -> mod_bosh:close_session(State#state.sid), case State#state.c2s_pid of C2SPid when is_pid(C2SPid) -> - (?GEN_FSM):send_event(C2SPid, closed); + p1_fsm:send_event(C2SPid, closed); _ -> ok end, bounce_receivers(State, closed), @@ -644,7 +642,7 @@ print_state(State) -> State. route_els(#state{el_ibuf = Buf, c2s_pid = C2SPid} = State) -> NewBuf = p1_queue:dropwhile( fun(El) -> - ?GEN_FSM:send_event(C2SPid, El), + p1_fsm:send_event(C2SPid, El), true end, Buf), State#state{el_ibuf = NewBuf}. @@ -653,7 +651,7 @@ route_els(State, Els) -> case State#state.c2s_pid of C2SPid when is_pid(C2SPid) -> lists:foreach(fun (El) -> - (?GEN_FSM):send_event(C2SPid, El) + p1_fsm:send_event(C2SPid, El) end, Els), State; @@ -676,7 +674,7 @@ reply(State, Body, RID, From) -> case catch gb_trees:take_smallest(Receivers) of {NextRID, {From1, Req}, Receivers1} when NextRID == RID + 1 -> - (?GEN_FSM):send_event(self(), {Req, From1}), + p1_fsm:send_event(self(), {Req, From1}), State2#state{receivers = Receivers1}; _ -> State2#state{receivers = Receivers} end. @@ -715,7 +713,7 @@ do_reply(State, From, Body, RID) -> ?DEBUG("send reply:~n** RequestID: ~p~n** Reply: " "~p~n** To: ~p~n** State: ~p", [RID, Body, From, State]), - (?GEN_FSM):reply(From, Body), + p1_fsm:reply(From, Body), Responses = gb_trees:delete_any(RID, State#state.responses), Responses1 = case gb_trees:size(Responses) of @@ -1053,7 +1051,7 @@ buf_out(Buf, I, Els) -> end. cancel_timer(TRef) when is_reference(TRef) -> - (?GEN_FSM):cancel_timer(TRef); + p1_fsm:cancel_timer(TRef); cancel_timer(_) -> false. restart_timer(TRef, Timeout, Msg) -> diff --git a/src/ejabberd_http_ws.erl b/src/ejabberd_http_ws.erl index 2c44d6552..f9f7b07e9 100644 --- a/src/ejabberd_http_ws.erl +++ b/src/ejabberd_http_ws.erl @@ -28,7 +28,7 @@ -author('ecestari@process-one.net'). --behaviour(gen_fsm). +-behaviour(p1_fsm). -export([start/1, start_link/1, init/1, handle_event/3, handle_sync_event/4, code_change/4, handle_info/3, @@ -75,13 +75,13 @@ -export_type([ws_socket/0]). start(WS) -> - gen_fsm:start(?MODULE, [WS], ?FSMOPTS). + p1_fsm:start(?MODULE, [WS], ?FSMOPTS). start_link(WS) -> - gen_fsm:start_link(?MODULE, [WS], ?FSMOPTS). + p1_fsm:start_link(?MODULE, [WS], ?FSMOPTS). send_xml({http_ws, FsmRef, _IP}, Packet) -> - case catch gen_fsm:sync_send_all_state_event(FsmRef, + case catch p1_fsm:sync_send_all_state_event(FsmRef, {send_xml, Packet}, 15000) of @@ -93,7 +93,7 @@ send_xml({http_ws, FsmRef, _IP}, Packet) -> setopts({http_ws, FsmRef, _IP}, Opts) -> case lists:member({active, once}, Opts) of true -> - gen_fsm:send_all_state_event(FsmRef, + p1_fsm:send_all_state_event(FsmRef, {activate, self()}); _ -> ok end. @@ -105,11 +105,11 @@ peername({http_ws, _FsmRef, IP}) -> {ok, IP}. controlling_process(_Socket, _Pid) -> ok. become_controller(FsmRef, C2SPid) -> - gen_fsm:send_all_state_event(FsmRef, + p1_fsm:send_all_state_event(FsmRef, {become_controller, C2SPid}). close({http_ws, FsmRef, _IP}) -> - catch gen_fsm:sync_send_all_state_event(FsmRef, close). + catch p1_fsm:sync_send_all_state_event(FsmRef, close). socket_handoff(LocalPath, Request, Socket, SockMod, Buf, Opts) -> ejabberd_websocket:socket_handoff(LocalPath, Request, Socket, SockMod, diff --git a/src/ejabberd_receiver.erl b/src/ejabberd_receiver.erl index 44c29680c..52077ac3c 100644 --- a/src/ejabberd_receiver.erl +++ b/src/ejabberd_receiver.erl @@ -234,7 +234,7 @@ terminate(_Reason, State) -> close_stream(XMLStreamState), if C2SPid /= undefined -> - gen_fsm:send_event(C2SPid, closed); + p1_fsm:send_event(C2SPid, closed); true -> ok end, catch (State#state.sock_mod):close(State#state.socket), @@ -272,7 +272,7 @@ process_data([Element | Els], element(1, Element) == xmlstreamend -> if C2SPid == undefined -> State; true -> - catch gen_fsm:send_event(C2SPid, + catch p1_fsm:send_event(C2SPid, element_wrapper(Element)), process_data(Els, State) end; diff --git a/src/ejabberd_s2s.erl b/src/ejabberd_s2s.erl index a0e9411cf..c7f9b6684 100644 --- a/src/ejabberd_s2s.erl +++ b/src/ejabberd_s2s.erl @@ -682,7 +682,7 @@ complete_s2s_info([Connection | T], Type, Result) -> -spec get_s2s_state(pid()) -> [{status, open | closed | error} | {s2s_pid, pid()}]. get_s2s_state(S2sPid) -> - Infos = case gen_fsm:sync_send_all_state_event(S2sPid, + Infos = case p1_fsm:sync_send_all_state_event(S2sPid, get_state_infos) of {state_infos, Is} -> [{status, open} | Is]; diff --git a/src/ejabberd_sql.erl b/src/ejabberd_sql.erl index 35d970291..cae41da6c 100644 --- a/src/ejabberd_sql.erl +++ b/src/ejabberd_sql.erl @@ -29,9 +29,7 @@ -author('alexey@process-one.net'). --define(GEN_FSM, p1_fsm). - --behaviour(?GEN_FSM). +-behaviour(p1_fsm). %% External exports -export([start/1, start_link/2, @@ -113,11 +111,11 @@ %%% API %%%---------------------------------------------------------------------- start(Host) -> - (?GEN_FSM):start(ejabberd_sql, [Host], + p1_fsm:start(ejabberd_sql, [Host], fsm_limit_opts() ++ (?FSMOPTS)). start_link(Host, StartInterval) -> - (?GEN_FSM):start_link(ejabberd_sql, + p1_fsm:start_link(ejabberd_sql, [Host, StartInterval], fsm_limit_opts() ++ (?FSMOPTS)). @@ -160,7 +158,7 @@ sql_call(Host, Msg) -> case ejabberd_sql_sup:get_random_pid(Host) of none -> {error, <<"Unknown Host">>}; Pid -> - (?GEN_FSM):sync_send_event(Pid,{sql_cmd, Msg, + p1_fsm:sync_send_event(Pid,{sql_cmd, Msg, p1_time_compat:monotonic_time(milli_seconds)}, query_timeout(Host)) end; @@ -168,7 +166,7 @@ sql_call(Host, Msg) -> end. keep_alive(Host, PID) -> - (?GEN_FSM):sync_send_event(PID, + p1_fsm:sync_send_event(PID, {sql_cmd, {sql_query, ?KEEPALIVE_QUERY}, p1_time_compat:monotonic_time(milli_seconds)}, query_timeout(Host)). @@ -280,7 +278,7 @@ init([Host, StartInterval]) -> keep_alive, [Host, self()]) end, [DBType | _] = db_opts(Host), - (?GEN_FSM):send_event(self(), connect), + p1_fsm:send_event(self(), connect), ejabberd_sql_sup:add_pid(Host, self()), QueueType = case ejabberd_config:get_option({sql_queue_type, Host}) of undefined -> @@ -313,7 +311,7 @@ connecting(connect, #state{host = Host} = State) -> PendingRequests = p1_queue:dropwhile( fun(Req) -> - ?GEN_FSM:send_event(self(), Req), + p1_fsm:send_event(self(), Req), true end, State#state.pending_requests), State1 = State#state{db_ref = Ref, @@ -325,7 +323,7 @@ connecting(connect, #state{host = Host} = State) -> "Retry after: ~p seconds", [State#state.db_type, Reason, State#state.start_interval div 1000]), - (?GEN_FSM):send_event_after(State#state.start_interval, + p1_fsm:send_event_after(State#state.start_interval, connect), {next_state, connecting, State} end; @@ -337,7 +335,7 @@ connecting(Event, State) -> connecting({sql_cmd, {sql_query, ?KEEPALIVE_QUERY}, _Timestamp}, From, State) -> - (?GEN_FSM):reply(From, + p1_fsm:reply(From, {error, <<"SQL connection failed">>}), {next_state, connecting, State}; connecting({sql_cmd, Command, Timestamp} = Req, From, @@ -350,7 +348,7 @@ connecting({sql_cmd, Command, Timestamp} = Req, From, catch error:full -> Q = p1_queue:dropwhile( fun({sql_cmd, _, To, _Timestamp}) -> - (?GEN_FSM):reply( + p1_fsm:reply( To, {error, <<"SQL connection failed">>}), true end, State#state.pending_requests), @@ -393,7 +391,7 @@ code_change(_OldVsn, StateName, State, _Extra) -> %% monitoring the connection) handle_info({'DOWN', _MonitorRef, process, _Pid, _Info}, _StateName, State) -> - (?GEN_FSM):send_event(self(), connect), + p1_fsm:send_event(self(), connect), {next_state, connecting, State}; handle_info(Info, StateName, State) -> ?WARNING_MSG("unexpected info in ~p: ~p", @@ -734,16 +732,16 @@ sql_query_to_iolist(SQLQuery) -> abort_on_driver_error({error, <<"query timed out">>} = Reply, From) -> - (?GEN_FSM):reply(From, Reply), + p1_fsm:reply(From, Reply), {stop, timeout, get(?STATE_KEY)}; abort_on_driver_error({error, <<"Failed sending data on socket", _/binary>>} = Reply, From) -> - (?GEN_FSM):reply(From, Reply), + p1_fsm:reply(From, Reply), {stop, closed, get(?STATE_KEY)}; abort_on_driver_error(Reply, From) -> - (?GEN_FSM):reply(From, Reply), + p1_fsm:reply(From, Reply), {next_state, session_established, get(?STATE_KEY)}. %% == pure ODBC code diff --git a/src/eldap.erl b/src/eldap.erl index f47550353..8e6b710b1 100644 --- a/src/eldap.erl +++ b/src/eldap.erl @@ -63,7 +63,7 @@ %%% active_bind - sent bind() request and waiting for response %%%---------------------------------------------------------------------- --behaviour(gen_fsm). +-behaviour(p1_fsm). -include("ejabberd.hrl"). -include("logger.hrl"). @@ -148,7 +148,7 @@ start_link(Name) -> Reg_name = misc:binary_to_atom(<<"eldap_", Name/binary>>), - gen_fsm:start_link({local, Reg_name}, ?MODULE, [], []). + p1_fsm:start_link({local, Reg_name}, ?MODULE, [], []). -spec start_link(binary(), [binary()], inet:port_number(), binary(), binary(), tlsopts()) -> any(). @@ -156,7 +156,7 @@ start_link(Name) -> start_link(Name, Hosts, Port, Rootdn, Passwd, Opts) -> Reg_name = misc:binary_to_atom(<<"eldap_", Name/binary>>), - gen_fsm:start_link({local, Reg_name}, ?MODULE, + p1_fsm:start_link({local, Reg_name}, ?MODULE, [Hosts, Port, Rootdn, Passwd, Opts], []). -spec get_status(handle()) -> any(). @@ -166,7 +166,7 @@ start_link(Name, Hosts, Port, Rootdn, Passwd, Opts) -> %%% -------------------------------------------------------------------- get_status(Handle) -> Handle1 = get_handle(Handle), - gen_fsm:sync_send_all_state_event(Handle1, get_status). + p1_fsm:sync_send_all_state_event(Handle1, get_status). %%% -------------------------------------------------------------------- %%% Shutdown connection (and process) asynchronous. @@ -175,7 +175,7 @@ get_status(Handle) -> close(Handle) -> Handle1 = get_handle(Handle), - gen_fsm:send_all_state_event(Handle1, close). + p1_fsm:send_all_state_event(Handle1, close). %%% -------------------------------------------------------------------- %%% Add an entry. The entry field MUST NOT exist for the AddRequest @@ -192,7 +192,7 @@ close(Handle) -> %%% -------------------------------------------------------------------- add(Handle, Entry, Attributes) -> Handle1 = get_handle(Handle), - gen_fsm:sync_send_event(Handle1, + p1_fsm:sync_send_event(Handle1, {add, Entry, add_attrs(Attributes)}, ?CALL_TIMEOUT). %%% Do sanity check ! @@ -216,7 +216,7 @@ add_attrs(Attrs) -> %%% -------------------------------------------------------------------- delete(Handle, Entry) -> Handle1 = get_handle(Handle), - gen_fsm:sync_send_event(Handle1, {delete, Entry}, + p1_fsm:sync_send_event(Handle1, {delete, Entry}, ?CALL_TIMEOUT). %%% -------------------------------------------------------------------- @@ -234,7 +234,7 @@ delete(Handle, Entry) -> modify(Handle, Object, Mods) -> Handle1 = get_handle(Handle), - gen_fsm:sync_send_event(Handle1, {modify, Object, Mods}, + p1_fsm:sync_send_event(Handle1, {modify, Object, Mods}, ?CALL_TIMEOUT). %%% @@ -274,7 +274,7 @@ m(Operation, Type, Values) -> modify_dn(Handle, Entry, NewRDN, DelOldRDN, NewSup) -> Handle1 = get_handle(Handle), - gen_fsm:sync_send_event(Handle1, + p1_fsm:sync_send_event(Handle1, {modify_dn, Entry, NewRDN, bool_p(DelOldRDN), optional(NewSup)}, ?CALL_TIMEOUT). @@ -283,7 +283,7 @@ modify_dn(Handle, Entry, NewRDN, DelOldRDN, NewSup) -> modify_passwd(Handle, DN, Passwd) -> Handle1 = get_handle(Handle), - gen_fsm:sync_send_event(Handle1, + p1_fsm:sync_send_event(Handle1, {modify_passwd, DN, Passwd}, ?CALL_TIMEOUT). %%% -------------------------------------------------------------------- @@ -298,7 +298,7 @@ modify_passwd(Handle, DN, Passwd) -> bind(Handle, RootDN, Passwd) -> Handle1 = get_handle(Handle), - gen_fsm:sync_send_event(Handle1, {bind, RootDN, Passwd}, + p1_fsm:sync_send_event(Handle1, {bind, RootDN, Passwd}, ?CALL_TIMEOUT). %%% Sanity checks ! @@ -356,7 +356,7 @@ search(Handle, L) when is_list(L) -> call_search(Handle, A) -> Handle1 = get_handle(Handle), - gen_fsm:sync_send_event(Handle1, {search, A}, + p1_fsm:sync_send_event(Handle1, {search, A}, ?CALL_TIMEOUT). -spec parse_search_args(search_args()) -> eldap_search(). @@ -637,7 +637,7 @@ active(Event, From, S) -> %%---------------------------------------------------------------------- %% Func: handle_event/3 -%% Called when gen_fsm:send_all_state_event/2 is invoked. +%% Called when p1_fsm:send_all_state_event/2 is invoked. %% Returns: {next_state, NextStateName, NextStateData} | %% {next_state, NextStateName, NextStateData, Timeout} | %% {stop, Reason, NewStateData} @@ -680,7 +680,7 @@ handle_info({Tag, _Socket, Data}, StateName, S) case catch recvd_packet(Data, S) of {response, Response, RequestType} -> NewS = case Response of - {reply, Reply, To, S1} -> gen_fsm:reply(To, Reply), S1; + {reply, Reply, To, S1} -> p1_fsm:reply(To, Reply), S1; {ok, S1} -> S1 end, if StateName == active_bind andalso @@ -709,7 +709,7 @@ handle_info({timeout, Timer, {cmd_timeout, Id}}, StateName, S) -> case cmd_timeout(Timer, Id, S) of {reply, To, Reason, NewS} -> - gen_fsm:reply(To, Reason), + p1_fsm:reply(To, Reason), {next_state, StateName, NewS}; {error, _Reason} -> {next_state, StateName, S} end; diff --git a/src/mod_irc_connection.erl b/src/mod_irc_connection.erl index 1e90c4005..b7b2f8e1d 100644 --- a/src/mod_irc_connection.erl +++ b/src/mod_irc_connection.erl @@ -27,7 +27,7 @@ -author('alexey@process-one.net'). --behaviour(gen_fsm). +-behaviour(p1_fsm). %% External exports -export([start_link/12, start/13, route_chan/4, @@ -91,7 +91,7 @@ start(From, Host, ServerHost, Server, Username, start_link(From, Host, Server, Username, Encoding, Port, Password, Ident, RemoteAddr, RealName, WebircPassword, Mod) -> - gen_fsm:start_link(?MODULE, + p1_fsm:start_link(?MODULE, [From, Host, Server, Username, Encoding, Port, Password, Ident, RemoteAddr, RealName, WebircPassword, Mod], ?FSMOPTS). @@ -109,7 +109,7 @@ start_link(From, Host, Server, Username, Encoding, Port, %%---------------------------------------------------------------------- init([From, Host, Server, Username, Encoding, Port, Password, Ident, RemoteAddr, RealName, WebircPassword, Mod]) -> - gen_fsm:send_event(self(), init), + p1_fsm:send_event(self(), init), {ok, open_socket, #state{mod = Mod, encoding = Encoding, port = Port, password = Password, @@ -628,11 +628,11 @@ handle_info({tcp, _Socket, Data}, StateName, StateData#state{inbuf = NewBuf}}; handle_info({tcp_closed, _Socket}, StateName, StateData) -> - gen_fsm:send_event(self(), closed), + p1_fsm:send_event(self(), closed), {next_state, StateName, StateData}; handle_info({tcp_error, _Socket, _Reason}, StateName, StateData) -> - gen_fsm:send_event(self(), closed), + p1_fsm:send_event(self(), closed), {next_state, StateName, StateData}. %%---------------------------------------------------------------------- diff --git a/src/mod_muc.erl b/src/mod_muc.erl index 8b6d7b8b9..2d87dd1d5 100644 --- a/src/mod_muc.erl +++ b/src/mod_muc.erl @@ -668,7 +668,7 @@ iq_disco_items(_ServerHost, _Host, _From, Lang, _MaxRoomsDiscoItems, _Node, _RSM {error, timeout | notfound}. get_room_disco_item({Name, Host, Pid}, Query) -> RoomJID = jid:make(Name, Host), - try gen_fsm:sync_send_all_state_event(Pid, Query, 100) of + try p1_fsm:sync_send_all_state_event(Pid, Query, 100) of {item, Desc} -> {ok, #disco_item{jid = RoomJID, name = Desc}}; false -> @@ -684,7 +684,7 @@ get_subscribed_rooms(ServerHost, Host, From) -> BareFrom = jid:remove_resource(From), lists:flatmap( fun({Name, _, Pid}) -> - case gen_fsm:sync_send_all_state_event(Pid, {is_subscribed, BareFrom}) of + case p1_fsm:sync_send_all_state_event(Pid, {is_subscribed, BareFrom}) of true -> [jid:make(Name, Host)]; false -> [] end; @@ -766,7 +766,7 @@ process_iq_register_set(ServerHost, Host, From, broadcast_service_message(ServerHost, Host, Msg) -> lists:foreach( fun({_, _, Pid}) -> - gen_fsm:send_all_state_event( + p1_fsm:send_all_state_event( Pid, {service_message, Msg}) end, get_online_rooms(ServerHost, Host)). diff --git a/src/mod_muc_admin.erl b/src/mod_muc_admin.erl index 2d1e66ba5..332c83b55 100644 --- a/src/mod_muc_admin.erl +++ b/src/mod_muc_admin.erl @@ -597,7 +597,7 @@ muc_create_room(ServerHost, {Name, Host, _}, DefRoomOpts) -> destroy_room(Name, Service) -> case mod_muc:find_online_room(Name, Service) of {ok, Pid} -> - gen_fsm:send_all_state_event(Pid, destroy), + p1_fsm:send_all_state_event(Pid, destroy), ok; error -> error @@ -716,11 +716,11 @@ get_rooms(ServerHost) -> end, Hosts). get_room_config(Room_pid) -> - {ok, R} = gen_fsm:sync_send_all_state_event(Room_pid, get_config), + {ok, R} = p1_fsm:sync_send_all_state_event(Room_pid, get_config), R. get_room_state(Room_pid) -> - {ok, R} = gen_fsm:sync_send_all_state_event(Room_pid, get_state), + {ok, R} = p1_fsm:sync_send_all_state_event(Room_pid, get_state), R. %%--------------- @@ -786,7 +786,7 @@ find_serverhost(Host, ServerHosts) -> ServerHost. act_on_room(destroy, {N, H, Pid}, SH) -> - gen_fsm:send_all_state_event( + p1_fsm:send_all_state_event( Pid, {destroy, <<"Room destroyed by rooms_unused_destroy.">>}), mod_muc:room_destroyed(H, N, Pid, SH), mod_muc:forget_room(SH, H, N); @@ -888,7 +888,7 @@ change_room_option(Name, Service, OptionString, ValueString) -> {Option, Value} = format_room_option(OptionString, ValueString), Config = get_room_config(Pid), Config2 = change_option(Option, Value, Config), - {ok, _} = gen_fsm:sync_send_all_state_event(Pid, {change_config, Config2}), + {ok, _} = p1_fsm:sync_send_all_state_event(Pid, {change_config, Config2}), ok end. @@ -983,7 +983,7 @@ get_room_affiliations(Name, Service) -> case mod_muc:find_online_room(Name, Service) of {ok, Pid} -> %% Get the PID of the online room, then request its state - {ok, StateData} = gen_fsm:sync_send_all_state_event(Pid, get_state), + {ok, StateData} = p1_fsm:sync_send_all_state_event(Pid, get_state), Affiliations = ?DICT:to_list(StateData#state.affiliations), lists:map( fun({{Uname, Domain, _Res}, {Aff, Reason}}) when is_atom(Aff)-> @@ -1012,7 +1012,7 @@ set_room_affiliation(Name, Service, JID, AffiliationString) -> case mod_muc:find_online_room(Name, Service) of {ok, Pid} -> %% Get the PID for the online room so we can get the state of the room - {ok, StateData} = gen_fsm:sync_send_all_state_event(Pid, {process_item_change, {jid:decode(JID), affiliation, Affiliation, <<"">>}, undefined}), + {ok, StateData} = p1_fsm:sync_send_all_state_event(Pid, {process_item_change, {jid:decode(JID), affiliation, Affiliation, <<"">>}, undefined}), mod_muc:store_room(StateData#state.server_host, StateData#state.host, StateData#state.room, make_opts(StateData)), ok; error -> @@ -1035,7 +1035,7 @@ subscribe_room(User, Nick, Room, Nodes) -> UserJID -> case get_room_pid(Name, Host) of Pid when is_pid(Pid) -> - case gen_fsm:sync_send_all_state_event( + case p1_fsm:sync_send_all_state_event( Pid, {muc_subscribe, UserJID, Nick, NodeList}) of {ok, SubscribedNodes} -> @@ -1062,7 +1062,7 @@ unsubscribe_room(User, Room) -> UserJID -> case get_room_pid(Name, Host) of Pid when is_pid(Pid) -> - case gen_fsm:sync_send_all_state_event( + case p1_fsm:sync_send_all_state_event( Pid, {muc_unsubscribe, UserJID}) of ok -> @@ -1085,7 +1085,7 @@ unsubscribe_room(User, Room) -> get_subscribers(Name, Host) -> case get_room_pid(Name, Host) of Pid when is_pid(Pid) -> - {ok, JIDList} = gen_fsm:sync_send_all_state_event(Pid, get_subscribers), + {ok, JIDList} = p1_fsm:sync_send_all_state_event(Pid, get_subscribers), [jid:encode(jid:remove_resource(J)) || J <- JIDList]; _ -> throw({error, "The room does not exist"}) diff --git a/src/mod_muc_log.erl b/src/mod_muc_log.erl index 3d4c87c0f..61101d1c2 100644 --- a/src/mod_muc_log.erl +++ b/src/mod_muc_log.erl @@ -1142,7 +1142,7 @@ get_room_state(RoomName, MucService) -> -spec get_room_state(pid()) -> mod_muc_room:state(). get_room_state(RoomPid) -> - {ok, R} = gen_fsm:sync_send_all_state_event(RoomPid, + {ok, R} = p1_fsm:sync_send_all_state_event(RoomPid, get_state), R. diff --git a/src/mod_muc_room.erl b/src/mod_muc_room.erl index 31dbbbfa7..2a1ca6011 100644 --- a/src/mod_muc_room.erl +++ b/src/mod_muc_room.erl @@ -27,7 +27,7 @@ -author('alexey@process-one.net'). --behaviour(gen_fsm). +-behaviour(p1_fsm). %% External exports -export([start_link/10, @@ -94,23 +94,23 @@ %%%---------------------------------------------------------------------- start(Host, ServerHost, Access, Room, HistorySize, RoomShaper, Creator, Nick, DefRoomOpts, QueueType) -> - gen_fsm:start(?MODULE, [Host, ServerHost, Access, Room, HistorySize, + p1_fsm:start(?MODULE, [Host, ServerHost, Access, Room, HistorySize, RoomShaper, Creator, Nick, DefRoomOpts, QueueType], ?FSMOPTS). start(Host, ServerHost, Access, Room, HistorySize, RoomShaper, Opts, QueueType) -> - gen_fsm:start(?MODULE, [Host, ServerHost, Access, Room, HistorySize, + p1_fsm:start(?MODULE, [Host, ServerHost, Access, Room, HistorySize, RoomShaper, Opts, QueueType], ?FSMOPTS). start_link(Host, ServerHost, Access, Room, HistorySize, RoomShaper, Creator, Nick, DefRoomOpts, QueueType) -> - gen_fsm:start_link(?MODULE, [Host, ServerHost, Access, Room, HistorySize, + p1_fsm:start_link(?MODULE, [Host, ServerHost, Access, Room, HistorySize, RoomShaper, Creator, Nick, DefRoomOpts, QueueType], ?FSMOPTS). start_link(Host, ServerHost, Access, Room, HistorySize, RoomShaper, Opts, QueueType) -> - gen_fsm:start_link(?MODULE, [Host, ServerHost, Access, Room, HistorySize, + p1_fsm:start_link(?MODULE, [Host, ServerHost, Access, Room, HistorySize, RoomShaper, Opts, QueueType], ?FSMOPTS). @@ -703,7 +703,7 @@ terminate(Reason, _StateName, StateData) -> -spec route(pid(), stanza()) -> ok. route(Pid, Packet) -> #jid{lresource = Nick} = xmpp:get_to(Packet), - gen_fsm:send_event(Pid, {route, Nick, Packet}). + p1_fsm:send_event(Pid, {route, Nick, Packet}). -spec process_groupchat_message(message(), state()) -> fsm_next(). process_groupchat_message(#message{from = From, lang = Lang} = Packet, StateData) -> diff --git a/src/mod_proxy65_stream.erl b/src/mod_proxy65_stream.erl index 899b66727..1fa6ff804 100644 --- a/src/mod_proxy65_stream.erl +++ b/src/mod_proxy65_stream.erl @@ -26,7 +26,7 @@ -author('xram@jabber.ru'). --behaviour(gen_fsm). +-behaviour(p1_fsm). %% gen_fsm callbacks. -export([init/1, handle_event/3, handle_sync_event/4, @@ -75,7 +75,7 @@ start({gen_tcp, Socket}, Opts1) -> [Socket, Host, Opts]). start_link(Socket, Host, Opts) -> - gen_fsm:start_link(?MODULE, [Socket, Host, Opts], []). + p1_fsm:start_link(?MODULE, [Socket, Host, Opts], []). init([Socket, Host, Opts]) -> process_flag(trap_exit, true), @@ -106,9 +106,9 @@ socket_type() -> raw. stop(StreamPid) -> StreamPid ! stop. activate({P1, J1}, {P2, J2}) -> - case catch {gen_fsm:sync_send_all_state_event(P1, + case catch {p1_fsm:sync_send_all_state_event(P1, get_socket), - gen_fsm:sync_send_all_state_event(P2, get_socket)} + p1_fsm:sync_send_all_state_event(P2, get_socket)} of {S1, S2} when is_port(S1), is_port(S2) -> P1 ! {activate, P2, S2, J1, J2}, @@ -197,7 +197,7 @@ handle_info({tcp, _S, Data}, StateName, StateData) when StateName /= wait_for_activation -> erlang:cancel_timer(StateData#state.timer), TRef = erlang:send_after(?WAIT_TIMEOUT, self(), stop), - gen_fsm:send_event(self(), Data), + p1_fsm:send_event(self(), Data), {next_state, StateName, StateData#state{timer = TRef}}; %% Activation message. handle_info({activate, PeerPid, PeerSocket, IJid, TJid}, diff --git a/src/mod_sip_proxy.erl b/src/mod_sip_proxy.erl index 19a02e8e4..25f035377 100644 --- a/src/mod_sip_proxy.erl +++ b/src/mod_sip_proxy.erl @@ -27,8 +27,7 @@ -ifndef(SIP). -export([]). -else. --define(GEN_FSM, p1_fsm). --behaviour(?GEN_FSM). +-behaviour(p1_fsm). %% API -export([start/2, start_link/2, route/3, route/4]). @@ -58,10 +57,10 @@ start(LServer, Opts) -> supervisor:start_child(mod_sip_proxy_sup, [LServer, Opts]). start_link(LServer, Opts) -> - ?GEN_FSM:start_link(?MODULE, [LServer, Opts], []). + p1_fsm:start_link(?MODULE, [LServer, Opts], []). route(SIPMsg, _SIPSock, TrID, Pid) -> - ?GEN_FSM:send_event(Pid, {SIPMsg, TrID}). + p1_fsm:send_event(Pid, {SIPMsg, TrID}). route(#sip{hdrs = Hdrs} = Req, LServer, Opts) -> case proplists:get_bool(authenticated, Opts) of From 2bceebc39de3674ad063d66d8f051b49d42c3fa1 Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Sat, 5 Aug 2017 21:01:29 +0300 Subject: [PATCH 35/64] Get rid of export_all --- src/ejabberd_regexp.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ejabberd_regexp.erl b/src/ejabberd_regexp.erl index b4ef7ac16..10f51aa47 100644 --- a/src/ejabberd_regexp.erl +++ b/src/ejabberd_regexp.erl @@ -25,7 +25,7 @@ -module(ejabberd_regexp). --compile([export_all]). +-export([exec/2, run/2, split/2, replace/3, greplace/3, sh_to_awk/1]). exec({ReM, ReF, ReA}, {RgM, RgF, RgA}) -> try apply(ReM, ReF, ReA) catch From 25af3fb029c28fafb67ed53c38216c4fb626c519 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C3=85=C2=82=20Chmielowski?= Date: Mon, 7 Aug 2017 09:32:41 +0200 Subject: [PATCH 36/64] Compile mod_push early as it defines behaviour --- rebar.config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rebar.config b/rebar.config index 160185511..b8a94611a 100644 --- a/rebar.config +++ b/rebar.config @@ -76,7 +76,7 @@ ezlib, iconv]}}. -{erl_first_files, ["src/ejabberd_config.erl", "src/gen_mod.erl", "src/mod_muc_room.erl"]}. +{erl_first_files, ["src/ejabberd_config.erl", "src/gen_mod.erl", "src/mod_muc_room.erl", "src/mod_push.erl"]}. {erl_opts, [nowarn_deprecated_function, {i, "include"}, From 8679cfd2f3a84d2c29b11206d864a946c7e0d9d9 Mon Sep 17 00:00:00 2001 From: Christophe Romain Date: Mon, 7 Aug 2017 15:06:07 +0200 Subject: [PATCH 37/64] Rename stop_all_connections to stop_s2s_connections for consistency --- src/ejabberd_s2s.erl | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/ejabberd_s2s.erl b/src/ejabberd_s2s.erl index c7f9b6684..a614d8c4a 100644 --- a/src/ejabberd_s2s.erl +++ b/src/ejabberd_s2s.erl @@ -39,7 +39,7 @@ remove_connection/2, start_connection/2, start_connection/3, dirty_get_connections/0, allow_host/2, incoming_s2s_number/0, outgoing_s2s_number/0, - stop_all_connections/0, + stop_s2s_connections/0, clean_temporarily_blocked_table/0, list_temporarily_blocked_hosts/0, external_host_overloaded/1, is_temporarly_blocked/1, @@ -558,10 +558,10 @@ get_commands_spec() -> module = ?MODULE, function = outgoing_s2s_number, args = [], result = {s2s_outgoing, integer}}, #ejabberd_commands{ - name = stop_all_connections, tags = [s2s], - desc = "Stop all outgoing and incoming connections", + name = stop_s2s_connections, tags = [s2s], + desc = "Stop all s2s outgoing and incoming connections", policy = admin, - module = ?MODULE, function = stop_all_connections, + module = ?MODULE, function = stop_s2s_connections, args = [], result = {res, rescode}}]. %% TODO Move those stats commands to ejabberd stats command ? @@ -578,8 +578,8 @@ supervisor_count(Supervisor) -> length(Result) end. --spec stop_all_connections() -> ok. -stop_all_connections() -> +-spec stop_s2s_connections() -> ok. +stop_s2s_connections() -> lists:foreach( fun({_Id, Pid, _Type, _Module}) -> supervisor:terminate_child(ejabberd_s2s_in_sup, Pid) From 52525eb76d6d3b3f2d5f1fa9ed458cb808e53fe8 Mon Sep 17 00:00:00 2001 From: Christophe Romain Date: Mon, 7 Aug 2017 15:38:17 +0200 Subject: [PATCH 38/64] Fix tests for 8679cfd2f --- test/ejabberd_SUITE.erl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/ejabberd_SUITE.erl b/test/ejabberd_SUITE.erl index 539d8dc33..97c56159a 100644 --- a/test/ejabberd_SUITE.erl +++ b/test/ejabberd_SUITE.erl @@ -750,25 +750,25 @@ test_component_send(Config) -> disconnect(Config). s2s_dialback(Config) -> - ejabberd_s2s:stop_all_connections(), + ejabberd_s2s:stop_s2s_connections(), ejabberd_config:add_option(s2s_use_starttls, false), ejabberd_config:add_option(domain_certfile, "self-signed-cert.pem"), s2s_ping(Config). s2s_optional(Config) -> - ejabberd_s2s:stop_all_connections(), + ejabberd_s2s:stop_s2s_connections(), ejabberd_config:add_option(s2s_use_starttls, optional), ejabberd_config:add_option(domain_certfile, "self-signed-cert.pem"), s2s_ping(Config). s2s_required(Config) -> - ejabberd_s2s:stop_all_connections(), + ejabberd_s2s:stop_s2s_connections(), ejabberd_config:add_option(s2s_use_starttls, required), ejabberd_config:add_option(domain_certfile, "self-signed-cert.pem"), s2s_ping(Config). s2s_required_trusted(Config) -> - ejabberd_s2s:stop_all_connections(), + ejabberd_s2s:stop_s2s_connections(), ejabberd_config:add_option(s2s_use_starttls, required), ejabberd_config:add_option(domain_certfile, "cert.pem"), s2s_ping(Config). From 3fec782494042ab1e1a015524e01cd63e6757c7e Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Tue, 8 Aug 2017 17:46:26 +0300 Subject: [PATCH 39/64] Introduce 'hosts' option The option can be used as a replacement of 'host' option when several (sub)domains are needed to be registered for the module. Note that you cannot combine both 'host' and 'hosts' in the config because 'host' option is of a higher priority. Example: mod_pubsub: ... hosts: - "pubsub1.@HOST@" - "pubsub2.@HOST@" Fixes #1883 --- src/gen_mod.erl | 17 ++++- src/mod_echo.erl | 41 ++++++----- src/mod_http_upload.erl | 20 +++-- src/mod_irc.erl | 62 ++++++++++------ src/mod_mix.erl | 103 ++++++++++++++------------ src/mod_muc.erl | 81 +++++++++++++-------- src/mod_muc_mnesia.erl | 7 +- src/mod_proxy65.erl | 4 +- src/mod_proxy65_service.erl | 95 ++++++++++++++---------- src/mod_pubsub.erl | 141 +++++++++++++++++++----------------- src/mod_vcard.erl | 82 +++++++++++---------- src/mod_vcard_ldap.erl | 7 +- 12 files changed, 380 insertions(+), 280 deletions(-) diff --git a/src/gen_mod.erl b/src/gen_mod.erl index 5bfa3b4d4..e17197dfb 100644 --- a/src/gen_mod.erl +++ b/src/gen_mod.erl @@ -34,7 +34,8 @@ stop_child/1, stop_child/2, config_reloaded/0]). -export([start_module/2, start_module/3, stop_module/2, stop_module_keep_config/2, - get_opt/2, get_opt/3, get_opt_host/3, opt_type/1, is_equal_opt/4, + get_opt/2, get_opt/3, get_opt_host/3, + get_opt_hosts/3, opt_type/1, is_equal_opt/4, get_module_opt/3, get_module_opt/4, get_module_opt_host/3, loaded_modules/1, loaded_modules_with_opts/1, get_hosts/2, get_module_proc/2, is_loaded/2, is_loaded_elsewhere/2, @@ -441,6 +442,20 @@ get_opt_host(Host, Opts, Default) -> Val = get_opt(host, Opts, Default), ejabberd_regexp:greplace(Val, <<"@HOST@">>, Host). +-spec get_opt_hosts(binary(), opts(), binary()) -> [binary()]. + +get_opt_hosts(Host, Opts, Default) -> + Vals = case get_opt(host, Opts, undefined) of + undefined -> + case get_opt(hosts, Opts, []) of + [] -> [Default]; + L -> L + end; + Val -> + [Val] + end, + [ejabberd_regexp:greplace(V, <<"@HOST@">>, Host) || V <- Vals]. + -spec get_validators(binary(), module(), opts()) -> dict:dict() | undef. get_validators(Host, Module, Opts) -> try Module:mod_opt_type('') of diff --git a/src/mod_echo.erl b/src/mod_echo.erl index 861b1a0ef..79dd59962 100644 --- a/src/mod_echo.erl +++ b/src/mod_echo.erl @@ -43,7 +43,7 @@ -include("xmpp.hrl"). --record(state, {host = <<"">> :: binary()}). +-record(state, {hosts = [] :: [binary()]}). %%==================================================================== %% gen_mod API @@ -62,7 +62,9 @@ depends(_Host, _Opts) -> []. mod_opt_type(host) -> fun iolist_to_binary/1; -mod_opt_type(_) -> [host]. +mod_opt_type(hosts) -> + fun(L) -> lists:map(fun iolist_to_binary/1, L) end; +mod_opt_type(_) -> [host, hosts]. %%==================================================================== %% gen_server callbacks @@ -77,10 +79,13 @@ mod_opt_type(_) -> [host]. %%-------------------------------------------------------------------- init([Host, Opts]) -> process_flag(trap_exit, true), - MyHost = gen_mod:get_opt_host(Host, Opts, + Hosts = gen_mod:get_opt_hosts(Host, Opts, <<"echo.@HOST@">>), - ejabberd_router:register_route(MyHost, Host), - {ok, #state{host = MyHost}}. + lists:foreach( + fun(H) -> + ejabberd_router:register_route(H, Host) + end, Hosts), + {ok, #state{hosts = Hosts}}. %%-------------------------------------------------------------------- %% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} | @@ -101,17 +106,19 @@ handle_call(stop, _From, State) -> %% Description: Handling cast messages %%-------------------------------------------------------------------- handle_cast({reload, Host, NewOpts, OldOpts}, State) -> - NewMyHost = gen_mod:get_opt_host(Host, NewOpts, - <<"echo.@HOST@">>), - OldMyHost = gen_mod:get_opt_host(Host, OldOpts, - <<"echo.@HOST@">>), - if NewMyHost /= OldMyHost -> - ejabberd_router:register_route(NewMyHost, Host), - ejabberd_router:unregister_route(OldMyHost); - true -> - ok - end, - {noreply, State#state{host = NewMyHost}}; + NewMyHosts = gen_mod:get_opt_hosts(Host, NewOpts, + <<"echo.@HOST@">>), + OldMyHosts = gen_mod:get_opt_hosts(Host, OldOpts, + <<"echo.@HOST@">>), + lists:foreach( + fun(H) -> + ejabberd_router:unregister_route(H) + end, OldMyHosts -- NewMyHosts), + lists:foreach( + fun(H) -> + ejabberd_router:register_route(H, Host) + end, NewMyHosts -- OldMyHosts), + {noreply, State#state{hosts = NewMyHosts}}; handle_cast(Msg, State) -> ?WARNING_MSG("unexpected cast: ~p", [Msg]), {noreply, State}. @@ -147,7 +154,7 @@ handle_info(_Info, State) -> {noreply, State}. %% The return value is ignored. %%-------------------------------------------------------------------- terminate(_Reason, State) -> - ejabberd_router:unregister_route(State#state.host), ok. + lists:foreach(fun ejabberd_router:unregister_route/1, State#state.hosts). %%-------------------------------------------------------------------- %% Func: code_change(OldVsn, State, Extra) -> {ok, NewState} diff --git a/src/mod_http_upload.erl b/src/mod_http_upload.erl index c8cd300f4..6d981e9ec 100644 --- a/src/mod_http_upload.erl +++ b/src/mod_http_upload.erl @@ -94,7 +94,7 @@ -record(state, {server_host :: binary(), - host :: binary(), + hosts :: [binary()], name :: binary(), access :: atom(), max_size :: pos_integer() | infinity, @@ -151,6 +151,8 @@ stop(ServerHost) -> mod_opt_type(host) -> fun iolist_to_binary/1; +mod_opt_type(hosts) -> + fun (L) -> lists:map(fun iolist_to_binary/1, L) end; mod_opt_type(name) -> fun iolist_to_binary/1; mod_opt_type(access) -> @@ -194,7 +196,7 @@ mod_opt_type(rm_on_unregister) -> mod_opt_type(thumbnail) -> fun(B) when is_boolean(B) -> B end; mod_opt_type(_) -> - [host, name, access, max_size, secret_length, jid_in_url, file_mode, + [host, hosts, name, access, max_size, secret_length, jid_in_url, file_mode, dir_mode, docroot, put_url, get_url, service_url, custom_headers, rm_on_unregister, thumbnail]. @@ -211,7 +213,7 @@ depends(_Host, _Opts) -> init([ServerHost, Opts]) -> process_flag(trap_exit, true), - Host = gen_mod:get_opt_host(ServerHost, Opts, <<"upload.@HOST@">>), + Hosts = gen_mod:get_opt_hosts(ServerHost, Opts, <<"upload.@HOST@">>), Name = gen_mod:get_opt(name, Opts, <<"HTTP File Upload">>), Access = gen_mod:get_opt(access, Opts, local), MaxSize = gen_mod:get_opt(max_size, Opts, 104857600), @@ -244,8 +246,11 @@ init([ServerHost, Opts]) -> false -> ok end, - ejabberd_router:register_route(Host, ServerHost), - {ok, #state{server_host = ServerHost, host = Host, name = Name, + lists:foreach( + fun(Host) -> + ejabberd_router:register_route(Host, ServerHost) + end, Hosts), + {ok, #state{server_host = ServerHost, hosts = Hosts, name = Name, access = Access, max_size = MaxSize, secret_length = SecretLength, jid_in_url = JIDinURL, file_mode = FileMode, dir_mode = DirMode, @@ -324,10 +329,9 @@ handle_info(Info, State) -> -spec terminate(normal | shutdown | {shutdown, _} | _, state()) -> ok. -terminate(Reason, #state{server_host = ServerHost, host = Host}) -> +terminate(Reason, #state{server_host = ServerHost, hosts = Hosts}) -> ?DEBUG("Stopping HTTP upload process for ~s: ~p", [ServerHost, Reason]), - ejabberd_router:unregister_route(Host), - ok. + lists:foreach(fun ejabberd_router:unregister_route/1, Hosts). -spec code_change({down, _} | _, state(), _) -> {ok, state()}. diff --git a/src/mod_irc.erl b/src/mod_irc.erl index fc85668e8..04687ea67 100644 --- a/src/mod_irc.erl +++ b/src/mod_irc.erl @@ -58,7 +58,7 @@ [<<"koi8-r">>, <<"iso8859-15">>, <<"iso8859-1">>, <<"iso8859-2">>, <<"utf-8">>, <<"utf-8+latin-1">>]). --record(state, {host = <<"">> :: binary(), +-record(state, {hosts = [] :: [binary()], server_host = <<"">> :: binary(), access = all :: atom()}). @@ -99,8 +99,7 @@ depends(_Host, _Opts) -> init([Host, Opts]) -> process_flag(trap_exit, true), ejabberd:start_app(iconv), - MyHost = gen_mod:get_opt_host(Host, Opts, - <<"irc.@HOST@">>), + MyHosts = gen_mod:get_opt_hosts(Host, Opts, <<"irc.@HOST@">>), Mod = gen_mod:db_mod(Host, Opts, ?MODULE), Mod:init(Host, Opts), Access = gen_mod:get_opt(access, Opts, all), @@ -108,10 +107,13 @@ init([Host, Opts]) -> [named_table, public, {keypos, #irc_connection.jid_server_host}]), IQDisc = gen_mod:get_opt(iqdisc, Opts, gen_iq_handler:iqdisc(Host)), - register_hooks(MyHost, IQDisc), - ejabberd_router:register_route(MyHost, Host), + lists:foreach( + fun(MyHost) -> + register_hooks(MyHost, IQDisc), + ejabberd_router:register_route(MyHost, Host) + end, MyHosts), {ok, - #state{host = MyHost, server_host = Host, + #state{hosts = MyHosts, server_host = Host, access = Access}}. %%-------------------------------------------------------------------- @@ -133,8 +135,8 @@ handle_call(stop, _From, State) -> %% Description: Handling cast messages %%-------------------------------------------------------------------- handle_cast({reload, ServerHost, NewOpts, OldOpts}, State) -> - NewHost = gen_mod:get_opt_host(ServerHost, NewOpts, <<"irc.@HOST@">>), - OldHost = gen_mod:get_opt_host(ServerHost, OldOpts, <<"irc.@HOST@">>), + NewHosts = gen_mod:get_opt_hosts(ServerHost, NewOpts, <<"irc.@HOST@">>), + OldHosts = gen_mod:get_opt_hosts(ServerHost, OldOpts, <<"irc.@HOST@">>), NewIQDisc = gen_mod:get_opt(iqdisc, NewOpts, gen_iq_handler:iqdisc(ServerHost)), OldIQDisc = gen_mod:get_opt(iqdisc, OldOpts, gen_iq_handler:iqdisc(ServerHost)), NewMod = gen_mod:db_mod(ServerHost, NewOpts, ?MODULE), @@ -145,20 +147,26 @@ handle_cast({reload, ServerHost, NewOpts, OldOpts}, State) -> true -> ok end, - if (NewIQDisc /= OldIQDisc) or (NewHost /= OldHost) -> - register_hooks(NewHost, NewIQDisc); - true -> - ok - end, - if NewHost /= OldHost -> - ejabberd_router:register_route(NewHost, ServerHost), - ejabberd_router:unregister_route(OldHost), - unregister_hooks(OldHost); + if (NewIQDisc /= OldIQDisc) -> + lists:foreach( + fun(NewHost) -> + register_hooks(NewHost, NewIQDisc) + end, NewHosts -- (NewHosts -- OldHosts)); true -> ok end, + lists:foreach( + fun(NewHost) -> + ejabberd_router:register_route(NewHost, ServerHost), + register_hooks(NewHost, NewIQDisc) + end, NewHosts -- OldHosts), + lists:foreach( + fun(OldHost) -> + ejabberd_router:unregister_route(OldHost), + unregister_hooks(OldHost) + end, OldHosts -- NewHosts), Access = gen_mod:get_opt(access, NewOpts, all), - {noreply, State#state{host = NewHost, access = Access}}; + {noreply, State#state{hosts = NewHosts, access = Access}}; handle_cast(Msg, State) -> ?WARNING_MSG("unexpected cast: ~p", [Msg]), {noreply, State}. @@ -170,9 +178,10 @@ handle_cast(Msg, State) -> %% Description: Handling all non call/cast messages %%-------------------------------------------------------------------- handle_info({route, Packet}, - #state{host = Host, server_host = ServerHost, - access = Access} = + #state{server_host = ServerHost, access = Access} = State) -> + To = xmpp:get_to(Packet), + Host = To#jid.lserver, case catch do_route(Host, ServerHost, Access, Packet) of {'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]); _ -> ok @@ -187,9 +196,12 @@ handle_info(_Info, State) -> {noreply, State}. %% cleaning up. When it returns, the gen_server terminates with Reason. %% The return value is ignored. %%-------------------------------------------------------------------- -terminate(_Reason, #state{host = MyHost}) -> - ejabberd_router:unregister_route(MyHost), - unregister_hooks(MyHost). +terminate(_Reason, #state{hosts = MyHosts}) -> + lists:foreach( + fun(MyHost) -> + ejabberd_router:unregister_route(MyHost), + unregister_hooks(MyHost) + end, MyHosts). %%-------------------------------------------------------------------- %% Func: code_change(OldVsn, State, Extra) -> {ok, NewState} @@ -975,8 +987,10 @@ mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end; mod_opt_type(default_encoding) -> fun iolist_to_binary/1; mod_opt_type(host) -> fun iolist_to_binary/1; +mod_opt_type(hosts) -> + fun (L) -> lists:map(fun iolist_to_binary/1, L) end; mod_opt_type(_) -> - [access, db_type, default_encoding, host]. + [access, db_type, default_encoding, host, hosts]. -spec extract_ident(stanza()) -> binary(). extract_ident(Packet) -> diff --git a/src/mod_mix.erl b/src/mod_mix.erl index 4763447f3..90507665b 100644 --- a/src/mod_mix.erl +++ b/src/mod_mix.erl @@ -46,7 +46,7 @@ ?NS_MIX_NODES_CONFIG]). -record(state, {server_host :: binary(), - host :: binary()}). + hosts :: [binary()]}). %%%=================================================================== %%% API @@ -124,36 +124,39 @@ process_iq(#iq{lang = Lang} = IQ) -> %%%=================================================================== init([ServerHost, Opts]) -> process_flag(trap_exit, true), - Host = gen_mod:get_opt_host(ServerHost, Opts, <<"mix.@HOST@">>), - IQDisc = gen_mod:get_opt(iqdisc, Opts, gen_iq_handler:iqdisc(Host)), - ConfigTab = gen_mod:get_module_proc(Host, config), - ets:new(ConfigTab, [named_table]), - ets:insert(ConfigTab, {plugins, [<<"mix">>]}), - ejabberd_hooks:add(disco_local_items, Host, ?MODULE, disco_items, 100), - ejabberd_hooks:add(disco_local_features, Host, ?MODULE, disco_features, 100), - ejabberd_hooks:add(disco_local_identity, Host, ?MODULE, disco_identity, 100), - ejabberd_hooks:add(disco_sm_items, Host, ?MODULE, disco_items, 100), - ejabberd_hooks:add(disco_sm_features, Host, ?MODULE, disco_features, 100), - ejabberd_hooks:add(disco_sm_identity, Host, ?MODULE, disco_identity, 100), - ejabberd_hooks:add(disco_info, Host, ?MODULE, disco_info, 100), - gen_iq_handler:add_iq_handler(ejabberd_local, Host, - ?NS_DISCO_ITEMS, mod_disco, - process_local_iq_items, IQDisc), - gen_iq_handler:add_iq_handler(ejabberd_local, Host, - ?NS_DISCO_INFO, mod_disco, - process_local_iq_info, IQDisc), - gen_iq_handler:add_iq_handler(ejabberd_sm, Host, - ?NS_DISCO_ITEMS, mod_disco, - process_local_iq_items, IQDisc), - gen_iq_handler:add_iq_handler(ejabberd_sm, Host, - ?NS_DISCO_INFO, mod_disco, - process_local_iq_info, IQDisc), - gen_iq_handler:add_iq_handler(ejabberd_sm, Host, - ?NS_PUBSUB, mod_pubsub, iq_sm, IQDisc), - gen_iq_handler:add_iq_handler(ejabberd_sm, Host, - ?NS_MIX_0, ?MODULE, process_iq, IQDisc), - ejabberd_router:register_route(Host, ServerHost), - {ok, #state{server_host = ServerHost, host = Host}}. + Hosts = gen_mod:get_opt_hosts(ServerHost, Opts, <<"mix.@HOST@">>), + IQDisc = gen_mod:get_opt(iqdisc, Opts, gen_iq_handler:iqdisc(ServerHost)), + lists:foreach( + fun(Host) -> + ConfigTab = gen_mod:get_module_proc(Host, config), + ets:new(ConfigTab, [named_table]), + ets:insert(ConfigTab, {plugins, [<<"mix">>]}), + ejabberd_hooks:add(disco_local_items, Host, ?MODULE, disco_items, 100), + ejabberd_hooks:add(disco_local_features, Host, ?MODULE, disco_features, 100), + ejabberd_hooks:add(disco_local_identity, Host, ?MODULE, disco_identity, 100), + ejabberd_hooks:add(disco_sm_items, Host, ?MODULE, disco_items, 100), + ejabberd_hooks:add(disco_sm_features, Host, ?MODULE, disco_features, 100), + ejabberd_hooks:add(disco_sm_identity, Host, ?MODULE, disco_identity, 100), + ejabberd_hooks:add(disco_info, Host, ?MODULE, disco_info, 100), + gen_iq_handler:add_iq_handler(ejabberd_local, Host, + ?NS_DISCO_ITEMS, mod_disco, + process_local_iq_items, IQDisc), + gen_iq_handler:add_iq_handler(ejabberd_local, Host, + ?NS_DISCO_INFO, mod_disco, + process_local_iq_info, IQDisc), + gen_iq_handler:add_iq_handler(ejabberd_sm, Host, + ?NS_DISCO_ITEMS, mod_disco, + process_local_iq_items, IQDisc), + gen_iq_handler:add_iq_handler(ejabberd_sm, Host, + ?NS_DISCO_INFO, mod_disco, + process_local_iq_info, IQDisc), + gen_iq_handler:add_iq_handler(ejabberd_sm, Host, + ?NS_PUBSUB, mod_pubsub, iq_sm, IQDisc), + gen_iq_handler:add_iq_handler(ejabberd_sm, Host, + ?NS_MIX_0, ?MODULE, process_iq, IQDisc), + ejabberd_router:register_route(Host, ServerHost) + end, Hosts), + {ok, #state{server_host = ServerHost, hosts = Hosts}}. handle_call(_Request, _From, State) -> Reply = ok, @@ -180,22 +183,24 @@ handle_info({route, Packet}, State) -> handle_info(_Info, State) -> {noreply, State}. -terminate(_Reason, #state{host = Host}) -> - ejabberd_hooks:delete(disco_local_items, Host, ?MODULE, disco_items, 100), - ejabberd_hooks:delete(disco_local_features, Host, ?MODULE, disco_features, 100), - ejabberd_hooks:delete(disco_local_identity, Host, ?MODULE, disco_identity, 100), - ejabberd_hooks:delete(disco_sm_items, Host, ?MODULE, disco_items, 100), - ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE, disco_features, 100), - ejabberd_hooks:delete(disco_sm_identity, Host, ?MODULE, disco_identity, 100), - ejabberd_hooks:delete(disco_info, Host, ?MODULE, disco_info, 100), - gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS), - gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO), - gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_DISCO_ITEMS), - gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_DISCO_INFO), - gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_PUBSUB), - gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_MIX_0), - ejabberd_router:unregister_route(Host), - ok. +terminate(_Reason, #state{hosts = Hosts}) -> + lists:foreach( + fun(Host) -> + ejabberd_hooks:delete(disco_local_items, Host, ?MODULE, disco_items, 100), + ejabberd_hooks:delete(disco_local_features, Host, ?MODULE, disco_features, 100), + ejabberd_hooks:delete(disco_local_identity, Host, ?MODULE, disco_identity, 100), + ejabberd_hooks:delete(disco_sm_items, Host, ?MODULE, disco_items, 100), + ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE, disco_features, 100), + ejabberd_hooks:delete(disco_sm_identity, Host, ?MODULE, disco_identity, 100), + ejabberd_hooks:delete(disco_info, Host, ?MODULE, disco_info, 100), + gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS), + gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO), + gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_DISCO_ITEMS), + gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_DISCO_INFO), + gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_PUBSUB), + gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_MIX_0), + ejabberd_router:unregister_route(Host) + end, Hosts). code_change(_OldVsn, State, _Extra) -> {ok, State}. @@ -316,4 +321,6 @@ depends(_Host, _Opts) -> mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1; mod_opt_type(host) -> fun iolist_to_binary/1; -mod_opt_type(_) -> [host, iqdisc]. +mod_opt_type(hosts) -> + fun (L) -> lists:map(fun iolist_to_binary/1, L) end; +mod_opt_type(_) -> [host, hosts, iqdisc]. diff --git a/src/mod_muc.erl b/src/mod_muc.erl index 2d87dd1d5..db101e4f6 100644 --- a/src/mod_muc.erl +++ b/src/mod_muc.erl @@ -75,7 +75,7 @@ -include("mod_muc.hrl"). -record(state, - {host = <<"">> :: binary(), + {hosts = [] :: [binary()], server_host = <<"">> :: binary(), access = {none, none, none, none} :: {atom(), atom(), atom(), atom()}, history_size = 20 :: non_neg_integer(), @@ -151,8 +151,9 @@ room_destroyed(Host, Room, Pid, ServerHost) -> %% If Opts = default, the default room options are used. %% Else use the passed options as defined in mod_muc_room. create_room(Host, Name, From, Nick, Opts) -> - Proc = gen_mod:get_module_proc(Host, ?MODULE), - gen_server:call(Proc, {create, Name, From, Nick, Opts}). + ServerHost = ejabberd_router:host_of_route(Host), + Proc = gen_mod:get_module_proc(ServerHost, ?MODULE), + gen_server:call(Proc, {create, Name, Host, From, Nick, Opts}). store_room(ServerHost, Host, Name, Opts) -> LServer = jid:nameprep(ServerHost), @@ -225,22 +226,26 @@ get_online_rooms_by_user(ServerHost, LUser, LServer) -> init([Host, Opts]) -> process_flag(trap_exit, true), IQDisc = gen_mod:get_opt(iqdisc, Opts, gen_iq_handler:iqdisc(Host)), - #state{access = Access, host = MyHost, + #state{access = Access, hosts = MyHosts, history_size = HistorySize, queue_type = QueueType, room_shaper = RoomShaper} = State = init_state(Host, Opts), Mod = gen_mod:db_mod(Host, Opts, ?MODULE), RMod = gen_mod:ram_db_mod(Host, Opts, ?MODULE), - Mod:init(Host, [{host, MyHost}|Opts]), - RMod:init(Host, [{host, MyHost}|Opts]), - register_iq_handlers(MyHost, IQDisc), - ejabberd_router:register_route(MyHost, Host), - load_permanent_rooms(MyHost, Host, Access, HistorySize, RoomShaper, QueueType), + Mod:init(Host, [{hosts, MyHosts}|Opts]), + RMod:init(Host, [{hosts, MyHosts}|Opts]), + lists:foreach( + fun(MyHost) -> + register_iq_handlers(MyHost, IQDisc), + ejabberd_router:register_route(MyHost, Host), + load_permanent_rooms(MyHost, Host, Access, HistorySize, + RoomShaper, QueueType) + end, MyHosts), {ok, State}. handle_call(stop, _From, State) -> {stop, normal, ok, State}; -handle_call({create, Room, From, Nick, Opts}, _From, - #state{host = Host, server_host = ServerHost, +handle_call({create, Room, Host, From, Nick, Opts}, _From, + #state{server_host = ServerHost, access = Access, default_room_opts = DefOpts, history_size = HistorySize, queue_type = QueueType, room_shaper = RoomShaper} = State) -> @@ -259,49 +264,56 @@ handle_call({create, Room, From, Nick, Opts}, _From, ejabberd_hooks:run(create_room, ServerHost, [ServerHost, Room, Host]), {reply, ok, State}. -handle_cast({reload, ServerHost, NewOpts, OldOpts}, #state{host = OldHost}) -> +handle_cast({reload, ServerHost, NewOpts, OldOpts}, #state{hosts = OldHosts}) -> NewIQDisc = gen_mod:get_opt(iqdisc, NewOpts, gen_iq_handler:iqdisc(ServerHost)), OldIQDisc = gen_mod:get_opt(iqdisc, OldOpts, gen_iq_handler:iqdisc(ServerHost)), NewMod = gen_mod:db_mod(ServerHost, NewOpts, ?MODULE), NewRMod = gen_mod:ram_db_mod(ServerHost, NewOpts, ?MODULE), OldMod = gen_mod:db_mod(ServerHost, OldOpts, ?MODULE), OldRMod = gen_mod:ram_db_mod(ServerHost, OldOpts, ?MODULE), - #state{host = NewHost} = NewState = init_state(ServerHost, NewOpts), + #state{hosts = NewHosts} = NewState = init_state(ServerHost, NewOpts), if NewMod /= OldMod -> - NewMod:init(ServerHost, [{host, NewHost}|NewOpts]); + NewMod:init(ServerHost, [{hosts, NewHosts}|NewOpts]); true -> ok end, if NewRMod /= OldRMod -> - NewRMod:init(ServerHost, [{host, NewHost}|NewOpts]); + NewRMod:init(ServerHost, [{hosts, NewHosts}|NewOpts]); true -> ok end, - if (NewIQDisc /= OldIQDisc) or (NewHost /= OldHost) -> - register_iq_handlers(NewHost, NewIQDisc); - true -> - ok - end, - if NewHost /= OldHost -> - ejabberd_router:register_route(NewHost, ServerHost), - ejabberd_router:unregister_route(OldHost), - unregister_iq_handlers(OldHost); + if (NewIQDisc /= OldIQDisc) -> + lists:foreach( + fun(NewHost) -> + register_iq_handlers(NewHost, NewIQDisc) + end, NewHosts -- (NewHosts -- OldHosts)); true -> ok end, + lists:foreach( + fun(NewHost) -> + ejabberd_router:register_route(NewHost, ServerHost), + register_iq_handlers(NewHost, NewIQDisc) + end, NewHosts -- OldHosts), + lists:foreach( + fun(OldHost) -> + ejabberd_router:unregister_route(OldHost), + unregister_iq_handlers(OldHost) + end, OldHosts -- NewHosts), {noreply, NewState}; handle_cast(Msg, State) -> ?WARNING_MSG("unexpected cast: ~p", [Msg]), {noreply, State}. handle_info({route, Packet}, - #state{host = Host, server_host = ServerHost, + #state{server_host = ServerHost, access = Access, default_room_opts = DefRoomOpts, history_size = HistorySize, queue_type = QueueType, max_rooms_discoitems = MaxRoomsDiscoItems, room_shaper = RoomShaper} = State) -> From = xmpp:get_from(Packet), To = xmpp:get_to(Packet), + Host = To#jid.lserver, case catch do_route(Host, ServerHost, Access, HistorySize, RoomShaper, From, To, Packet, DefRoomOpts, MaxRoomsDiscoItems, QueueType) of @@ -320,9 +332,12 @@ handle_info(Info, State) -> ?ERROR_MSG("unexpected info: ~p", [Info]), {noreply, State}. -terminate(_Reason, #state{host = MyHost}) -> - ejabberd_router:unregister_route(MyHost), - unregister_iq_handlers(MyHost). +terminate(_Reason, #state{hosts = MyHosts}) -> + lists:foreach( + fun(MyHost) -> + ejabberd_router:unregister_route(MyHost), + unregister_iq_handlers(MyHost) + end, MyHosts). code_change(_OldVsn, State, _Extra) -> {ok, State}. @@ -330,8 +345,8 @@ code_change(_OldVsn, State, _Extra) -> {ok, State}. %%% Internal functions %%-------------------------------------------------------------------- init_state(Host, Opts) -> - MyHost = gen_mod:get_opt_host(Host, Opts, - <<"conference.@HOST@">>), + MyHosts = gen_mod:get_opt_hosts(Host, Opts, + <<"conference.@HOST@">>), Access = gen_mod:get_opt(access, Opts, all), AccessCreate = gen_mod:get_opt(access_create, Opts, all), AccessAdmin = gen_mod:get_opt(access_admin, Opts, none), @@ -342,7 +357,7 @@ init_state(Host, Opts) -> QueueType = gen_mod:get_opt(queue_type, Opts, ejabberd_config:default_queue_type(Host)), RoomShaper = gen_mod:get_opt(room_shaper, Opts, none), - #state{host = MyHost, + #state{hosts = MyHosts, server_host = Host, access = {Access, AccessCreate, AccessAdmin, AccessPersistent}, default_room_opts = DefRoomOpts, @@ -851,6 +866,8 @@ mod_opt_type(ram_db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end; mod_opt_type(history_size) -> fun (I) when is_integer(I), I >= 0 -> I end; mod_opt_type(host) -> fun iolist_to_binary/1; +mod_opt_type(hosts) -> + fun (L) -> lists:map(fun iolist_to_binary/1, L) end; mod_opt_type(max_room_desc) -> fun (infinity) -> infinity; (I) when is_integer(I), I > 0 -> I @@ -944,7 +961,7 @@ mod_opt_type({default_room_options, presence_broadcast}) -> end; mod_opt_type(_) -> [access, access_admin, access_create, access_persistent, - db_type, ram_db_type, history_size, host, + db_type, ram_db_type, history_size, host, hosts, max_room_desc, max_room_id, max_room_name, max_rooms_discoitems, max_user_conferences, max_users, max_users_admin_threshold, max_users_presence, diff --git a/src/mod_muc_mnesia.erl b/src/mod_muc_mnesia.erl index 53f31cb9f..015c5ec43 100644 --- a/src/mod_muc_mnesia.erl +++ b/src/mod_muc_mnesia.erl @@ -296,7 +296,7 @@ import(_LServer, <<"muc_registered">>, %%% gen_server callbacks %%%=================================================================== init([Host, Opts]) -> - MyHost = proplists:get_value(host, Opts), + MyHosts = proplists:get_value(hosts, Opts), case gen_mod:db_mod(Host, Opts, mod_muc) of ?MODULE -> ejabberd_mnesia:create(?MODULE, muc_room, @@ -318,7 +318,10 @@ init([Host, Opts]) -> {type, ordered_set}, {attributes, record_info(fields, muc_online_room)}]), catch ets:new(muc_online_users, [bag, named_table, public, {keypos, 2}]), - clean_table_from_bad_node(node(), MyHost), + lists:foreach( + fun(MyHost) -> + clean_table_from_bad_node(node(), MyHost) + end, MyHosts), mnesia:subscribe(system); _ -> ok diff --git a/src/mod_proxy65.erl b/src/mod_proxy65.erl index aee324960..671baef9a 100644 --- a/src/mod_proxy65.erl +++ b/src/mod_proxy65.erl @@ -112,6 +112,8 @@ depends(_Host, _Opts) -> mod_opt_type(access) -> fun acl:access_rules_validator/1; mod_opt_type(host) -> fun iolist_to_binary/1; +mod_opt_type(hosts) -> + fun(L) -> lists:map(fun iolist_to_binary/1, L) end; mod_opt_type(hostname) -> fun iolist_to_binary/1; mod_opt_type(ip) -> fun (S) -> @@ -131,7 +133,7 @@ mod_opt_type(ram_db_type) -> mod_opt_type(Opt) -> case mod_proxy65_stream:listen_opt_type(Opt) of Opts when is_list(Opts) -> - [access, host, hostname, ip, name, port, + [access, host, hosts, hostname, ip, name, port, max_connections, ram_db_type] ++ Opts; Fun -> Fun diff --git a/src/mod_proxy65_service.erl b/src/mod_proxy65_service.erl index b27f3bc20..aaece980a 100644 --- a/src/mod_proxy65_service.erl +++ b/src/mod_proxy65_service.erl @@ -43,7 +43,7 @@ -define(PROCNAME, ejabberd_mod_proxy65_service). --record(state, {myhost = <<"">> :: binary()}). +-record(state, {myhosts = [] :: [binary()]}). %%%------------------------ %%% gen_server callbacks @@ -61,24 +61,27 @@ reload(Host, NewOpts, OldOpts) -> init([Host, Opts]) -> process_flag(trap_exit, true), IQDisc = gen_mod:get_opt(iqdisc, Opts, gen_iq_handler:iqdisc(Host)), - MyHost = gen_mod:get_opt_host(Host, Opts, <<"proxy.@HOST@">>), - gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_INFO, - ?MODULE, process_disco_info, IQDisc), - gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_ITEMS, - ?MODULE, process_disco_items, IQDisc), - gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_VCARD, - ?MODULE, process_vcard, IQDisc), - gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_BYTESTREAMS, - ?MODULE, process_bytestreams, IQDisc), - ejabberd_router:register_route(MyHost, Host), - {ok, #state{myhost = MyHost}}. + MyHosts = gen_mod:get_opt_hosts(Host, Opts, <<"proxy.@HOST@">>), + lists:foreach( + fun(MyHost) -> + gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_INFO, + ?MODULE, process_disco_info, IQDisc), + gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_ITEMS, + ?MODULE, process_disco_items, IQDisc), + gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_VCARD, + ?MODULE, process_vcard, IQDisc), + gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_BYTESTREAMS, + ?MODULE, process_bytestreams, IQDisc), + ejabberd_router:register_route(MyHost, Host) + end, MyHosts), + {ok, #state{myhosts = MyHosts}}. -terminate(_Reason, #state{myhost = MyHost}) -> - ejabberd_router:unregister_route(MyHost), - gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_INFO), - gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_ITEMS), - gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_VCARD), - gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_BYTESTREAMS). +terminate(_Reason, #state{myhosts = MyHosts}) -> + lists:foreach( + fun(MyHost) -> + ejabberd_router:unregister_route(MyHost), + unregister_handlers(MyHost) + end, MyHosts). handle_info({route, #iq{} = Packet}, State) -> ejabberd_router:process_iq(Packet), @@ -89,33 +92,29 @@ handle_call(_Request, _From, State) -> {reply, ok, State}. handle_cast({reload, ServerHost, NewOpts, OldOpts}, State) -> - NewHost = gen_mod:get_opt_host(ServerHost, NewOpts, <<"proxy.@HOST@">>), - OldHost = gen_mod:get_opt_host(ServerHost, OldOpts, <<"proxy.@HOST@">>), + NewHosts = gen_mod:get_opt_hosts(ServerHost, NewOpts, <<"proxy.@HOST@">>), + OldHosts = gen_mod:get_opt_hosts(ServerHost, OldOpts, <<"proxy.@HOST@">>), NewIQDisc = gen_mod:get_opt(iqdisc, NewOpts, gen_iq_handler:iqdisc(ServerHost)), OldIQDisc = gen_mod:get_opt(iqdisc, OldOpts, gen_iq_handler:iqdisc(ServerHost)), - if (NewIQDisc /= OldIQDisc) or (NewHost /= OldHost) -> - gen_iq_handler:add_iq_handler(ejabberd_local, NewHost, ?NS_DISCO_INFO, - ?MODULE, process_disco_info, NewIQDisc), - gen_iq_handler:add_iq_handler(ejabberd_local, NewHost, ?NS_DISCO_ITEMS, - ?MODULE, process_disco_items, NewIQDisc), - gen_iq_handler:add_iq_handler(ejabberd_local, NewHost, ?NS_VCARD, - ?MODULE, process_vcard, NewIQDisc), - gen_iq_handler:add_iq_handler(ejabberd_local, NewHost, ?NS_BYTESTREAMS, - ?MODULE, process_bytestreams, NewIQDisc); + if (NewIQDisc /= OldIQDisc) -> + lists:foreach( + fun(NewHost) -> + register_handlers(NewHost, NewIQDisc) + end, NewHosts -- (NewHosts -- OldHosts)); true -> ok end, - if NewHost /= OldHost -> - ejabberd_router:register_route(NewHost, ServerHost), - ejabberd_router:unregister_route(OldHost), - gen_iq_handler:remove_iq_handler(ejabberd_local, OldHost, ?NS_DISCO_INFO), - gen_iq_handler:remove_iq_handler(ejabberd_local, OldHost, ?NS_DISCO_ITEMS), - gen_iq_handler:remove_iq_handler(ejabberd_local, OldHost, ?NS_VCARD), - gen_iq_handler:remove_iq_handler(ejabberd_local, OldHost, ?NS_BYTESTREAMS); - true -> - ok - end, - {noreply, State#state{myhost = NewHost}}; + lists:foreach( + fun(NewHost) -> + ejabberd_router:register_route(NewHost, ServerHost), + register_handlers(NewHost, NewIQDisc) + end, NewHosts -- OldHosts), + lists:foreach( + fun(OldHost) -> + ejabberd_router:unregister_route(OldHost), + unregister_handlers(OldHost) + end, OldHosts -- NewHosts), + {noreply, State#state{myhosts = NewHosts}}; handle_cast(Msg, State) -> ?WARNING_MSG("unexpected cast: ~p", [Msg]), {noreply, State}. @@ -276,3 +275,19 @@ get_my_ip() -> max_connections(ServerHost) -> gen_mod:get_module_opt(ServerHost, mod_proxy65, max_connections, infinity). + +register_handlers(Host, IQDisc) -> + gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO, + ?MODULE, process_disco_info, IQDisc), + gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS, + ?MODULE, process_disco_items, IQDisc), + gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_VCARD, + ?MODULE, process_vcard, IQDisc), + gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_BYTESTREAMS, + ?MODULE, process_bytestreams, IQDisc). + +unregister_handlers(Host) -> + gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO), + gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS), + gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_VCARD), + gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_BYTESTREAMS). diff --git a/src/mod_pubsub.erl b/src/mod_pubsub.erl index d682eb5f3..0ae68b24b 100644 --- a/src/mod_pubsub.erl +++ b/src/mod_pubsub.erl @@ -189,7 +189,7 @@ -record(state, { server_host, - host, + hosts, access, pep_mapping = [], ignore_pep_from_offline = true, @@ -205,7 +205,7 @@ -type(state() :: #state{ server_host :: binary(), - host :: mod_pubsub:hostPubsub(), + hosts :: [mod_pubsub:hostPubsub()], access :: atom(), pep_mapping :: [{binary(), binary()}], ignore_pep_from_offline :: boolean(), @@ -243,42 +243,63 @@ stop(Host) -> init([ServerHost, Opts]) -> process_flag(trap_exit, true), ?DEBUG("pubsub init ~p ~p", [ServerHost, Opts]), - Host = gen_mod:get_opt_host(ServerHost, Opts, <<"pubsub.@HOST@">>), - ejabberd_router:register_route(Host, ServerHost), + Hosts = gen_mod:get_opt_hosts(ServerHost, Opts, <<"pubsub.@HOST@">>), Access = gen_mod:get_opt(access_createnode, Opts, all), PepOffline = gen_mod:get_opt(ignore_pep_from_offline, Opts, true), - IQDisc = gen_mod:get_opt(iqdisc, Opts, gen_iq_handler:iqdisc(Host)), + IQDisc = gen_mod:get_opt(iqdisc, Opts, gen_iq_handler:iqdisc(ServerHost)), LastItemCache = gen_mod:get_opt(last_item_cache, Opts, false), MaxItemsNode = gen_mod:get_opt(max_items_node, Opts, ?MAXITEMS), MaxSubsNode = gen_mod:get_opt(max_subscriptions_node, Opts), - case gen_mod:db_type(ServerHost, ?MODULE) of - mnesia -> pubsub_index:init(Host, ServerHost, Opts); - _ -> ok - end, - {Plugins, NodeTree, PepMapping} = init_plugins(Host, ServerHost, Opts), - DefaultModule = plugin(Host, hd(Plugins)), - BaseOptions = DefaultModule:options(), - DefaultNodeCfg = filter_node_options( - gen_mod:get_opt(default_node_config, Opts, []), - BaseOptions), ejabberd_mnesia:create(?MODULE, pubsub_last_item, - [{ram_copies, [node()]}, - {attributes, record_info(fields, pubsub_last_item)}]), - lists:foreach( - fun(H) -> - T = gen_mod:get_module_proc(H, config), - ets:new(T, [set, named_table]), - ets:insert(T, {nodetree, NodeTree}), - ets:insert(T, {plugins, Plugins}), - ets:insert(T, {last_item_cache, LastItemCache}), - ets:insert(T, {max_items_node, MaxItemsNode}), - ets:insert(T, {max_subscriptions_node, MaxSubsNode}), - ets:insert(T, {default_node_config, DefaultNodeCfg}), - ets:insert(T, {pep_mapping, PepMapping}), - ets:insert(T, {ignore_pep_from_offline, PepOffline}), - ets:insert(T, {host, Host}), - ets:insert(T, {access, Access}) - end, [Host, ServerHost]), + [{ram_copies, [node()]}, + {attributes, record_info(fields, pubsub_last_item)}]), + AllPlugins = + lists:flatmap( + fun(Host) -> + ejabberd_router:register_route(Host, ServerHost), + case gen_mod:db_type(ServerHost, ?MODULE) of + mnesia -> pubsub_index:init(Host, ServerHost, Opts); + _ -> ok + end, + {Plugins, NodeTree, PepMapping} = init_plugins(Host, ServerHost, Opts), + DefaultModule = plugin(Host, hd(Plugins)), + BaseOptions = DefaultModule:options(), + DefaultNodeCfg = filter_node_options( + gen_mod:get_opt(default_node_config, Opts, []), + BaseOptions), + lists:foreach( + fun(H) -> + T = gen_mod:get_module_proc(H, config), + try + ets:new(T, [set, named_table]), + ets:insert(T, {nodetree, NodeTree}), + ets:insert(T, {plugins, Plugins}), + ets:insert(T, {last_item_cache, LastItemCache}), + ets:insert(T, {max_items_node, MaxItemsNode}), + ets:insert(T, {max_subscriptions_node, MaxSubsNode}), + ets:insert(T, {default_node_config, DefaultNodeCfg}), + ets:insert(T, {pep_mapping, PepMapping}), + ets:insert(T, {ignore_pep_from_offline, PepOffline}), + ets:insert(T, {host, Host}), + ets:insert(T, {access, Access}) + catch error:badarg when H == ServerHost -> + ok + end + end, [Host, ServerHost]), + gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO, + ?MODULE, process_disco_info, IQDisc), + gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS, + ?MODULE, process_disco_items, IQDisc), + gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_PUBSUB, + ?MODULE, process_pubsub, IQDisc), + gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_PUBSUB_OWNER, + ?MODULE, process_pubsub_owner, IQDisc), + gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_VCARD, + ?MODULE, process_vcard, IQDisc), + gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_COMMANDS, + ?MODULE, process_commands, IQDisc), + Plugins + end, Hosts), ejabberd_hooks:add(sm_remove_connection_hook, ServerHost, ?MODULE, on_user_offline, 75), ejabberd_hooks:add(disco_local_identity, ServerHost, @@ -297,19 +318,7 @@ init([ServerHost, Opts]) -> ?MODULE, remove_user, 50), ejabberd_hooks:add(c2s_handle_info, ServerHost, ?MODULE, c2s_handle_info, 50), - gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO, - ?MODULE, process_disco_info, IQDisc), - gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS, - ?MODULE, process_disco_items, IQDisc), - gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_PUBSUB, - ?MODULE, process_pubsub, IQDisc), - gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_PUBSUB_OWNER, - ?MODULE, process_pubsub_owner, IQDisc), - gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_VCARD, - ?MODULE, process_vcard, IQDisc), - gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_COMMANDS, - ?MODULE, process_commands, IQDisc), - case lists:member(?PEPNODE, Plugins) of + case lists:member(?PEPNODE, AllPlugins) of true -> ejabberd_hooks:add(caps_add, ServerHost, ?MODULE, caps_add, 80), @@ -328,20 +337,19 @@ init([ServerHost, Opts]) -> false -> ok end, - {_, State} = init_send_loop(ServerHost), + {_, State} = init_send_loop(ServerHost, Hosts), {ok, State}. -init_send_loop(ServerHost) -> +init_send_loop(ServerHost, Hosts) -> NodeTree = config(ServerHost, nodetree), Plugins = config(ServerHost, plugins), LastItemCache = config(ServerHost, last_item_cache), MaxItemsNode = config(ServerHost, max_items_node), PepMapping = config(ServerHost, pep_mapping), PepOffline = config(ServerHost, ignore_pep_from_offline), - Host = config(ServerHost, host), Access = config(ServerHost, access), DBType = gen_mod:db_type(ServerHost, ?MODULE), - State = #state{host = Host, server_host = ServerHost, + State = #state{hosts = Hosts, server_host = ServerHost, access = Access, pep_mapping = PepMapping, ignore_pep_from_offline = PepOffline, last_item_cache = LastItemCache, @@ -419,7 +427,7 @@ get_subscribed(User, Server) -> send_loop(State) -> receive {presence, JID, _Pid} -> - Host = State#state.host, + Host = State#state.server_host, ServerHost = State#state.server_host, DBType = State#state.db_type, LJID = jid:tolower(JID), @@ -461,7 +469,7 @@ send_loop(State) -> send_loop(State); {presence, User, Server, Resources, JID} -> spawn(fun() -> - Host = State#state.host, + Host = State#state.server_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 @@ -689,11 +697,7 @@ presence_probe(_From, _To, _Pid) -> ok. presence(ServerHost, Presence) -> - {SendLoop, _} = case whereis(gen_mod:get_module_proc(ServerHost, ?LOOPNAME)) of - undefined -> init_send_loop(ServerHost); - Pid -> {Pid, undefined} - end, - SendLoop ! Presence, + gen_mod:get_module_proc(ServerHost, ?LOOPNAME) ! Presence, ok. %% ------- @@ -859,7 +863,7 @@ handle_info(_Info, State) -> %%-------------------------------------------------------------------- %% @private terminate(_Reason, - #state{host = Host, server_host = ServerHost, nodetree = TreePlugin, plugins = Plugins}) -> + #state{hosts = Hosts, server_host = ServerHost, nodetree = TreePlugin, plugins = Plugins}) -> case lists:member(?PEPNODE, Plugins) of true -> ejabberd_hooks:delete(caps_add, ServerHost, @@ -897,20 +901,23 @@ terminate(_Reason, ?MODULE, remove_user, 50), ejabberd_hooks:delete(c2s_handle_info, ServerHost, ?MODULE, c2s_handle_info, 50), - gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO), - gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS), - gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_PUBSUB), - gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_PUBSUB_OWNER), - gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_VCARD), - gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_COMMANDS), case whereis(gen_mod:get_module_proc(ServerHost, ?LOOPNAME)) of undefined -> ?ERROR_MSG("~s process is dead, pubsub was broken", [?LOOPNAME]); Pid -> Pid ! stop end, - terminate_plugins(Host, ServerHost, Plugins, TreePlugin), - ejabberd_router:unregister_route(Host). + lists:foreach( + fun(Host) -> + gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO), + gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS), + gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_PUBSUB), + gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_PUBSUB_OWNER), + gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_VCARD), + gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_COMMANDS), + terminate_plugins(Host, ServerHost, Plugins, TreePlugin), + ejabberd_router:unregister_route(Host) + end, Hosts). %%-------------------------------------------------------------------- %% Func: code_change(OldVsn, State, Extra) -> {ok, NewState} @@ -3877,6 +3884,8 @@ export(Server) -> mod_opt_type(access_createnode) -> fun acl:access_rules_validator/1; mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end; mod_opt_type(host) -> fun iolist_to_binary/1; +mod_opt_type(hosts) -> + fun (L) -> lists:map(fun iolist_to_binary/1, L) end; mod_opt_type(ignore_pep_from_offline) -> fun (A) when is_boolean(A) -> A end; mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1; @@ -3895,7 +3904,7 @@ mod_opt_type(pep_mapping) -> mod_opt_type(plugins) -> fun (A) when is_list(A) -> A end; mod_opt_type(_) -> - [access_createnode, db_type, host, + [access_createnode, db_type, host, hosts, ignore_pep_from_offline, iqdisc, last_item_cache, max_items_node, nodetree, pep_mapping, plugins, max_subscriptions_node, default_node_config]. diff --git a/src/mod_vcard.erl b/src/mod_vcard.erl index 495393f72..67d01a085 100644 --- a/src/mod_vcard.erl +++ b/src/mod_vcard.erl @@ -67,7 +67,7 @@ -optional_callbacks([use_cache/1, cache_nodes/1]). --record(state, {host :: binary(), server_host :: binary()}). +-record(state, {hosts :: [binary()], server_host :: binary()}). %%==================================================================== %% gen_mod callbacks @@ -95,37 +95,40 @@ init([Host, Opts]) -> ?NS_VCARD, ?MODULE, process_sm_iq, IQDisc), ejabberd_hooks:add(disco_sm_features, Host, ?MODULE, get_sm_features, 50), - MyHost = gen_mod:get_opt_host(Host, Opts, <<"vjud.@HOST@">>), + MyHosts = gen_mod:get_opt_hosts(Host, Opts, <<"vjud.@HOST@">>), Search = gen_mod:get_opt(search, Opts, false), if Search -> - ejabberd_hooks:add( - disco_local_items, MyHost, ?MODULE, disco_items, 100), - ejabberd_hooks:add( - disco_local_features, MyHost, ?MODULE, disco_features, 100), - ejabberd_hooks:add( - disco_local_identity, MyHost, ?MODULE, disco_identity, 100), - gen_iq_handler:add_iq_handler( - ejabberd_local, MyHost, ?NS_SEARCH, ?MODULE, process_search, IQDisc), - gen_iq_handler:add_iq_handler( - ejabberd_local, MyHost, ?NS_VCARD, ?MODULE, process_vcard, IQDisc), - gen_iq_handler:add_iq_handler( - ejabberd_local, MyHost, ?NS_DISCO_ITEMS, mod_disco, - process_local_iq_items, IQDisc), - gen_iq_handler:add_iq_handler( - ejabberd_local, MyHost, ?NS_DISCO_INFO, mod_disco, - process_local_iq_info, IQDisc), - case Mod:is_search_supported(Host) of - false -> - ?WARNING_MSG("vcard search functionality is " - "not implemented for ~s backend", - [gen_mod:db_type(Host, Opts, ?MODULE)]); - true -> - ejabberd_router:register_route(MyHost, Host) - end; + lists:foreach( + fun(MyHost) -> + ejabberd_hooks:add( + disco_local_items, MyHost, ?MODULE, disco_items, 100), + ejabberd_hooks:add( + disco_local_features, MyHost, ?MODULE, disco_features, 100), + ejabberd_hooks:add( + disco_local_identity, MyHost, ?MODULE, disco_identity, 100), + gen_iq_handler:add_iq_handler( + ejabberd_local, MyHost, ?NS_SEARCH, ?MODULE, process_search, IQDisc), + gen_iq_handler:add_iq_handler( + ejabberd_local, MyHost, ?NS_VCARD, ?MODULE, process_vcard, IQDisc), + gen_iq_handler:add_iq_handler( + ejabberd_local, MyHost, ?NS_DISCO_ITEMS, mod_disco, + process_local_iq_items, IQDisc), + gen_iq_handler:add_iq_handler( + ejabberd_local, MyHost, ?NS_DISCO_INFO, mod_disco, + process_local_iq_info, IQDisc), + case Mod:is_search_supported(Host) of + false -> + ?WARNING_MSG("vcard search functionality is " + "not implemented for ~s backend", + [gen_mod:db_type(Host, Opts, ?MODULE)]); + true -> + ejabberd_router:register_route(MyHost, Host) + end + end, MyHosts); true -> ok end, - {ok, #state{host = MyHost, server_host = Host}}. + {ok, #state{hosts = MyHosts, server_host = Host}}. handle_call(_Call, _From, State) -> {noreply, State}. @@ -144,21 +147,24 @@ handle_info(Info, State) -> ?WARNING_MSG("unexpected info: ~p", [Info]), {noreply, State}. -terminate(_Reason, #state{host = MyHost, server_host = Host}) -> +terminate(_Reason, #state{hosts = MyHosts, server_host = Host}) -> ejabberd_hooks:delete(remove_user, Host, ?MODULE, remove_user, 50), gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_VCARD), gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_VCARD), ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE, get_sm_features, 50), Mod = gen_mod:db_mod(Host, ?MODULE), Mod:stop(Host), - ejabberd_router:unregister_route(MyHost), - ejabberd_hooks:delete(disco_local_items, MyHost, ?MODULE, disco_items, 100), - ejabberd_hooks:delete(disco_local_features, MyHost, ?MODULE, disco_features, 100), - ejabberd_hooks:delete(disco_local_identity, MyHost, ?MODULE, disco_identity, 100), - gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_SEARCH), - gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_VCARD), - gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_ITEMS), - gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_INFO). + lists:foreach( + fun(MyHost) -> + ejabberd_router:unregister_route(MyHost), + ejabberd_hooks:delete(disco_local_items, MyHost, ?MODULE, disco_items, 100), + ejabberd_hooks:delete(disco_local_features, MyHost, ?MODULE, disco_features, 100), + ejabberd_hooks:delete(disco_local_identity, MyHost, ?MODULE, disco_identity, 100), + gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_SEARCH), + gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_VCARD), + gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_ITEMS), + gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_INFO) + end, MyHosts). code_change(_OldVsn, State, _Extra) -> {ok, State}. @@ -527,6 +533,8 @@ mod_opt_type(allow_return_all) -> fun (B) when is_boolean(B) -> B end; mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end; mod_opt_type(host) -> fun iolist_to_binary/1; +mod_opt_type(hosts) -> + fun (L) -> lists:map(fun iolist_to_binary/1, L) end; mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1; mod_opt_type(matches) -> fun (infinity) -> infinity; @@ -543,6 +551,6 @@ mod_opt_type(O) when O == cache_life_time; O == cache_size -> mod_opt_type(O) when O == use_cache; O == cache_missed -> fun (B) when is_boolean(B) -> B end; mod_opt_type(_) -> - [allow_return_all, db_type, host, iqdisc, matches, + [allow_return_all, db_type, host, hosts, iqdisc, matches, search, search_all_hosts, cache_life_time, cache_size, use_cache, cache_missed]. diff --git a/src/mod_vcard_ldap.erl b/src/mod_vcard_ldap.erl index f1f076468..38c4747e6 100644 --- a/src/mod_vcard_ldap.erl +++ b/src/mod_vcard_ldap.erl @@ -47,7 +47,7 @@ -record(state, {serverhost = <<"">> :: binary(), - myhost = <<"">> :: binary(), + myhosts = [] :: [binary()], eldap_id = <<"">> :: binary(), search = false :: boolean(), servers = [] :: [binary()], @@ -351,8 +351,7 @@ default_search_reported() -> {<<"Organization Unit">>, <<"ORGUNIT">>}]. parse_options(Host, Opts) -> - MyHost = gen_mod:get_opt_host(Host, Opts, - <<"vjud.@HOST@">>), + MyHosts = gen_mod:get_opt_hosts(Host, Opts, <<"vjud.@HOST@">>), Search = gen_mod:get_opt(search, Opts, false), Matches = gen_mod:get_opt(matches, Opts, 30), Eldap_ID = misc:atom_to_binary(gen_mod:get_module_proc(Host, ?PROCNAME)), @@ -394,7 +393,7 @@ parse_options(Host, Opts) -> end, SearchReported) ++ UIDAttrs), - #state{serverhost = Host, myhost = MyHost, + #state{serverhost = Host, myhosts = MyHosts, eldap_id = Eldap_ID, search = Search, servers = Cfg#eldap_config.servers, backups = Cfg#eldap_config.backups, From c658907331998ec7fa96c74e9dda0e858d5b753a Mon Sep 17 00:00:00 2001 From: Mathias Ertl Date: Tue, 8 Aug 2017 19:09:55 +0200 Subject: [PATCH 40/64] fix typo --- ejabberdctl.template | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ejabberdctl.template b/ejabberdctl.template index 57ada34dc..9d47334a2 100755 --- a/ejabberdctl.template +++ b/ejabberdctl.template @@ -69,7 +69,7 @@ done # define erl parameters ERLANG_OPTS="+K $POLL -smp $SMP +P $ERL_PROCESSES $ERL_OPTIONS" if [ "$FIREWALL_WINDOW" != "" ] ; then - ERLANG_OPTS="$ERLANG_OPTS -kernel inet_dist_listen_min ${FIREWALL_WINDOW%-*} inet_dist_listen_max ${FIREWALL_WINDOW#*-}/" + ERLANG_OPTS="$ERLANG_OPTS -kernel inet_dist_listen_min ${FIREWALL_WINDOW%-*} inet_dist_listen_max ${FIREWALL_WINDOW#*-}" fi if [ "$INET_DIST_INTERFACE" != "" ] ; then INET_DIST_INTERFACE2=$("$ERL" -noshell -eval 'case inet:parse_address("'$INET_DIST_INTERFACE'") of {ok,IP} -> io:format("~p",[IP]); _ -> ok end.' -s erlang halt) From e903348dd320e1c4cabaa26414f8fbb45f189a45 Mon Sep 17 00:00:00 2001 From: Christophe Romain Date: Wed, 9 Aug 2017 11:34:36 +0200 Subject: [PATCH 41/64] Fix pubsub send_loop after 3fec7824 --- src/mod_pubsub.erl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mod_pubsub.erl b/src/mod_pubsub.erl index 0ae68b24b..194d12444 100644 --- a/src/mod_pubsub.erl +++ b/src/mod_pubsub.erl @@ -427,8 +427,8 @@ get_subscribed(User, Server) -> send_loop(State) -> receive {presence, JID, _Pid} -> - Host = State#state.server_host, ServerHost = State#state.server_host, + Host = host(State#state.server_host), DBType = State#state.db_type, LJID = jid:tolower(JID), BJID = jid:remove_resource(LJID), @@ -469,7 +469,7 @@ send_loop(State) -> send_loop(State); {presence, User, Server, Resources, JID} -> spawn(fun() -> - Host = State#state.server_host, + Host = host(State#state.server_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 From 7d626b4f5cc945a57e52288dd65494b21663cf51 Mon Sep 17 00:00:00 2001 From: Christophe Romain Date: Thu, 10 Aug 2017 12:17:31 +0200 Subject: [PATCH 42/64] Add support of section 4.9.3.16 on rfc6120 --- src/ejabberd_sm.erl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/ejabberd_sm.erl b/src/ejabberd_sm.erl index f7f7447bf..5771a5bbf 100644 --- a/src/ejabberd_sm.erl +++ b/src/ejabberd_sm.erl @@ -489,9 +489,13 @@ host_up(Host) -> -spec host_down(binary()) -> ok. host_down(Host) -> Mod = get_sm_backend(Host), + Err = case ejabberd_cluster:get_nodes() of + [Node] when Node == node() -> xmpp:serr_system_shutdown(); + _ -> xmpp:serr_reset() + end, lists:foreach( fun(#session{sid = {_, Pid}}) when node(Pid) == node() -> - ejabberd_c2s:send(Pid, xmpp:serr_system_shutdown()), + ejabberd_c2s:send(Pid, Err), ejabberd_c2s:stop(Pid); (_) -> ok From a96d72330d050a4e83eccf1b4f4997c9fdf00aa0 Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Thu, 10 Aug 2017 14:49:05 +0300 Subject: [PATCH 43/64] Rename remove_room hook back --- src/mod_muc.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mod_muc.erl b/src/mod_muc.erl index db101e4f6..2aebe2226 100644 --- a/src/mod_muc.erl +++ b/src/mod_muc.erl @@ -167,7 +167,7 @@ restore_room(ServerHost, Host, Name) -> forget_room(ServerHost, Host, Name) -> LServer = jid:nameprep(ServerHost), - ejabberd_hooks:run(destroy_room, LServer, [LServer, Name, Host]), + ejabberd_hooks:run(remove_room, LServer, [LServer, Name, Host]), Mod = gen_mod:db_mod(LServer, ?MODULE), Mod:forget_room(LServer, Host, Name). From fc7ba53c37784807128cee97d1089a4cc52d7ae0 Mon Sep 17 00:00:00 2001 From: Holger Weiss Date: Thu, 10 Aug 2017 18:54:00 +0200 Subject: [PATCH 44/64] prosody2ejabberd: Remove superfluous 'catch' --- src/prosody2ejabberd.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/prosody2ejabberd.erl b/src/prosody2ejabberd.erl index c60f2c24b..029fb5b94 100644 --- a/src/prosody2ejabberd.erl +++ b/src/prosody2ejabberd.erl @@ -422,7 +422,7 @@ convert_node_items(Host, Data) -> Authors = proplists:get_value(<<"data_author">>, Data, []), lists:flatmap( fun({ItemId, Item}) -> - try catch jid:decode(proplists:get_value(ItemId, Authors, Host)) of + try jid:decode(proplists:get_value(ItemId, Authors, Host)) of JID -> [El] = deserialize(Item), [{ItemId, JID, El#xmlel.children}] From 7d3609d954160eaa7637120028cb64280abe6ce5 Mon Sep 17 00:00:00 2001 From: Holger Weiss Date: Thu, 10 Aug 2017 19:49:20 +0200 Subject: [PATCH 45/64] prosody2ejabberd: Support PEP import --- src/prosody2ejabberd.erl | 111 +++++++++++++++++++++++---------------- 1 file changed, 67 insertions(+), 44 deletions(-) diff --git a/src/prosody2ejabberd.erl b/src/prosody2ejabberd.erl index 029fb5b94..0c73a337b 100644 --- a/src/prosody2ejabberd.erl +++ b/src/prosody2ejabberd.erl @@ -50,7 +50,7 @@ from_dir(ProsodyDir) -> convert_dir(Path, Host, SubDir) end, ["vcard", "accounts", "roster", "private", "config", "offline", - "privacy", "pubsub"]) + "privacy", "pep", "pubsub"]) end, HostDirs); {error, Why} = Err -> ?ERROR_MSG("failed to list ~s: ~s", @@ -67,12 +67,23 @@ convert_dir(Path, Host, Type) -> lists:foreach( fun(File) -> FilePath = filename:join(Path, File), - case eval_file(FilePath) of - {ok, Data} -> - Name = iolist_to_binary(filename:rootname(File)), - convert_data(Host, Type, Name, Data); - Err -> - Err + case Type of + "pep" -> + case filelib:is_dir(FilePath) of + true -> + JID = list_to_binary(File ++ "@" ++ Host), + convert_dir(FilePath, JID, "pubsub"); + false -> + ok + end; + _ -> + case eval_file(FilePath) of + {ok, Data} -> + Name = iolist_to_binary(filename:rootname(File)), + convert_data(Host, Type, Name, Data); + Err -> + Err + end end end, Files); {error, enoent} -> @@ -213,43 +224,50 @@ convert_data(Host, "privacy", User, [Data]) -> end, Lists)}, mod_privacy:set_list(Priv); convert_data(PubSub, "pubsub", NodeId, [Data]) -> - Host = url_decode(PubSub), - Node = url_decode(NodeId), - Type = node_type(Host, Node), - NodeData = convert_node_config(Host, Data), - DefaultConfig = mod_pubsub:config(Host, default_node_config, []), - Owner = proplists:get_value(owner, NodeData), - Options = lists:foldl( - fun({_Opt, undefined}, Acc) -> - Acc; - ({Opt, Val}, Acc) -> - lists:keystore(Opt, 1, Acc, {Opt, Val}) - end, DefaultConfig, proplists:get_value(options, NodeData)), - case mod_pubsub:tree_action(Host, create_node, [Host, Node, Type, Owner, Options, []]) of - {ok, Nidx} -> - case mod_pubsub:node_action(Host, Type, create_node, [Nidx, Owner]) of - {result, _} -> - Access = open, % always allow subscriptions proplists:get_value(access_model, Options), - Publish = open, % always allow publications proplists:get_value(publish_model, Options), - MaxItems = proplists:get_value(max_items, Options), - Affiliations = proplists:get_value(affiliations, NodeData), - Subscriptions = proplists:get_value(subscriptions, NodeData), - Items = proplists:get_value(items, NodeData), - [mod_pubsub:node_action(Host, Type, set_affiliation, - [Nidx, Entity, Aff]) - || {Entity, Aff} <- Affiliations, Entity =/= Owner], - [mod_pubsub:node_action(Host, Type, subscribe_node, - [Nidx, jid:make(Entity), Entity, Access, never, [], [], []]) - || Entity <- Subscriptions], - [mod_pubsub:node_action(Host, Type, publish_item, - [Nidx, Publisher, Publish, MaxItems, ItemId, Payload, []]) - || {ItemId, Publisher, Payload} <- Items]; + HostStr = url_decode(PubSub), + case decode_pubsub_host(HostStr) of + Host when is_binary(Host); + is_tuple(Host) -> + Node = url_decode(NodeId), + Type = node_type(Host), + NodeData = convert_node_config(HostStr, Data), + DefaultConfig = mod_pubsub:config(Host, default_node_config, []), + Owner = proplists:get_value(owner, NodeData), + Options = lists:foldl( + fun({_Opt, undefined}, Acc) -> + Acc; + ({Opt, Val}, Acc) -> + lists:keystore(Opt, 1, Acc, {Opt, Val}) + end, DefaultConfig, proplists:get_value(options, NodeData)), + case mod_pubsub:tree_action(Host, create_node, [Host, Node, Type, Owner, Options, []]) of + {ok, Nidx} -> + case mod_pubsub:node_action(Host, Type, create_node, [Nidx, Owner]) of + {result, _} -> + Access = open, % always allow subscriptions proplists:get_value(access_model, Options), + Publish = open, % always allow publications proplists:get_value(publish_model, Options), + MaxItems = proplists:get_value(max_items, Options), + Affiliations = proplists:get_value(affiliations, NodeData), + Subscriptions = proplists:get_value(subscriptions, NodeData), + Items = proplists:get_value(items, NodeData), + [mod_pubsub:node_action(Host, Type, set_affiliation, + [Nidx, Entity, Aff]) + || {Entity, Aff} <- Affiliations, Entity =/= Owner], + [mod_pubsub:node_action(Host, Type, subscribe_node, + [Nidx, jid:make(Entity), Entity, Access, never, [], [], []]) + || Entity <- Subscriptions], + [mod_pubsub:node_action(Host, Type, publish_item, + [Nidx, Publisher, Publish, MaxItems, ItemId, Payload, []]) + || {ItemId, Publisher, Payload} <- Items]; + Error -> + Error + end; Error -> + ?ERROR_MSG("failed to import pubsub node ~s on ~p:~n~p", + [Node, Host, NodeData]), Error end; Error -> - ?ERROR_MSG("failed to import pubsub node ~s on host ~s:~n~p", - [Node, Host, NodeData]), + ?ERROR_MSG("failed to import pubsub node: ~p", [Error]), Error end; convert_data(_Host, _Type, _User, _Data) -> @@ -383,10 +401,15 @@ url_decode(<>, Acc) -> url_decode(<<>>, Acc) -> Acc. -node_type(_Host, <<"urn:", _Tail/binary>>) -> <<"pep">>; -node_type(_Host, <<"http:", _Tail/binary>>) -> <<"pep">>; -node_type(_Host, <<"https:", _Tail/binary>>) -> <<"pep">>; -node_type(Host, _) -> hd(mod_pubsub:plugins(Host)). +decode_pubsub_host(Host) -> + try jid:decode(Host) of + #jid{luser = <<>>, lserver = LServer} -> LServer; + #jid{luser = LUser, lserver = LServer} -> {LUser, LServer, <<>>} + catch _:{bad_jid, _} -> bad_jid + end. + +node_type({_U, _S, _R}) -> <<"pep">>; +node_type(Host) -> hd(mod_pubsub:plugins(Host)). max_items(Config, Default) -> case round(proplists:get_value(<<"max_items">>, Config, Default)) of From 9c5427e0c2ded92ea974b812d57359637994d8d6 Mon Sep 17 00:00:00 2001 From: Christophe Romain Date: Fri, 11 Aug 2017 10:20:33 +0200 Subject: [PATCH 46/64] PubSub: refactor send_last_items remove send_loop --- src/mod_pubsub.erl | 415 ++++++++++++++++++++------------------------- 1 file changed, 181 insertions(+), 234 deletions(-) diff --git a/src/mod_pubsub.erl b/src/mod_pubsub.erl index 194d12444..054770829 100644 --- a/src/mod_pubsub.erl +++ b/src/mod_pubsub.erl @@ -52,7 +52,7 @@ %% exports for hooks -export([presence_probe/3, caps_add/3, caps_update/3, in_subscription/6, out_subscription/4, - on_user_offline/3, remove_user/2, + on_user_online/1, on_user_offline/2, remove_user/2, disco_local_identity/5, disco_local_features/5, disco_local_items/5, disco_sm_identity/5, disco_sm_features/5, disco_sm_items/5, @@ -89,11 +89,7 @@ %% API and gen_server callbacks -export([start/2, stop/1, init/1, handle_call/3, handle_cast/2, handle_info/2, - terminate/2, code_change/3, depends/2, export/1]). - --export([send_loop/1, mod_opt_type/1]). - --define(LOOPNAME, ejabberd_mod_pubsub_loop). + terminate/2, code_change/3, depends/2, export/1, mod_opt_type/1]). %%==================================================================== %% API @@ -300,7 +296,9 @@ init([ServerHost, Opts]) -> ?MODULE, process_commands, IQDisc), Plugins end, Hosts), - ejabberd_hooks:add(sm_remove_connection_hook, ServerHost, + ejabberd_hooks:add(c2s_session_opened, ServerHost, + ?MODULE, on_user_online, 75), + ejabberd_hooks:add(c2s_terminated, ServerHost, ?MODULE, on_user_offline, 75), ejabberd_hooks:add(disco_local_identity, ServerHost, ?MODULE, disco_local_identity, 75), @@ -337,34 +335,16 @@ init([ServerHost, Opts]) -> false -> ok end, - {_, State} = init_send_loop(ServerHost, Hosts), - {ok, State}. - -init_send_loop(ServerHost, Hosts) -> NodeTree = config(ServerHost, nodetree), Plugins = config(ServerHost, plugins), - LastItemCache = config(ServerHost, last_item_cache), - MaxItemsNode = config(ServerHost, max_items_node), PepMapping = config(ServerHost, pep_mapping), - PepOffline = config(ServerHost, ignore_pep_from_offline), - Access = config(ServerHost, access), DBType = gen_mod:db_type(ServerHost, ?MODULE), - State = #state{hosts = Hosts, server_host = ServerHost, - access = Access, pep_mapping = PepMapping, - ignore_pep_from_offline = PepOffline, - last_item_cache = LastItemCache, - max_items_node = MaxItemsNode, nodetree = NodeTree, - plugins = Plugins, db_type = DBType}, - Proc = gen_mod:get_module_proc(ServerHost, ?LOOPNAME), - Pid = case whereis(Proc) of - undefined -> - SendLoop = spawn(?MODULE, send_loop, [State]), - register(Proc, SendLoop), - SendLoop; - Loop -> - Loop - end, - {Pid, State}. + {ok, #state{hosts = Hosts, server_host = ServerHost, + access = Access, pep_mapping = PepMapping, + ignore_pep_from_offline = PepOffline, + last_item_cache = LastItemCache, + max_items_node = MaxItemsNode, nodetree = NodeTree, + plugins = Plugins, db_type = DBType}}. depends(ServerHost, Opts) -> Host = gen_mod:get_opt_host(ServerHost, Opts, <<"pubsub.@HOST@">>), @@ -414,94 +394,6 @@ terminate_plugins(Host, ServerHost, Plugins, TreePlugin) -> TreePlugin:terminate(Host, ServerHost), ok. -get_subscribed(User, Server) -> - Items = ejabberd_hooks:run_fold(roster_get, Server, [], [{User, Server}]), - lists:filtermap( - fun(#roster{jid = LJID, subscription = Sub}) - when Sub == both orelse Sub == from -> - {true, LJID}; - (_) -> - false - end, Items). - -send_loop(State) -> - receive - {presence, JID, _Pid} -> - ServerHost = State#state.server_host, - Host = host(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 = host(State#state.server_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 %% @@ -660,12 +552,12 @@ disco_items(Host, Node, From) -> end. %% ------- -%% presence hooks handling functions +%% presence and session hooks handling functions %% -spec caps_add(jid(), jid(), [binary()]) -> ok. -caps_add(#jid{luser = U, lserver = S, lresource = R}, #jid{lserver = Host} = JID, _Features) - when Host =/= S -> +caps_add(#jid{lserver = S1} = From, #jid{lserver = S2} = To, _Features) + when S1 =/= S2 -> %% When a remote contact goes online while the local user is offline, the %% remote contact won't receive last items from the local user even if %% ignore_pep_from_offline is set to false. To work around this issue a bit, @@ -675,30 +567,36 @@ caps_add(#jid{luser = U, lserver = S, lresource = R}, #jid{lserver = Host} = JID %% contact becomes available; the former is also executed when the local %% user goes online (because that triggers the contact to send a presence %% packet with CAPS). - presence(Host, {presence, U, S, [R], JID}); + send_last_pep(To, From); caps_add(_From, _To, _Feature) -> ok. -spec caps_update(jid(), jid(), [binary()]) -> ok. -caps_update(#jid{luser = U, lserver = S, lresource = R}, #jid{lserver = Host} = JID, _Features) -> - presence(Host, {presence, U, S, [R], JID}). +caps_update(#jid{lserver = S1} = From, #jid{lserver = S2} = To, _Features) + when S1 =/= S2 -> + send_last_pep(To, From). -spec presence_probe(jid(), jid(), pid()) -> ok. presence_probe(#jid{luser = U, lserver = S}, #jid{luser = U, lserver = S}, _Pid) -> %% ignore presence_probe from my other ressources - %% to not get duplicated last items ok; -presence_probe(#jid{lserver = S} = From, #jid{lserver = S} = To, Pid) -> - presence(S, {presence, From, Pid}), - presence(S, {presence, From#jid.luser, S, [From#jid.lresource], To}); +presence_probe(#jid{lserver = S} = From, #jid{lserver = S} = To, _Pid) -> + send_last_pep(To, From); presence_probe(_From, _To, _Pid) -> - %% ignore presence_probe from remote contacts, - %% those are handled via caps_add + %% ignore presence_probe from remote contacts, those are handled via caps_add ok. -presence(ServerHost, Presence) -> - gen_mod:get_module_proc(ServerHost, ?LOOPNAME) ! Presence, - ok. +-spec on_user_online(ejabberd_c2s:state()) -> ejabberd_c2s:state(). +on_user_online(C2SState) -> + JID = maps:get(jid, C2SState), + send_last_items(JID), + C2SState. + +-spec on_user_offline(ejabberd_c2s:state(), atom()) -> ejabberd_c2s:state(). +on_user_offline(C2SState, _Reason) -> + JID = maps:get(jid, C2SState), + purge_offline(jid:tolower(JID)), + C2SState. %% ------- %% subscription hooks handling functions @@ -707,14 +605,8 @@ presence(ServerHost, Presence) -> -spec out_subscription( binary(), binary(), jid(), subscribed | unsubscribed | subscribe | unsubscribe) -> boolean(). -out_subscription(User, Server, JID, subscribed) -> - Owner = jid:make(User, Server), - {PUser, PServer, PResource} = jid:tolower(JID), - PResources = case PResource of - <<>> -> user_resources(PUser, PServer); - _ -> [PResource] - end, - presence(Server, {presence, PUser, PServer, PResources, Owner}), +out_subscription(User, Server, To, subscribed) -> + send_last_pep(jid:make(User, Server), To), true; out_subscription(_, _, _, _) -> true. @@ -883,7 +775,9 @@ terminate(_Reason, false -> ok end, - ejabberd_hooks:delete(sm_remove_connection_hook, ServerHost, + ejabberd_hooks:delete(c2s_session_opened, ServerHost, + ?MODULE, on_user_online, 75), + ejabberd_hooks:delete(c2s_terminated, ServerHost, ?MODULE, on_user_offline, 75), ejabberd_hooks:delete(disco_local_identity, ServerHost, ?MODULE, disco_local_identity, 75), @@ -901,12 +795,6 @@ terminate(_Reason, ?MODULE, remove_user, 50), ejabberd_hooks:delete(c2s_handle_info, ServerHost, ?MODULE, c2s_handle_info, 50), - case whereis(gen_mod:get_module_proc(ServerHost, ?LOOPNAME)) of - undefined -> - ?ERROR_MSG("~s process is dead, pubsub was broken", [?LOOPNAME]); - Pid -> - Pid ! stop - end, lists:foreach( fun(Host) -> gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO), @@ -1797,7 +1685,7 @@ subscribe_node(Host, Node, From, JID, Configuration) -> Nidx = TNode#pubsub_node.id, Type = TNode#pubsub_node.type, Options = TNode#pubsub_node.options, - send_items(Host, Node, Nidx, Type, Options, Subscriber, last), + send_items(Host, Node, Nidx, Type, Options, Subscriber, 1), ServerHost = serverhost(Host), ejabberd_hooks:run(pubsub_subscribe_node, ServerHost, [ServerHost, Host, Node, Subscriber, SubId]), @@ -2069,8 +1957,6 @@ purge_node(Host, Node, Owner) -> %% @doc

    Return the items of a given node.

    %%

    The number of items to return is limited by MaxItems.

    %%

    The permission are not checked in this function.

    -%% @todo We probably need to check that the user doing the query has the right -%% to read the items. -spec get_items(host(), binary(), jid(), binary(), binary(), [binary()], undefined | rsm_set()) -> {result, pubsub()} | {error, stanza_error()}. @@ -2153,43 +2039,23 @@ get_allowed_items_call(Host, Nidx, From, Type, Options, Owners, RSM) -> {PS, RG} = get_presence_and_roster_permissions(Host, From, Owners, AccessModel, AllowedGroups), node_call(Host, Type, get_items, [Nidx, From, AccessModel, PS, RG, undefined, RSM]). -get_last_items(Host, Type, Nidx, LJID, Count) -> +get_last_items(Host, Type, Nidx, LJID, 1) -> case get_cached_item(Host, Nidx) of undefined -> - case node_action(Host, Type, get_last_items, [Nidx, LJID, Count]) of + case node_action(Host, Type, get_last_items, [Nidx, LJID, 1]) of {result, Items} -> Items; - _ -> [] + _ -> [] end; LastItem -> [LastItem] - end. - -%% @doc

    Resend the items of a node to the user.

    -%% @todo use cache-last-item feature -send_items(Host, Node, Nidx, Type, Options, LJID, last) -> - case get_last_items(Host, Type, Nidx, LJID, 1) of - [LastItem] -> - Stanza = items_event_stanza(Node, Options, [LastItem]), - dispatch_items(Host, LJID, Node, Stanza); - _ -> - ok end; -send_items(Host, Node, Nidx, Type, Options, LJID, Number) when Number > 0 -> - Stanza = items_event_stanza(Node, Options, get_last_items(Host, Type, Nidx, Number, LJID)), - dispatch_items(Host, LJID, Node, Stanza); -send_items(Host, Node, _Nidx, _Type, Options, LJID, _) -> - Stanza = items_event_stanza(Node, Options, []), - dispatch_items(Host, LJID, Node, Stanza). - -dispatch_items({FromU, FromS, FromR}, To, Node, Stanza) -> - SenderResource = user_resource(FromU, FromS, FromR), - ejabberd_sm:route(jid:make(FromU, FromS, SenderResource), - {send_filtered, {pep_message, <<((Node))/binary, "+notify">>}, - jid:make(FromU, FromS), jid:make(To), - Stanza}); -dispatch_items(From, To, _Node, Stanza) -> - ejabberd_router:route( - xmpp:set_from_to(Stanza, service_jid(From), jid:make(To))). +get_last_items(Host, Type, Nidx, LJID, Count) when Count > 1 -> + case node_action(Host, Type, get_last_items, [Nidx, LJID, Count]) of + {result, Items} -> Items; + _ -> [] + end; +get_last_items(_Host, _Type, _Nidx, _LJID, _Count) -> + []. %% @doc

    Return the list of affiliations as an XMPP response.

    -spec get_affiliations(host(), binary(), jid(), [binary()]) -> @@ -2536,14 +2402,15 @@ get_subscriptions_for_send_last(Host, PType, sql, JID, LJID, BJID) -> {result, Subs} = node_action(Host, PType, get_entity_subscriptions_for_send_last, [Host, JID]), - [{Node, Sub, SubId, SubJID} + [{Node, SubId, SubJID} || {Node, Sub, SubId, SubJID} <- Subs, Sub =:= subscribed, (SubJID == LJID) or (SubJID == BJID)]; + % sql version already filter result by on_sub_and_presence get_subscriptions_for_send_last(Host, PType, _, JID, LJID, BJID) -> {result, Subs} = node_action(Host, PType, get_entity_subscriptions, [Host, JID]), - [{Node, Sub, SubId, SubJID} + [{Node, SubId, SubJID} || {Node, Sub, SubId, SubJID} <- Subs, Sub =:= subscribed, (SubJID == LJID) or (SubJID == BJID), match_option(Node, send_last_published_item, on_sub_and_presence)]. @@ -2932,8 +2799,9 @@ get_options_for_subs(Host, Nidx, Subs, true) -> broadcast_stanza(Host, _Node, _Nidx, _Type, NodeOptions, SubsByDepth, NotifyType, BaseStanza, SHIM) -> NotificationType = get_option(NodeOptions, notification_type, headline), BroadcastAll = get_option(NodeOptions, broadcast_all_resources), %% XXX this is not standard, but usefull - From = service_jid(Host), - Stanza = add_message_type(BaseStanza, NotificationType), + Stanza = add_message_type( + xmpp:set_from(BaseStanza, service_jid(Host)), + NotificationType), %% Handles explicit subscriptions SubIDsByJID = subscribed_nodes_by_jid(NotifyType, SubsByDepth), lists:foreach(fun ({LJID, _NodeName, SubIDs}) -> @@ -2956,7 +2824,7 @@ broadcast_stanza(Host, _Node, _Nidx, _Type, NodeOptions, SubsByDepth, NotifyType end, lists:foreach(fun(To) -> ejabberd_router:route( - xmpp:set_from_to(StanzaToSend, From, jid:make(To))) + xmpp:set_to(StanzaToSend, jid:make(To))) end, LJIDs) end, SubIDsByJID). @@ -2965,55 +2833,145 @@ broadcast_stanza({LUser, LServer, LResource}, Publisher, Node, Nidx, Type, NodeO %% Handles implicit presence subscriptions SenderResource = user_resource(LUser, LServer, LResource), NotificationType = get_option(NodeOptions, notification_type, headline), - Stanza = add_message_type(BaseStanza, NotificationType), + Stanza = add_message_type( + xmpp:set_from(BaseStanza, jid:make(LUser, LServer)), + NotificationType), %% set the from address on the notification to the bare JID of the account owner %% Also, add "replyto" if entity has presence subscription to the account owner %% See XEP-0163 1.1 section 4.3.1 ejabberd_sm:route(jid:make(LUser, LServer, SenderResource), {pep_message, <<((Node))/binary, "+notify">>, - jid:make(LUser, LServer), add_extended_headers( Stanza, extended_headers([Publisher]))}); broadcast_stanza(Host, _Publisher, Node, Nidx, Type, NodeOptions, SubsByDepth, NotifyType, BaseStanza, SHIM) -> broadcast_stanza(Host, Node, Nidx, Type, NodeOptions, SubsByDepth, NotifyType, BaseStanza, SHIM). -spec c2s_handle_info(ejabberd_c2s:state(), term()) -> ejabberd_c2s:state(). -c2s_handle_info(#{server := Server} = C2SState, - {pep_message, Feature, From, Packet}) -> - LServer = jid:nameprep(Server), - lists:foreach( - fun({USR, Caps}) -> - Features = mod_caps:get_features(LServer, Caps), - case lists:member(Feature, Features) of - true -> - To = jid:make(USR), - NewPacket = xmpp:set_from_to(Packet, From, To), - ejabberd_router:route(NewPacket); - false -> - ok - end - end, mod_caps:list_features(C2SState)), +c2s_handle_info(#{lserver := LServer} = C2SState, + {pep_message, Feature, Packet}) -> + [maybe_send_pep_stanza(LServer, USR, Caps, Feature, Packet) + || {USR, Caps} <- mod_caps:list_features(C2SState)], {stop, C2SState}; -c2s_handle_info(#{server := Server} = C2SState, - {send_filtered, {pep_message, Feature}, From, To, Packet}) -> - LServer = jid:nameprep(Server), - case mod_caps:get_user_caps(To, C2SState) of - {ok, Caps} -> - Features = mod_caps:get_features(LServer, Caps), - case lists:member(Feature, Features) of - true -> - NewPacket = xmpp:set_from_to(Packet, From, To), - ejabberd_router:route(NewPacket); - false -> - ok - end; - error -> - ok +c2s_handle_info(#{lserver := LServer} = C2SState, + {pep_message, Feature, Packet, USR}) -> + case mod_caps:get_user_caps(USR, C2SState) of + {ok, Caps} -> maybe_send_pep_stanza(LServer, USR, Caps, Feature, Packet); + error -> ok end, {stop, C2SState}; c2s_handle_info(C2SState, _) -> C2SState. +send_items(Host, Node, Nidx, Type, Options, LJID, Number) -> + send_items(Host, Node, Nidx, Type, Options, Host, LJID, LJID, Number). +send_items(Host, Node, Nidx, Type, Options, Publisher, SubLJID, ToLJID, Number) -> + case get_last_items(Host, Type, Nidx, SubLJID, Number) of + [] -> + ok; + Items -> + Stanza = items_event_stanza(Node, Options, Items), + send_stanza(Host, Publisher, ToLJID, Node, Stanza) + end. + +send_stanza({LUser, LServer, LResource}, Publisher, USR, Node, BaseStanza) -> + SenderResource = user_resource(LUser, LServer, LResource), + 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(LUser, LServer, SenderResource), + {pep_message, <<((Node))/binary, "+notify">>, + add_extended_headers( + Stanza, extended_headers([Publisher])), + To}) || To <- USRs]; +send_stanza(Host, _Publisher, 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) -> + ?DEBUG("~s", [jid:to_string(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) -> + ?DEBUG("~s -> ~s", [jid:to_string(From), jid:to_string(To)]), + ServerHost = From#jid.lserver, + Host = host(ServerHost), + Publisher = jid:tolower(From), + Owner = jid:remove_resource(Publisher), + lists:foreach( + fun(#pubsub_node{nodeid = {_, Node}, type = Type, id = Nidx, options = Options}) -> + case match_option(Options, send_last_published_item, on_sub_and_presence) of + true -> + LJID = jid:tolower(To), + Subscribed = case get_option(Options, access_model) of + open -> true; + presence -> true; + whitelist -> false; % subscribers are added manually + authorize -> false; % likewise + roster -> + Grps = get_option(Options, roster_groups_allowed, []), + {OU, OS, _} = Owner, + element(2, get_roster_info(OU, OS, LJID, Grps)) + end, + if Subscribed -> send_items(Owner, Node, Nidx, Type, Options, Publisher, LJID, LJID, 1); + true -> ok + end; + _ -> + ok + end + end, + tree_action(Host, get_nodes, [Owner, From])). + subscribed_nodes_by_jid(NotifyType, SubsByDepth) -> NodesToDeliver = fun (Depth, Node, Subs, Acc) -> NodeName = case Node#pubsub_node.nodeid of @@ -3186,9 +3144,6 @@ node_owners_call(_Host, _Type, _Nidx, Owners) -> %% @doc

    Return the maximum number of items for a given node.

    %%

    Unlimited means that there is no limit in the number of items that can %% be stored.

    -%% @todo In practice, the current data structure means that we cannot manage -%% millions of items on a given node. This should be addressed in a new -%% version. -spec max_items(host(), [{atom(), any()}]) -> non_neg_integer(). max_items(Host, Options) -> case get_option(Options, persist_items) of @@ -3792,14 +3747,6 @@ subid_shim(SubIds) -> extended_headers(Jids) -> [#address{type = replyto, jid = Jid} || Jid <- Jids]. --spec on_user_offline(ejabberd_sm:sid(), jid(), ejabberd_sm:info()) -> ok. -on_user_offline(_, JID, _) -> - {User, Server, Resource} = jid:tolower(JID), - case user_resources(User, Server) of - [] -> purge_offline({User, Server, Resource}); - _ -> ok - end. - -spec purge_offline(ljid()) -> ok. purge_offline(LJID) -> Host = host(element(2, LJID)), @@ -3840,7 +3787,7 @@ purge_offline(LJID) -> end end, lists:usort(lists:flatten(Affs))); {Error, _} -> - ?DEBUG("on_user_offline ~p", [Error]) + ?ERROR_MSG("can not purge offline: ~p", [Error]) end. -spec purge_offline(host(), ljid(), binary()) -> ok | {error, stanza_error()}. @@ -3852,13 +3799,13 @@ purge_offline(Host, LJID, Node) -> {result, {[], _}} -> ok; {result, {Items, _}} -> - {User, Server, _} = LJID, + {User, Server, Resource} = LJID, PublishModel = get_option(Options, publish_model), ForceNotify = get_option(Options, notify_retract), {_, NodeId} = Node#pubsub_node.nodeid, lists:foreach(fun - (#pubsub_item{itemid = {ItemId, _}, modification = {_, {U, S, _}}}) - when (U == User) and (S == Server) -> + (#pubsub_item{itemid = {ItemId, _}, modification = {_, {U, S, R}}}) + when (U == User) and (S == Server) and (R == Resource) -> case node_action(Host, Type, delete_item, [Nidx, {U, S, <<>>}, PublishModel, ItemId]) of {result, {_, broadcast}} -> broadcast_retract_items(Host, NodeId, Nidx, Type, Options, [ItemId], ForceNotify), From 32fbfe198129fbed409765ef080ac8c7a7e7810e Mon Sep 17 00:00:00 2001 From: Christophe Romain Date: Fri, 11 Aug 2017 10:32:36 +0200 Subject: [PATCH 47/64] Use correct c2s process sending PEP with multi devices --- src/mod_pubsub.erl | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/mod_pubsub.erl b/src/mod_pubsub.erl index 054770829..40c6e3d06 100644 --- a/src/mod_pubsub.erl +++ b/src/mod_pubsub.erl @@ -2870,11 +2870,10 @@ send_items(Host, Node, Nidx, Type, Options, Publisher, SubLJID, ToLJID, Number) ok; Items -> Stanza = items_event_stanza(Node, Options, Items), - send_stanza(Host, Publisher, ToLJID, Node, Stanza) + send_stanza(Publisher, ToLJID, Node, Stanza) end. -send_stanza({LUser, LServer, LResource}, Publisher, USR, Node, BaseStanza) -> - SenderResource = user_resource(LUser, LServer, LResource), +send_stanza({LUser, LServer, _} = Publisher, USR, Node, BaseStanza) -> Stanza = xmpp:set_from(BaseStanza, jid:make(LUser, LServer)), USRs = case USR of {PUser, PServer, <<>>} -> @@ -2883,12 +2882,12 @@ send_stanza({LUser, LServer, LResource}, Publisher, USR, Node, BaseStanza) -> _ -> [USR] end, - [ejabberd_sm:route(jid:make(LUser, LServer, SenderResource), + [ejabberd_sm:route(jid:make(Publisher), {pep_message, <<((Node))/binary, "+notify">>, add_extended_headers( Stanza, extended_headers([Publisher])), To}) || To <- USRs]; -send_stanza(Host, _Publisher, USR, _Node, Stanza) -> +send_stanza(Host, USR, _Node, Stanza) -> ejabberd_router:route( xmpp:set_from_to(Stanza, service_jid(Host), jid:make(USR))). From 024713a4418526cd59d8e95a95b13f890dcb0160 Mon Sep 17 00:00:00 2001 From: Christophe Romain Date: Fri, 11 Aug 2017 10:53:19 +0200 Subject: [PATCH 48/64] Remove temporary debug --- src/mod_pubsub.erl | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/mod_pubsub.erl b/src/mod_pubsub.erl index 40c6e3d06..2d340cbdf 100644 --- a/src/mod_pubsub.erl +++ b/src/mod_pubsub.erl @@ -2901,7 +2901,6 @@ maybe_send_pep_stanza(LServer, USR, Caps, Feature, Packet) -> end. send_last_items(JID) -> - ?DEBUG("~s", [jid:to_string(JID)]), ServerHost = JID#jid.lserver, Host = host(ServerHost), DBType = config(ServerHost, db_type), @@ -2942,7 +2941,6 @@ send_last_items(JID) -> % ok % end. send_last_pep(From, To) -> - ?DEBUG("~s -> ~s", [jid:to_string(From), jid:to_string(To)]), ServerHost = From#jid.lserver, Host = host(ServerHost), Publisher = jid:tolower(From), From 35eeaa5869763e01dfead4262e375b41d6deb098 Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Fri, 11 Aug 2017 11:43:16 +0300 Subject: [PATCH 49/64] Fix regression introduced by b82b93f8f0c229e Fixes #1928 --- src/ejabberd_captcha.erl | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/src/ejabberd_captcha.erl b/src/ejabberd_captcha.erl index 48e4ac1e6..76af5278f 100644 --- a/src/ejabberd_captcha.erl +++ b/src/ejabberd_captcha.erl @@ -388,29 +388,24 @@ get_transfer_protocol(PortString) -> get_port_listeners(PortNumber) -> AllListeners = ejabberd_config:get_option(listen, []), - lists:filter(fun (Listener) when is_list(Listener) -> - case proplists:get_value(port, Listener) of - PortNumber -> true; - _ -> false - end; - (_) -> false - end, - AllListeners). + lists:filter( + fun({{Port, _IP, _Transport}, _Module, _Opts}) -> + Port == PortNumber + end, AllListeners). get_captcha_transfer_protocol([]) -> throw(<<"The port number mentioned in captcha_host " "is not a ejabberd_http listener with " "'captcha' option. Change the port number " "or specify http:// in that option.">>); -get_captcha_transfer_protocol([Listener | Listeners]) when is_list(Listener) -> - case proplists:get_value(module, Listener) == ejabberd_http andalso - proplists:get_bool(captcha, Listener) of +get_captcha_transfer_protocol([{_, ejabberd_http, Opts} | Listeners]) -> + case proplists:get_bool(captcha, Opts) of true -> - case proplists:get_bool(tls, Listener) of - true -> https; - false -> http - end; - false -> get_captcha_transfer_protocol(Listeners) + case proplists:get_bool(tls, Opts) of + true -> https; + false -> http + end; + false -> get_captcha_transfer_protocol(Listeners) end; get_captcha_transfer_protocol([_ | Listeners]) -> get_captcha_transfer_protocol(Listeners). From 7a90cda8ffa3dcf1073b39e02cc4d69da3669ef0 Mon Sep 17 00:00:00 2001 From: Christophe Romain Date: Fri, 11 Aug 2017 12:05:14 +0200 Subject: [PATCH 50/64] Process on_user_offline only from valid sessions --- src/mod_pubsub.erl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/mod_pubsub.erl b/src/mod_pubsub.erl index 2d340cbdf..ae43627ff 100644 --- a/src/mod_pubsub.erl +++ b/src/mod_pubsub.erl @@ -593,9 +593,10 @@ on_user_online(C2SState) -> C2SState. -spec on_user_offline(ejabberd_c2s:state(), atom()) -> ejabberd_c2s:state(). -on_user_offline(C2SState, _Reason) -> - JID = maps:get(jid, C2SState), +on_user_offline(#{jid := JID} = C2SState, _Reason) -> purge_offline(jid:tolower(JID)), + C2SState; +on_user_offline(C2SState, _Reason) -> C2SState. %% ------- From fd7bf7fed355a0aa2942ccb3150fb8c86e505055 Mon Sep 17 00:00:00 2001 From: Christophe Romain Date: Fri, 11 Aug 2017 17:54:53 +0200 Subject: [PATCH 51/64] Fix typo from 9c5427e0c --- src/mod_pubsub.erl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/mod_pubsub.erl b/src/mod_pubsub.erl index ae43627ff..a67ae5bfc 100644 --- a/src/mod_pubsub.erl +++ b/src/mod_pubsub.erl @@ -572,8 +572,7 @@ caps_add(_From, _To, _Feature) -> ok. -spec caps_update(jid(), jid(), [binary()]) -> ok. -caps_update(#jid{lserver = S1} = From, #jid{lserver = S2} = To, _Features) - when S1 =/= S2 -> +caps_update(From, To, _Features) -> send_last_pep(To, From). -spec presence_probe(jid(), jid(), pid()) -> ok. From 63aabed3204d928bc91e052a3de494a5e1fad886 Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Sun, 13 Aug 2017 19:18:19 +0300 Subject: [PATCH 52/64] Apply URL decoding wherever possible Fixes #1936 --- src/prosody2ejabberd.erl | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/prosody2ejabberd.erl b/src/prosody2ejabberd.erl index 0c73a337b..b6647675f 100644 --- a/src/prosody2ejabberd.erl +++ b/src/prosody2ejabberd.erl @@ -80,7 +80,8 @@ convert_dir(Path, Host, Type) -> case eval_file(FilePath) of {ok, Data} -> Name = iolist_to_binary(filename:rootname(File)), - convert_data(Host, Type, Name, Data); + convert_data(url_decode(Host), Type, + url_decode(Name), Data); Err -> Err end @@ -223,12 +224,10 @@ convert_data(Host, "privacy", User, [Data]) -> end end, Lists)}, mod_privacy:set_list(Priv); -convert_data(PubSub, "pubsub", NodeId, [Data]) -> - HostStr = url_decode(PubSub), +convert_data(HostStr, "pubsub", Node, [Data]) -> case decode_pubsub_host(HostStr) of Host when is_binary(Host); is_tuple(Host) -> - Node = url_decode(NodeId), Type = node_type(Host), NodeData = convert_node_config(HostStr, Data), DefaultConfig = mod_pubsub:config(Host, default_node_config, []), From 64150cc7c5ed60bf6bc422908a1cc39fccde2fe0 Mon Sep 17 00:00:00 2001 From: Holger Weiss Date: Sun, 13 Aug 2017 20:31:03 +0200 Subject: [PATCH 53/64] Let 'domain_certfile' take higher precedence If a 'domain_certfile' is specified, use that instead of the 's2s_certfile' (or 'c2s_certfile'). --- src/ejabberd_c2s.erl | 4 ++-- src/ejabberd_s2s.erl | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ejabberd_c2s.erl b/src/ejabberd_c2s.erl index a0be2e118..fe60f344e 100644 --- a/src/ejabberd_c2s.erl +++ b/src/ejabberd_c2s.erl @@ -303,9 +303,9 @@ tls_options(#{lserver := LServer, tls_options := DefaultOpts, {true, CertFile} when CertFile /= undefined -> DefaultOpts; {_, _} -> case ejabberd_config:get_option( - {c2s_certfile, LServer}, + {domain_certfile, LServer}, ejabberd_config:get_option( - {domain_certfile, LServer})) of + {c2s_certfile, LServer})) of undefined -> DefaultOpts; CertFile -> lists:keystore(certfile, 1, DefaultOpts, {certfile, CertFile}) diff --git a/src/ejabberd_s2s.erl b/src/ejabberd_s2s.erl index a614d8c4a..cb4e5e5ec 100644 --- a/src/ejabberd_s2s.erl +++ b/src/ejabberd_s2s.erl @@ -199,9 +199,9 @@ dirty_get_connections() -> -spec tls_options(binary(), [proplists:property()]) -> [proplists:property()]. tls_options(LServer, DefaultOpts) -> TLSOpts1 = case ejabberd_config:get_option( - {s2s_certfile, LServer}, + {domain_certfile, LServer}, ejabberd_config:get_option( - {domain_certfile, LServer})) of + {s2s_certfile, LServer})) of undefined -> DefaultOpts; CertFile -> lists:keystore(certfile, 1, DefaultOpts, {certfile, CertFile}) From 1820b4f63b5c08a7a5fc603a7161d5c5e7e92ab4 Mon Sep 17 00:00:00 2001 From: Christophe Romain Date: Mon, 14 Aug 2017 09:43:02 +0200 Subject: [PATCH 54/64] Temporary remove recent last_item refactor --- src/mod_pubsub.erl | 412 +++++++++++++++++++++++++-------------------- 1 file changed, 234 insertions(+), 178 deletions(-) diff --git a/src/mod_pubsub.erl b/src/mod_pubsub.erl index a67ae5bfc..194d12444 100644 --- a/src/mod_pubsub.erl +++ b/src/mod_pubsub.erl @@ -52,7 +52,7 @@ %% exports for hooks -export([presence_probe/3, caps_add/3, caps_update/3, in_subscription/6, out_subscription/4, - on_user_online/1, on_user_offline/2, remove_user/2, + on_user_offline/3, remove_user/2, disco_local_identity/5, disco_local_features/5, disco_local_items/5, disco_sm_identity/5, disco_sm_features/5, disco_sm_items/5, @@ -89,7 +89,11 @@ %% API and gen_server callbacks -export([start/2, stop/1, init/1, handle_call/3, handle_cast/2, handle_info/2, - terminate/2, code_change/3, depends/2, export/1, mod_opt_type/1]). + terminate/2, code_change/3, depends/2, export/1]). + +-export([send_loop/1, mod_opt_type/1]). + +-define(LOOPNAME, ejabberd_mod_pubsub_loop). %%==================================================================== %% API @@ -296,9 +300,7 @@ init([ServerHost, Opts]) -> ?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, + ejabberd_hooks:add(sm_remove_connection_hook, ServerHost, ?MODULE, on_user_offline, 75), ejabberd_hooks:add(disco_local_identity, ServerHost, ?MODULE, disco_local_identity, 75), @@ -335,16 +337,34 @@ init([ServerHost, Opts]) -> false -> ok end, + {_, State} = init_send_loop(ServerHost, Hosts), + {ok, State}. + +init_send_loop(ServerHost, Hosts) -> NodeTree = config(ServerHost, nodetree), Plugins = config(ServerHost, plugins), + LastItemCache = config(ServerHost, last_item_cache), + MaxItemsNode = config(ServerHost, max_items_node), PepMapping = config(ServerHost, pep_mapping), + PepOffline = config(ServerHost, ignore_pep_from_offline), + Access = config(ServerHost, access), DBType = gen_mod:db_type(ServerHost, ?MODULE), - {ok, #state{hosts = Hosts, server_host = ServerHost, - access = Access, pep_mapping = PepMapping, - ignore_pep_from_offline = PepOffline, - last_item_cache = LastItemCache, - max_items_node = MaxItemsNode, nodetree = NodeTree, - plugins = Plugins, db_type = DBType}}. + State = #state{hosts = Hosts, server_host = ServerHost, + access = Access, pep_mapping = PepMapping, + ignore_pep_from_offline = PepOffline, + last_item_cache = LastItemCache, + max_items_node = MaxItemsNode, nodetree = NodeTree, + plugins = Plugins, db_type = DBType}, + 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) -> Host = gen_mod:get_opt_host(ServerHost, Opts, <<"pubsub.@HOST@">>), @@ -394,6 +414,94 @@ terminate_plugins(Host, ServerHost, Plugins, TreePlugin) -> TreePlugin:terminate(Host, ServerHost), ok. +get_subscribed(User, Server) -> + Items = ejabberd_hooks:run_fold(roster_get, Server, [], [{User, Server}]), + lists:filtermap( + fun(#roster{jid = LJID, subscription = Sub}) + when Sub == both orelse Sub == from -> + {true, LJID}; + (_) -> + false + end, Items). + +send_loop(State) -> + receive + {presence, JID, _Pid} -> + ServerHost = State#state.server_host, + Host = host(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 = host(State#state.server_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 %% @@ -552,12 +660,12 @@ disco_items(Host, Node, From) -> end. %% ------- -%% presence and session hooks handling functions +%% presence hooks handling functions %% -spec caps_add(jid(), jid(), [binary()]) -> ok. -caps_add(#jid{lserver = S1} = From, #jid{lserver = S2} = To, _Features) - when S1 =/= S2 -> +caps_add(#jid{luser = U, lserver = S, lresource = R}, #jid{lserver = Host} = JID, _Features) + when Host =/= S -> %% When a remote contact goes online while the local user is offline, the %% remote contact won't receive last items from the local user even if %% ignore_pep_from_offline is set to false. To work around this issue a bit, @@ -567,36 +675,30 @@ caps_add(#jid{lserver = S1} = From, #jid{lserver = S2} = To, _Features) %% contact becomes available; the former is also executed when the local %% user goes online (because that triggers the contact to send a presence %% packet with CAPS). - send_last_pep(To, From); + presence(Host, {presence, U, S, [R], JID}); caps_add(_From, _To, _Feature) -> ok. -spec caps_update(jid(), jid(), [binary()]) -> ok. -caps_update(From, To, _Features) -> - send_last_pep(To, From). +caps_update(#jid{luser = U, lserver = S, lresource = R}, #jid{lserver = Host} = JID, _Features) -> + presence(Host, {presence, U, S, [R], JID}). -spec presence_probe(jid(), jid(), pid()) -> ok. presence_probe(#jid{luser = U, lserver = S}, #jid{luser = U, lserver = S}, _Pid) -> %% ignore presence_probe from my other ressources + %% to not get duplicated last items ok; -presence_probe(#jid{lserver = S} = From, #jid{lserver = S} = To, _Pid) -> - send_last_pep(To, From); +presence_probe(#jid{lserver = S} = From, #jid{lserver = S} = To, Pid) -> + presence(S, {presence, From, Pid}), + presence(S, {presence, From#jid.luser, S, [From#jid.lresource], To}); presence_probe(_From, _To, _Pid) -> - %% ignore presence_probe from remote contacts, those are handled via caps_add + %% ignore presence_probe from remote contacts, + %% those are handled via caps_add ok. --spec on_user_online(ejabberd_c2s:state()) -> ejabberd_c2s:state(). -on_user_online(C2SState) -> - JID = maps:get(jid, C2SState), - send_last_items(JID), - C2SState. - --spec on_user_offline(ejabberd_c2s:state(), atom()) -> ejabberd_c2s:state(). -on_user_offline(#{jid := JID} = C2SState, _Reason) -> - purge_offline(jid:tolower(JID)), - C2SState; -on_user_offline(C2SState, _Reason) -> - C2SState. +presence(ServerHost, Presence) -> + gen_mod:get_module_proc(ServerHost, ?LOOPNAME) ! Presence, + ok. %% ------- %% subscription hooks handling functions @@ -605,8 +707,14 @@ on_user_offline(C2SState, _Reason) -> -spec out_subscription( binary(), binary(), jid(), subscribed | unsubscribed | subscribe | unsubscribe) -> boolean(). -out_subscription(User, Server, To, subscribed) -> - send_last_pep(jid:make(User, Server), To), +out_subscription(User, Server, JID, subscribed) -> + Owner = jid:make(User, Server), + {PUser, PServer, PResource} = jid:tolower(JID), + PResources = case PResource of + <<>> -> user_resources(PUser, PServer); + _ -> [PResource] + end, + presence(Server, {presence, PUser, PServer, PResources, Owner}), true; out_subscription(_, _, _, _) -> true. @@ -775,9 +883,7 @@ terminate(_Reason, false -> ok end, - ejabberd_hooks:delete(c2s_session_opened, ServerHost, - ?MODULE, on_user_online, 75), - ejabberd_hooks:delete(c2s_terminated, ServerHost, + ejabberd_hooks:delete(sm_remove_connection_hook, ServerHost, ?MODULE, on_user_offline, 75), ejabberd_hooks:delete(disco_local_identity, ServerHost, ?MODULE, disco_local_identity, 75), @@ -795,6 +901,12 @@ terminate(_Reason, ?MODULE, remove_user, 50), ejabberd_hooks:delete(c2s_handle_info, ServerHost, ?MODULE, c2s_handle_info, 50), + case whereis(gen_mod:get_module_proc(ServerHost, ?LOOPNAME)) of + undefined -> + ?ERROR_MSG("~s process is dead, pubsub was broken", [?LOOPNAME]); + Pid -> + Pid ! stop + end, lists:foreach( fun(Host) -> gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO), @@ -1685,7 +1797,7 @@ subscribe_node(Host, Node, From, JID, Configuration) -> Nidx = TNode#pubsub_node.id, Type = TNode#pubsub_node.type, Options = TNode#pubsub_node.options, - send_items(Host, Node, Nidx, Type, Options, Subscriber, 1), + send_items(Host, Node, Nidx, Type, Options, Subscriber, last), ServerHost = serverhost(Host), ejabberd_hooks:run(pubsub_subscribe_node, ServerHost, [ServerHost, Host, Node, Subscriber, SubId]), @@ -1957,6 +2069,8 @@ purge_node(Host, Node, Owner) -> %% @doc

    Return the items of a given node.

    %%

    The number of items to return is limited by MaxItems.

    %%

    The permission are not checked in this function.

    +%% @todo We probably need to check that the user doing the query has the right +%% to read the items. -spec get_items(host(), binary(), jid(), binary(), binary(), [binary()], undefined | rsm_set()) -> {result, pubsub()} | {error, stanza_error()}. @@ -2039,23 +2153,43 @@ get_allowed_items_call(Host, Nidx, From, Type, Options, Owners, RSM) -> {PS, RG} = get_presence_and_roster_permissions(Host, From, Owners, AccessModel, AllowedGroups), node_call(Host, Type, get_items, [Nidx, From, AccessModel, PS, RG, undefined, RSM]). -get_last_items(Host, Type, Nidx, LJID, 1) -> +get_last_items(Host, Type, Nidx, LJID, Count) -> case get_cached_item(Host, Nidx) of undefined -> - case node_action(Host, Type, get_last_items, [Nidx, LJID, 1]) of + case node_action(Host, Type, get_last_items, [Nidx, LJID, Count]) of {result, Items} -> Items; - _ -> [] + _ -> [] end; LastItem -> [LastItem] + end. + +%% @doc

    Resend the items of a node to the user.

    +%% @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; -get_last_items(Host, Type, Nidx, LJID, Count) when Count > 1 -> - case node_action(Host, Type, get_last_items, [Nidx, LJID, Count]) of - {result, Items} -> Items; - _ -> [] - end; -get_last_items(_Host, _Type, _Nidx, _LJID, _Count) -> - []. +send_items(Host, Node, Nidx, Type, Options, LJID, Number) when Number > 0 -> + Stanza = items_event_stanza(Node, Options, get_last_items(Host, Type, Nidx, Number, LJID)), + dispatch_items(Host, LJID, Node, Stanza); +send_items(Host, Node, _Nidx, _Type, Options, LJID, _) -> + Stanza = items_event_stanza(Node, Options, []), + dispatch_items(Host, LJID, Node, Stanza). + +dispatch_items({FromU, FromS, FromR}, To, Node, Stanza) -> + SenderResource = user_resource(FromU, FromS, FromR), + ejabberd_sm:route(jid:make(FromU, FromS, SenderResource), + {send_filtered, {pep_message, <<((Node))/binary, "+notify">>}, + jid:make(FromU, FromS), jid:make(To), + Stanza}); +dispatch_items(From, To, _Node, Stanza) -> + ejabberd_router:route( + xmpp:set_from_to(Stanza, service_jid(From), jid:make(To))). %% @doc

    Return the list of affiliations as an XMPP response.

    -spec get_affiliations(host(), binary(), jid(), [binary()]) -> @@ -2402,15 +2536,14 @@ get_subscriptions_for_send_last(Host, PType, sql, JID, LJID, BJID) -> {result, Subs} = node_action(Host, PType, get_entity_subscriptions_for_send_last, [Host, JID]), - [{Node, SubId, SubJID} + [{Node, Sub, SubId, SubJID} || {Node, Sub, SubId, SubJID} <- Subs, Sub =:= subscribed, (SubJID == LJID) or (SubJID == BJID)]; - % sql version already filter result by on_sub_and_presence get_subscriptions_for_send_last(Host, PType, _, JID, LJID, BJID) -> {result, Subs} = node_action(Host, PType, get_entity_subscriptions, [Host, JID]), - [{Node, SubId, SubJID} + [{Node, Sub, SubId, SubJID} || {Node, Sub, SubId, SubJID} <- Subs, Sub =:= subscribed, (SubJID == LJID) or (SubJID == BJID), match_option(Node, send_last_published_item, on_sub_and_presence)]. @@ -2799,9 +2932,8 @@ get_options_for_subs(Host, Nidx, Subs, true) -> broadcast_stanza(Host, _Node, _Nidx, _Type, NodeOptions, SubsByDepth, NotifyType, BaseStanza, SHIM) -> NotificationType = get_option(NodeOptions, notification_type, headline), BroadcastAll = get_option(NodeOptions, broadcast_all_resources), %% XXX this is not standard, but usefull - Stanza = add_message_type( - xmpp:set_from(BaseStanza, service_jid(Host)), - NotificationType), + From = service_jid(Host), + Stanza = add_message_type(BaseStanza, NotificationType), %% Handles explicit subscriptions SubIDsByJID = subscribed_nodes_by_jid(NotifyType, SubsByDepth), lists:foreach(fun ({LJID, _NodeName, SubIDs}) -> @@ -2824,7 +2956,7 @@ broadcast_stanza(Host, _Node, _Nidx, _Type, NodeOptions, SubsByDepth, NotifyType end, lists:foreach(fun(To) -> ejabberd_router:route( - xmpp:set_to(StanzaToSend, jid:make(To))) + xmpp:set_from_to(StanzaToSend, From, jid:make(To))) end, LJIDs) end, SubIDsByJID). @@ -2833,142 +2965,55 @@ broadcast_stanza({LUser, LServer, LResource}, Publisher, Node, Nidx, Type, NodeO %% Handles implicit presence subscriptions SenderResource = user_resource(LUser, LServer, LResource), NotificationType = get_option(NodeOptions, notification_type, headline), - Stanza = add_message_type( - xmpp:set_from(BaseStanza, jid:make(LUser, LServer)), - NotificationType), + Stanza = add_message_type(BaseStanza, NotificationType), %% set the from address on the notification to the bare JID of the account owner %% Also, add "replyto" if entity has presence subscription to the account owner %% See XEP-0163 1.1 section 4.3.1 ejabberd_sm:route(jid:make(LUser, LServer, SenderResource), {pep_message, <<((Node))/binary, "+notify">>, + jid:make(LUser, LServer), add_extended_headers( Stanza, extended_headers([Publisher]))}); broadcast_stanza(Host, _Publisher, Node, Nidx, Type, NodeOptions, SubsByDepth, NotifyType, BaseStanza, SHIM) -> broadcast_stanza(Host, Node, Nidx, Type, NodeOptions, SubsByDepth, NotifyType, BaseStanza, SHIM). -spec c2s_handle_info(ejabberd_c2s:state(), term()) -> ejabberd_c2s:state(). -c2s_handle_info(#{lserver := LServer} = C2SState, - {pep_message, Feature, Packet}) -> - [maybe_send_pep_stanza(LServer, USR, Caps, Feature, Packet) - || {USR, Caps} <- mod_caps:list_features(C2SState)], +c2s_handle_info(#{server := Server} = C2SState, + {pep_message, Feature, From, Packet}) -> + LServer = jid:nameprep(Server), + lists:foreach( + fun({USR, Caps}) -> + Features = mod_caps:get_features(LServer, Caps), + case lists:member(Feature, Features) of + true -> + To = jid:make(USR), + NewPacket = xmpp:set_from_to(Packet, From, To), + ejabberd_router:route(NewPacket); + false -> + ok + end + end, mod_caps:list_features(C2SState)), {stop, C2SState}; -c2s_handle_info(#{lserver := LServer} = C2SState, - {pep_message, Feature, Packet, USR}) -> - case mod_caps:get_user_caps(USR, C2SState) of - {ok, Caps} -> maybe_send_pep_stanza(LServer, USR, Caps, Feature, Packet); - error -> ok +c2s_handle_info(#{server := Server} = C2SState, + {send_filtered, {pep_message, Feature}, From, To, Packet}) -> + LServer = jid:nameprep(Server), + case mod_caps:get_user_caps(To, C2SState) of + {ok, Caps} -> + Features = mod_caps:get_features(LServer, Caps), + case lists:member(Feature, Features) of + true -> + NewPacket = xmpp:set_from_to(Packet, From, To), + ejabberd_router:route(NewPacket); + false -> + ok + end; + error -> + ok end, {stop, C2SState}; c2s_handle_info(C2SState, _) -> C2SState. -send_items(Host, Node, Nidx, Type, Options, LJID, Number) -> - send_items(Host, Node, Nidx, Type, Options, Host, LJID, LJID, Number). -send_items(Host, Node, Nidx, Type, Options, Publisher, SubLJID, ToLJID, Number) -> - case get_last_items(Host, Type, Nidx, SubLJID, Number) of - [] -> - ok; - Items -> - Stanza = items_event_stanza(Node, Options, Items), - send_stanza(Publisher, ToLJID, Node, Stanza) - end. - -send_stanza({LUser, LServer, _} = Publisher, USR, Node, BaseStanza) -> - Stanza = xmpp:set_from(BaseStanza, jid:make(LUser, LServer)), - USRs = case USR of - {PUser, PServer, <<>>} -> - [{PUser, PServer, PRessource} - || PRessource <- user_resources(PUser, PServer)]; - _ -> - [USR] - end, - [ejabberd_sm:route(jid:make(Publisher), - {pep_message, <<((Node))/binary, "+notify">>, - add_extended_headers( - Stanza, extended_headers([Publisher])), - To}) || To <- USRs]; -send_stanza(Host, USR, _Node, Stanza) -> - ejabberd_router:route( - xmpp:set_from_to(Stanza, service_jid(Host), jid:make(USR))). - -maybe_send_pep_stanza(LServer, USR, Caps, Feature, Packet) -> - Features = mod_caps:get_features(LServer, Caps), - case lists:member(Feature, Features) of - true -> - ejabberd_router:route(xmpp:set_to(Packet, jid:make(USR))); - false -> - ok - end. - -send_last_items(JID) -> - ServerHost = JID#jid.lserver, - Host = host(ServerHost), - DBType = config(ServerHost, db_type), - LJID = jid:tolower(JID), - BJID = jid:remove_resource(LJID), - lists:foreach( - fun(PType) -> - Subs = get_subscriptions_for_send_last(Host, PType, DBType, JID, LJID, BJID), - lists:foreach( - fun({#pubsub_node{nodeid = {_, Node}, type = Type, id = Nidx, - options = Options}, _, SubJID}) - when Type == PType-> - send_items(Host, Node, Nidx, PType, Options, Host, SubJID, LJID, 1); - (_) -> - ok - end, - lists:usort(Subs)) - end, config(ServerHost, plugins)). -% pep_from_offline hack can not work anymore, as sender c2s does not -% exists when sender is offline, so we can't get match receiver caps -% does it make sens to send PEP from an offline contact anyway ? -% case config(ServerHost, ignore_pep_from_offline) of -% false -> -% Roster = ejabberd_hooks:run_fold(roster_get, ServerHost, [], -% [{JID#jid.luser, ServerHost}]), -% lists:foreach( -% fun(#roster{jid = {U, S, R}, subscription = Sub}) -% when Sub == both orelse Sub == from, -% S == ServerHost -> -% case user_resources(U, S) of -% [] -> send_last_pep(jid:make(U, S, R), JID); -% _ -> ok %% this is already handled by presence probe -% end; -% (_) -> -% ok %% we can not do anything in any cases -% end, Roster); -% true -> -% ok -% end. -send_last_pep(From, To) -> - ServerHost = From#jid.lserver, - Host = host(ServerHost), - Publisher = jid:tolower(From), - Owner = jid:remove_resource(Publisher), - lists:foreach( - fun(#pubsub_node{nodeid = {_, Node}, type = Type, id = Nidx, options = Options}) -> - case match_option(Options, send_last_published_item, on_sub_and_presence) of - true -> - LJID = jid:tolower(To), - Subscribed = case get_option(Options, access_model) of - open -> true; - presence -> true; - whitelist -> false; % subscribers are added manually - authorize -> false; % likewise - roster -> - Grps = get_option(Options, roster_groups_allowed, []), - {OU, OS, _} = Owner, - element(2, get_roster_info(OU, OS, LJID, Grps)) - end, - if Subscribed -> send_items(Owner, Node, Nidx, Type, Options, Publisher, LJID, LJID, 1); - true -> ok - end; - _ -> - ok - end - end, - tree_action(Host, get_nodes, [Owner, From])). - subscribed_nodes_by_jid(NotifyType, SubsByDepth) -> NodesToDeliver = fun (Depth, Node, Subs, Acc) -> NodeName = case Node#pubsub_node.nodeid of @@ -3141,6 +3186,9 @@ node_owners_call(_Host, _Type, _Nidx, Owners) -> %% @doc

    Return the maximum number of items for a given node.

    %%

    Unlimited means that there is no limit in the number of items that can %% be stored.

    +%% @todo In practice, the current data structure means that we cannot manage +%% millions of items on a given node. This should be addressed in a new +%% version. -spec max_items(host(), [{atom(), any()}]) -> non_neg_integer(). max_items(Host, Options) -> case get_option(Options, persist_items) of @@ -3744,6 +3792,14 @@ subid_shim(SubIds) -> extended_headers(Jids) -> [#address{type = replyto, jid = Jid} || Jid <- Jids]. +-spec on_user_offline(ejabberd_sm:sid(), jid(), ejabberd_sm:info()) -> ok. +on_user_offline(_, JID, _) -> + {User, Server, Resource} = jid:tolower(JID), + case user_resources(User, Server) of + [] -> purge_offline({User, Server, Resource}); + _ -> ok + end. + -spec purge_offline(ljid()) -> ok. purge_offline(LJID) -> Host = host(element(2, LJID)), @@ -3784,7 +3840,7 @@ purge_offline(LJID) -> end end, lists:usort(lists:flatten(Affs))); {Error, _} -> - ?ERROR_MSG("can not purge offline: ~p", [Error]) + ?DEBUG("on_user_offline ~p", [Error]) end. -spec purge_offline(host(), ljid(), binary()) -> ok | {error, stanza_error()}. @@ -3796,13 +3852,13 @@ purge_offline(Host, LJID, Node) -> {result, {[], _}} -> ok; {result, {Items, _}} -> - {User, Server, Resource} = LJID, + {User, Server, _} = LJID, PublishModel = get_option(Options, publish_model), ForceNotify = get_option(Options, notify_retract), {_, NodeId} = Node#pubsub_node.nodeid, lists:foreach(fun - (#pubsub_item{itemid = {ItemId, _}, modification = {_, {U, S, R}}}) - when (U == User) and (S == Server) and (R == Resource) -> + (#pubsub_item{itemid = {ItemId, _}, modification = {_, {U, S, _}}}) + when (U == User) and (S == Server) -> case node_action(Host, Type, delete_item, [Nidx, {U, S, <<>>}, PublishModel, ItemId]) of {result, {_, broadcast}} -> broadcast_retract_items(Host, NodeId, Nidx, Type, Options, [ItemId], ForceNotify), From 68fb12153eb2e24ab18412ff9aa4f4a149f54018 Mon Sep 17 00:00:00 2001 From: Christophe Romain Date: Mon, 14 Aug 2017 15:25:45 +0200 Subject: [PATCH 55/64] Revert "Temporary remove recent last_item refactor" This reverts commit 1820b4f63b5c08a7a5fc603a7161d5c5e7e92ab4. --- src/mod_pubsub.erl | 412 ++++++++++++++++++++------------------------- 1 file changed, 178 insertions(+), 234 deletions(-) diff --git a/src/mod_pubsub.erl b/src/mod_pubsub.erl index 194d12444..a67ae5bfc 100644 --- a/src/mod_pubsub.erl +++ b/src/mod_pubsub.erl @@ -52,7 +52,7 @@ %% exports for hooks -export([presence_probe/3, caps_add/3, caps_update/3, in_subscription/6, out_subscription/4, - on_user_offline/3, remove_user/2, + on_user_online/1, on_user_offline/2, remove_user/2, disco_local_identity/5, disco_local_features/5, disco_local_items/5, disco_sm_identity/5, disco_sm_features/5, disco_sm_items/5, @@ -89,11 +89,7 @@ %% API and gen_server callbacks -export([start/2, stop/1, init/1, handle_call/3, handle_cast/2, handle_info/2, - terminate/2, code_change/3, depends/2, export/1]). - --export([send_loop/1, mod_opt_type/1]). - --define(LOOPNAME, ejabberd_mod_pubsub_loop). + terminate/2, code_change/3, depends/2, export/1, mod_opt_type/1]). %%==================================================================== %% API @@ -300,7 +296,9 @@ init([ServerHost, Opts]) -> ?MODULE, process_commands, IQDisc), Plugins end, Hosts), - ejabberd_hooks:add(sm_remove_connection_hook, ServerHost, + ejabberd_hooks:add(c2s_session_opened, ServerHost, + ?MODULE, on_user_online, 75), + ejabberd_hooks:add(c2s_terminated, ServerHost, ?MODULE, on_user_offline, 75), ejabberd_hooks:add(disco_local_identity, ServerHost, ?MODULE, disco_local_identity, 75), @@ -337,34 +335,16 @@ init([ServerHost, Opts]) -> false -> ok end, - {_, State} = init_send_loop(ServerHost, Hosts), - {ok, State}. - -init_send_loop(ServerHost, Hosts) -> NodeTree = config(ServerHost, nodetree), Plugins = config(ServerHost, plugins), - LastItemCache = config(ServerHost, last_item_cache), - MaxItemsNode = config(ServerHost, max_items_node), PepMapping = config(ServerHost, pep_mapping), - PepOffline = config(ServerHost, ignore_pep_from_offline), - Access = config(ServerHost, access), DBType = gen_mod:db_type(ServerHost, ?MODULE), - State = #state{hosts = Hosts, server_host = ServerHost, - access = Access, pep_mapping = PepMapping, - ignore_pep_from_offline = PepOffline, - last_item_cache = LastItemCache, - max_items_node = MaxItemsNode, nodetree = NodeTree, - plugins = Plugins, db_type = DBType}, - Proc = gen_mod:get_module_proc(ServerHost, ?LOOPNAME), - Pid = case whereis(Proc) of - undefined -> - SendLoop = spawn(?MODULE, send_loop, [State]), - register(Proc, SendLoop), - SendLoop; - Loop -> - Loop - end, - {Pid, State}. + {ok, #state{hosts = Hosts, server_host = ServerHost, + access = Access, pep_mapping = PepMapping, + ignore_pep_from_offline = PepOffline, + last_item_cache = LastItemCache, + max_items_node = MaxItemsNode, nodetree = NodeTree, + plugins = Plugins, db_type = DBType}}. depends(ServerHost, Opts) -> Host = gen_mod:get_opt_host(ServerHost, Opts, <<"pubsub.@HOST@">>), @@ -414,94 +394,6 @@ terminate_plugins(Host, ServerHost, Plugins, TreePlugin) -> TreePlugin:terminate(Host, ServerHost), ok. -get_subscribed(User, Server) -> - Items = ejabberd_hooks:run_fold(roster_get, Server, [], [{User, Server}]), - lists:filtermap( - fun(#roster{jid = LJID, subscription = Sub}) - when Sub == both orelse Sub == from -> - {true, LJID}; - (_) -> - false - end, Items). - -send_loop(State) -> - receive - {presence, JID, _Pid} -> - ServerHost = State#state.server_host, - Host = host(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 = host(State#state.server_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 %% @@ -660,12 +552,12 @@ disco_items(Host, Node, From) -> end. %% ------- -%% presence hooks handling functions +%% presence and session hooks handling functions %% -spec caps_add(jid(), jid(), [binary()]) -> ok. -caps_add(#jid{luser = U, lserver = S, lresource = R}, #jid{lserver = Host} = JID, _Features) - when Host =/= S -> +caps_add(#jid{lserver = S1} = From, #jid{lserver = S2} = To, _Features) + when S1 =/= S2 -> %% When a remote contact goes online while the local user is offline, the %% remote contact won't receive last items from the local user even if %% ignore_pep_from_offline is set to false. To work around this issue a bit, @@ -675,30 +567,36 @@ caps_add(#jid{luser = U, lserver = S, lresource = R}, #jid{lserver = Host} = JID %% contact becomes available; the former is also executed when the local %% user goes online (because that triggers the contact to send a presence %% packet with CAPS). - presence(Host, {presence, U, S, [R], JID}); + send_last_pep(To, From); caps_add(_From, _To, _Feature) -> ok. -spec caps_update(jid(), jid(), [binary()]) -> ok. -caps_update(#jid{luser = U, lserver = S, lresource = R}, #jid{lserver = Host} = JID, _Features) -> - presence(Host, {presence, U, S, [R], JID}). +caps_update(From, To, _Features) -> + send_last_pep(To, From). -spec presence_probe(jid(), jid(), pid()) -> ok. presence_probe(#jid{luser = U, lserver = S}, #jid{luser = U, lserver = S}, _Pid) -> %% ignore presence_probe from my other ressources - %% to not get duplicated last items ok; -presence_probe(#jid{lserver = S} = From, #jid{lserver = S} = To, Pid) -> - presence(S, {presence, From, Pid}), - presence(S, {presence, From#jid.luser, S, [From#jid.lresource], To}); +presence_probe(#jid{lserver = S} = From, #jid{lserver = S} = To, _Pid) -> + send_last_pep(To, From); presence_probe(_From, _To, _Pid) -> - %% ignore presence_probe from remote contacts, - %% those are handled via caps_add + %% ignore presence_probe from remote contacts, those are handled via caps_add ok. -presence(ServerHost, Presence) -> - gen_mod:get_module_proc(ServerHost, ?LOOPNAME) ! Presence, - ok. +-spec on_user_online(ejabberd_c2s:state()) -> ejabberd_c2s:state(). +on_user_online(C2SState) -> + JID = maps:get(jid, C2SState), + send_last_items(JID), + C2SState. + +-spec on_user_offline(ejabberd_c2s:state(), atom()) -> ejabberd_c2s:state(). +on_user_offline(#{jid := JID} = C2SState, _Reason) -> + purge_offline(jid:tolower(JID)), + C2SState; +on_user_offline(C2SState, _Reason) -> + C2SState. %% ------- %% subscription hooks handling functions @@ -707,14 +605,8 @@ presence(ServerHost, Presence) -> -spec out_subscription( binary(), binary(), jid(), subscribed | unsubscribed | subscribe | unsubscribe) -> boolean(). -out_subscription(User, Server, JID, subscribed) -> - Owner = jid:make(User, Server), - {PUser, PServer, PResource} = jid:tolower(JID), - PResources = case PResource of - <<>> -> user_resources(PUser, PServer); - _ -> [PResource] - end, - presence(Server, {presence, PUser, PServer, PResources, Owner}), +out_subscription(User, Server, To, subscribed) -> + send_last_pep(jid:make(User, Server), To), true; out_subscription(_, _, _, _) -> true. @@ -883,7 +775,9 @@ terminate(_Reason, false -> ok end, - ejabberd_hooks:delete(sm_remove_connection_hook, ServerHost, + ejabberd_hooks:delete(c2s_session_opened, ServerHost, + ?MODULE, on_user_online, 75), + ejabberd_hooks:delete(c2s_terminated, ServerHost, ?MODULE, on_user_offline, 75), ejabberd_hooks:delete(disco_local_identity, ServerHost, ?MODULE, disco_local_identity, 75), @@ -901,12 +795,6 @@ terminate(_Reason, ?MODULE, remove_user, 50), ejabberd_hooks:delete(c2s_handle_info, ServerHost, ?MODULE, c2s_handle_info, 50), - case whereis(gen_mod:get_module_proc(ServerHost, ?LOOPNAME)) of - undefined -> - ?ERROR_MSG("~s process is dead, pubsub was broken", [?LOOPNAME]); - Pid -> - Pid ! stop - end, lists:foreach( fun(Host) -> gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO), @@ -1797,7 +1685,7 @@ subscribe_node(Host, Node, From, JID, Configuration) -> Nidx = TNode#pubsub_node.id, Type = TNode#pubsub_node.type, Options = TNode#pubsub_node.options, - send_items(Host, Node, Nidx, Type, Options, Subscriber, last), + send_items(Host, Node, Nidx, Type, Options, Subscriber, 1), ServerHost = serverhost(Host), ejabberd_hooks:run(pubsub_subscribe_node, ServerHost, [ServerHost, Host, Node, Subscriber, SubId]), @@ -2069,8 +1957,6 @@ purge_node(Host, Node, Owner) -> %% @doc

    Return the items of a given node.

    %%

    The number of items to return is limited by MaxItems.

    %%

    The permission are not checked in this function.

    -%% @todo We probably need to check that the user doing the query has the right -%% to read the items. -spec get_items(host(), binary(), jid(), binary(), binary(), [binary()], undefined | rsm_set()) -> {result, pubsub()} | {error, stanza_error()}. @@ -2153,43 +2039,23 @@ get_allowed_items_call(Host, Nidx, From, Type, Options, Owners, RSM) -> {PS, RG} = get_presence_and_roster_permissions(Host, From, Owners, AccessModel, AllowedGroups), node_call(Host, Type, get_items, [Nidx, From, AccessModel, PS, RG, undefined, RSM]). -get_last_items(Host, Type, Nidx, LJID, Count) -> +get_last_items(Host, Type, Nidx, LJID, 1) -> case get_cached_item(Host, Nidx) of undefined -> - case node_action(Host, Type, get_last_items, [Nidx, LJID, Count]) of + case node_action(Host, Type, get_last_items, [Nidx, LJID, 1]) of {result, Items} -> Items; - _ -> [] + _ -> [] end; LastItem -> [LastItem] - end. - -%% @doc

    Resend the items of a node to the user.

    -%% @todo use cache-last-item feature -send_items(Host, Node, Nidx, Type, Options, LJID, last) -> - case get_last_items(Host, Type, Nidx, LJID, 1) of - [LastItem] -> - Stanza = items_event_stanza(Node, Options, [LastItem]), - dispatch_items(Host, LJID, Node, Stanza); - _ -> - ok end; -send_items(Host, Node, Nidx, Type, Options, LJID, Number) when Number > 0 -> - Stanza = items_event_stanza(Node, Options, get_last_items(Host, Type, Nidx, Number, LJID)), - dispatch_items(Host, LJID, Node, Stanza); -send_items(Host, Node, _Nidx, _Type, Options, LJID, _) -> - Stanza = items_event_stanza(Node, Options, []), - dispatch_items(Host, LJID, Node, Stanza). - -dispatch_items({FromU, FromS, FromR}, To, Node, Stanza) -> - SenderResource = user_resource(FromU, FromS, FromR), - ejabberd_sm:route(jid:make(FromU, FromS, SenderResource), - {send_filtered, {pep_message, <<((Node))/binary, "+notify">>}, - jid:make(FromU, FromS), jid:make(To), - Stanza}); -dispatch_items(From, To, _Node, Stanza) -> - ejabberd_router:route( - xmpp:set_from_to(Stanza, service_jid(From), jid:make(To))). +get_last_items(Host, Type, Nidx, LJID, Count) when Count > 1 -> + case node_action(Host, Type, get_last_items, [Nidx, LJID, Count]) of + {result, Items} -> Items; + _ -> [] + end; +get_last_items(_Host, _Type, _Nidx, _LJID, _Count) -> + []. %% @doc

    Return the list of affiliations as an XMPP response.

    -spec get_affiliations(host(), binary(), jid(), [binary()]) -> @@ -2536,14 +2402,15 @@ get_subscriptions_for_send_last(Host, PType, sql, JID, LJID, BJID) -> {result, Subs} = node_action(Host, PType, get_entity_subscriptions_for_send_last, [Host, JID]), - [{Node, Sub, SubId, SubJID} + [{Node, SubId, SubJID} || {Node, Sub, SubId, SubJID} <- Subs, Sub =:= subscribed, (SubJID == LJID) or (SubJID == BJID)]; + % sql version already filter result by on_sub_and_presence get_subscriptions_for_send_last(Host, PType, _, JID, LJID, BJID) -> {result, Subs} = node_action(Host, PType, get_entity_subscriptions, [Host, JID]), - [{Node, Sub, SubId, SubJID} + [{Node, SubId, SubJID} || {Node, Sub, SubId, SubJID} <- Subs, Sub =:= subscribed, (SubJID == LJID) or (SubJID == BJID), match_option(Node, send_last_published_item, on_sub_and_presence)]. @@ -2932,8 +2799,9 @@ get_options_for_subs(Host, Nidx, Subs, true) -> broadcast_stanza(Host, _Node, _Nidx, _Type, NodeOptions, SubsByDepth, NotifyType, BaseStanza, SHIM) -> NotificationType = get_option(NodeOptions, notification_type, headline), BroadcastAll = get_option(NodeOptions, broadcast_all_resources), %% XXX this is not standard, but usefull - From = service_jid(Host), - Stanza = add_message_type(BaseStanza, NotificationType), + Stanza = add_message_type( + xmpp:set_from(BaseStanza, service_jid(Host)), + NotificationType), %% Handles explicit subscriptions SubIDsByJID = subscribed_nodes_by_jid(NotifyType, SubsByDepth), lists:foreach(fun ({LJID, _NodeName, SubIDs}) -> @@ -2956,7 +2824,7 @@ broadcast_stanza(Host, _Node, _Nidx, _Type, NodeOptions, SubsByDepth, NotifyType end, lists:foreach(fun(To) -> ejabberd_router:route( - xmpp:set_from_to(StanzaToSend, From, jid:make(To))) + xmpp:set_to(StanzaToSend, jid:make(To))) end, LJIDs) end, SubIDsByJID). @@ -2965,55 +2833,142 @@ broadcast_stanza({LUser, LServer, LResource}, Publisher, Node, Nidx, Type, NodeO %% Handles implicit presence subscriptions SenderResource = user_resource(LUser, LServer, LResource), NotificationType = get_option(NodeOptions, notification_type, headline), - Stanza = add_message_type(BaseStanza, NotificationType), + Stanza = add_message_type( + xmpp:set_from(BaseStanza, jid:make(LUser, LServer)), + NotificationType), %% set the from address on the notification to the bare JID of the account owner %% Also, add "replyto" if entity has presence subscription to the account owner %% See XEP-0163 1.1 section 4.3.1 ejabberd_sm:route(jid:make(LUser, LServer, SenderResource), {pep_message, <<((Node))/binary, "+notify">>, - jid:make(LUser, LServer), add_extended_headers( Stanza, extended_headers([Publisher]))}); broadcast_stanza(Host, _Publisher, Node, Nidx, Type, NodeOptions, SubsByDepth, NotifyType, BaseStanza, SHIM) -> broadcast_stanza(Host, Node, Nidx, Type, NodeOptions, SubsByDepth, NotifyType, BaseStanza, SHIM). -spec c2s_handle_info(ejabberd_c2s:state(), term()) -> ejabberd_c2s:state(). -c2s_handle_info(#{server := Server} = C2SState, - {pep_message, Feature, From, Packet}) -> - LServer = jid:nameprep(Server), - lists:foreach( - fun({USR, Caps}) -> - Features = mod_caps:get_features(LServer, Caps), - case lists:member(Feature, Features) of - true -> - To = jid:make(USR), - NewPacket = xmpp:set_from_to(Packet, From, To), - ejabberd_router:route(NewPacket); - false -> - ok - end - end, mod_caps:list_features(C2SState)), +c2s_handle_info(#{lserver := LServer} = C2SState, + {pep_message, Feature, Packet}) -> + [maybe_send_pep_stanza(LServer, USR, Caps, Feature, Packet) + || {USR, Caps} <- mod_caps:list_features(C2SState)], {stop, C2SState}; -c2s_handle_info(#{server := Server} = C2SState, - {send_filtered, {pep_message, Feature}, From, To, Packet}) -> - LServer = jid:nameprep(Server), - case mod_caps:get_user_caps(To, C2SState) of - {ok, Caps} -> - Features = mod_caps:get_features(LServer, Caps), - case lists:member(Feature, Features) of - true -> - NewPacket = xmpp:set_from_to(Packet, From, To), - ejabberd_router:route(NewPacket); - false -> - ok - end; - error -> - ok +c2s_handle_info(#{lserver := LServer} = C2SState, + {pep_message, Feature, Packet, USR}) -> + case mod_caps:get_user_caps(USR, C2SState) of + {ok, Caps} -> maybe_send_pep_stanza(LServer, USR, Caps, Feature, Packet); + error -> ok end, {stop, C2SState}; c2s_handle_info(C2SState, _) -> C2SState. +send_items(Host, Node, Nidx, Type, Options, LJID, Number) -> + send_items(Host, Node, Nidx, Type, Options, Host, LJID, LJID, Number). +send_items(Host, Node, Nidx, Type, Options, Publisher, SubLJID, ToLJID, Number) -> + case get_last_items(Host, Type, Nidx, SubLJID, Number) of + [] -> + ok; + Items -> + Stanza = items_event_stanza(Node, Options, Items), + send_stanza(Publisher, ToLJID, Node, Stanza) + end. + +send_stanza({LUser, LServer, _} = Publisher, USR, Node, BaseStanza) -> + Stanza = xmpp:set_from(BaseStanza, jid:make(LUser, LServer)), + USRs = case USR of + {PUser, PServer, <<>>} -> + [{PUser, PServer, PRessource} + || PRessource <- user_resources(PUser, PServer)]; + _ -> + [USR] + end, + [ejabberd_sm:route(jid:make(Publisher), + {pep_message, <<((Node))/binary, "+notify">>, + add_extended_headers( + Stanza, extended_headers([Publisher])), + To}) || To <- USRs]; +send_stanza(Host, USR, _Node, Stanza) -> + ejabberd_router:route( + xmpp:set_from_to(Stanza, service_jid(Host), jid:make(USR))). + +maybe_send_pep_stanza(LServer, USR, Caps, Feature, Packet) -> + Features = mod_caps:get_features(LServer, Caps), + case lists:member(Feature, Features) of + true -> + ejabberd_router:route(xmpp:set_to(Packet, jid:make(USR))); + false -> + ok + end. + +send_last_items(JID) -> + ServerHost = JID#jid.lserver, + Host = host(ServerHost), + DBType = config(ServerHost, db_type), + LJID = jid:tolower(JID), + BJID = jid:remove_resource(LJID), + lists:foreach( + fun(PType) -> + Subs = get_subscriptions_for_send_last(Host, PType, DBType, JID, LJID, BJID), + lists:foreach( + fun({#pubsub_node{nodeid = {_, Node}, type = Type, id = Nidx, + options = Options}, _, SubJID}) + when Type == PType-> + send_items(Host, Node, Nidx, PType, Options, Host, SubJID, LJID, 1); + (_) -> + ok + end, + lists:usort(Subs)) + end, config(ServerHost, plugins)). +% pep_from_offline hack can not work anymore, as sender c2s does not +% exists when sender is offline, so we can't get match receiver caps +% does it make sens to send PEP from an offline contact anyway ? +% case config(ServerHost, ignore_pep_from_offline) of +% false -> +% Roster = ejabberd_hooks:run_fold(roster_get, ServerHost, [], +% [{JID#jid.luser, ServerHost}]), +% lists:foreach( +% fun(#roster{jid = {U, S, R}, subscription = Sub}) +% when Sub == both orelse Sub == from, +% S == ServerHost -> +% case user_resources(U, S) of +% [] -> send_last_pep(jid:make(U, S, R), JID); +% _ -> ok %% this is already handled by presence probe +% end; +% (_) -> +% ok %% we can not do anything in any cases +% end, Roster); +% true -> +% ok +% end. +send_last_pep(From, To) -> + ServerHost = From#jid.lserver, + Host = host(ServerHost), + Publisher = jid:tolower(From), + Owner = jid:remove_resource(Publisher), + lists:foreach( + fun(#pubsub_node{nodeid = {_, Node}, type = Type, id = Nidx, options = Options}) -> + case match_option(Options, send_last_published_item, on_sub_and_presence) of + true -> + LJID = jid:tolower(To), + Subscribed = case get_option(Options, access_model) of + open -> true; + presence -> true; + whitelist -> false; % subscribers are added manually + authorize -> false; % likewise + roster -> + Grps = get_option(Options, roster_groups_allowed, []), + {OU, OS, _} = Owner, + element(2, get_roster_info(OU, OS, LJID, Grps)) + end, + if Subscribed -> send_items(Owner, Node, Nidx, Type, Options, Publisher, LJID, LJID, 1); + true -> ok + end; + _ -> + ok + end + end, + tree_action(Host, get_nodes, [Owner, From])). + subscribed_nodes_by_jid(NotifyType, SubsByDepth) -> NodesToDeliver = fun (Depth, Node, Subs, Acc) -> NodeName = case Node#pubsub_node.nodeid of @@ -3186,9 +3141,6 @@ node_owners_call(_Host, _Type, _Nidx, Owners) -> %% @doc

    Return the maximum number of items for a given node.

    %%

    Unlimited means that there is no limit in the number of items that can %% be stored.

    -%% @todo In practice, the current data structure means that we cannot manage -%% millions of items on a given node. This should be addressed in a new -%% version. -spec max_items(host(), [{atom(), any()}]) -> non_neg_integer(). max_items(Host, Options) -> case get_option(Options, persist_items) of @@ -3792,14 +3744,6 @@ subid_shim(SubIds) -> extended_headers(Jids) -> [#address{type = replyto, jid = Jid} || Jid <- Jids]. --spec on_user_offline(ejabberd_sm:sid(), jid(), ejabberd_sm:info()) -> ok. -on_user_offline(_, JID, _) -> - {User, Server, Resource} = jid:tolower(JID), - case user_resources(User, Server) of - [] -> purge_offline({User, Server, Resource}); - _ -> ok - end. - -spec purge_offline(ljid()) -> ok. purge_offline(LJID) -> Host = host(element(2, LJID)), @@ -3840,7 +3784,7 @@ purge_offline(LJID) -> end end, lists:usort(lists:flatten(Affs))); {Error, _} -> - ?DEBUG("on_user_offline ~p", [Error]) + ?ERROR_MSG("can not purge offline: ~p", [Error]) end. -spec purge_offline(host(), ljid(), binary()) -> ok | {error, stanza_error()}. @@ -3852,13 +3796,13 @@ purge_offline(Host, LJID, Node) -> {result, {[], _}} -> ok; {result, {Items, _}} -> - {User, Server, _} = LJID, + {User, Server, Resource} = LJID, PublishModel = get_option(Options, publish_model), ForceNotify = get_option(Options, notify_retract), {_, NodeId} = Node#pubsub_node.nodeid, lists:foreach(fun - (#pubsub_item{itemid = {ItemId, _}, modification = {_, {U, S, _}}}) - when (U == User) and (S == Server) -> + (#pubsub_item{itemid = {ItemId, _}, modification = {_, {U, S, R}}}) + when (U == User) and (S == Server) and (R == Resource) -> case node_action(Host, Type, delete_item, [Nidx, {U, S, <<>>}, PublishModel, ItemId]) of {result, {_, broadcast}} -> broadcast_retract_items(Host, NodeId, Nidx, Type, Options, [ItemId], ForceNotify), From 9bd099013fd02c00385d37ca2bc05602c3c52bcf Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Thu, 17 Aug 2017 14:33:41 +0300 Subject: [PATCH 56/64] Don't attempt to access(2) a certificate file Fixes #1375 --- src/ejabberd_config.erl | 5 +++-- src/misc.erl | 20 +++++--------------- 2 files changed, 8 insertions(+), 17 deletions(-) diff --git a/src/ejabberd_config.erl b/src/ejabberd_config.erl index 1e5d495ce..5d3bc8680 100644 --- a/src/ejabberd_config.erl +++ b/src/ejabberd_config.erl @@ -28,7 +28,7 @@ -export([start/0, load_file/1, reload_file/0, read_file/1, get_option/1, get_option/2, add_option/2, has_option/1, - get_vh_by_auth_method/1, is_file_readable/1, + get_vh_by_auth_method/1, get_version/0, get_myhosts/0, get_mylang/0, get_lang/1, get_ejabberd_config_path/0, is_using_elixir_config/0, prepare_opt_val/4, transform_options/1, collect_options/1, @@ -46,11 +46,12 @@ get_global_option/2, get_local_option/2, get_global_option/3, get_local_option/3, get_option/3]). +-export([is_file_readable/1]). -deprecated([{add_global_option, 2}, {add_local_option, 2}, {get_global_option, 2}, {get_local_option, 2}, {get_global_option, 3}, {get_local_option, 3}, - {get_option, 3}]). + {get_option, 3}, {is_file_readable, 1}]). -include("ejabberd.hrl"). -include("logger.hrl"). diff --git a/src/misc.erl b/src/misc.erl index 2112cd90c..32699e76b 100644 --- a/src/misc.erl +++ b/src/misc.erl @@ -204,22 +204,12 @@ join_atoms(Atoms, Sep) -> %% in configuration validators only. -spec try_read_file(file:filename_all()) -> binary(). try_read_file(Path) -> - Res = case file:read_file_info(Path) of - {ok, #file_info{type = Type, access = Access}} -> - case {Type, Access} of - {regular, read} -> ok; - {regular, read_write} -> ok; - {regular, _} -> {error, file:format_error(eaccess)}; - _ -> {error, "not a regular file"} - end; - {error, Why} -> - {error, file:format_error(Why)} - end, - case Res of - ok -> + case file:open(Path, [read]) of + {ok, Fd} -> + file:close(Fd), iolist_to_binary(Path); - {error, Reason} -> - ?ERROR_MSG("Failed to read ~s: ~s", [Path, Reason]), + {error, Why} -> + ?ERROR_MSG("Failed to read ~s: ~s", [Path, file:format_error(Why)]), erlang:error(badarg) end. From 0760c7273c16b69b09e7a7feecf41bc416709323 Mon Sep 17 00:00:00 2001 From: Holger Weiss Date: Thu, 17 Aug 2017 18:19:04 +0200 Subject: [PATCH 57/64] mod_stream_mgmt: Remove outdated TODO comment The CSI queue is now flushed from mod_client_state. --- src/mod_stream_mgmt.erl | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/mod_stream_mgmt.erl b/src/mod_stream_mgmt.erl index 068550906..e9da40f56 100644 --- a/src/mod_stream_mgmt.erl +++ b/src/mod_stream_mgmt.erl @@ -405,8 +405,6 @@ handle_resume(#{user := User, lserver := LServer, sockmod := SockMod, previd = AttrId}), State3 = resend_unacked_stanzas(State2), State4 = send(State3, #sm_r{xmlns = AttrXmlns}), - %% TODO: move this to mod_client_state - %% csi_flush_queue(State4), State5 = ejabberd_hooks:run_fold(c2s_session_resumed, LServer, State4, []), ?INFO_MSG("(~s) Resumed session for ~s", [SockMod:pp(Socket), jid:encode(JID)]), From b8d2a723337391a57c311d10a7dc381adcdbcee4 Mon Sep 17 00:00:00 2001 From: Holger Weiss Date: Thu, 17 Aug 2017 18:25:06 +0200 Subject: [PATCH 58/64] mod_stream_mgmt: Delete 'c2s_init' hook Delete the 'c2s_init' hook when the last 'mod_stream_mgmt' instance is stopped. --- src/mod_stream_mgmt.erl | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/mod_stream_mgmt.erl b/src/mod_stream_mgmt.erl index e9da40f56..2f6b0fc71 100644 --- a/src/mod_stream_mgmt.erl +++ b/src/mod_stream_mgmt.erl @@ -71,7 +71,12 @@ start(Host, _Opts) -> ejabberd_hooks:add(c2s_terminated, Host, ?MODULE, c2s_terminated, 50). stop(Host) -> - %% TODO: do something with global 'c2s_init' hook + case gen_mod:is_loaded_elsewhere(Host, ?MODULE) of + true -> + ok; + false -> + ejabberd_hooks:delete(c2s_init, ?MODULE, c2s_stream_init, 50) + end, ejabberd_hooks:delete(c2s_stream_started, Host, ?MODULE, c2s_stream_started, 50), ejabberd_hooks:delete(c2s_post_auth_features, Host, ?MODULE, From 6e20e9bcf9b3181eeee335815e06bb7313c17919 Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Thu, 17 Aug 2017 19:32:15 +0300 Subject: [PATCH 59/64] Get rid of deprecated crypto functions --- rebar.config | 1 + src/ejabberd_s2s_out.erl | 2 +- src/mod_http_upload.erl | 2 +- src/randoms.erl | 15 +++++++++++++++ 4 files changed, 18 insertions(+), 2 deletions(-) diff --git a/rebar.config b/rebar.config index b8a94611a..e10ab3cd4 100644 --- a/rebar.config +++ b/rebar.config @@ -92,6 +92,7 @@ {if_var_true, elixir, {d, 'ELIXIR_ENABLED'}}, {if_var_true, erlang_deprecated_types, {d, 'ERL_DEPRECATED_TYPES'}}, {if_have_fun, {crypto, strong_rand_bytes, 1}, {d, 'STRONG_RAND_BYTES'}}, + {if_have_fun, {rand, uniform, 1}, {d, 'RAND_UNIFORM'}}, {if_have_fun, {gb_sets, iterator_from, 2}, {d, 'GB_SETS_ITERATOR_FROM'}}, {if_have_fun, {public_key, short_name_hash, 1}, {d, 'SHORT_NAME_HASH'}}, {if_var_true, hipe, native}, diff --git a/src/ejabberd_s2s_out.erl b/src/ejabberd_s2s_out.erl index e8cad9792..fea5d8162 100644 --- a/src/ejabberd_s2s_out.erl +++ b/src/ejabberd_s2s_out.erl @@ -374,7 +374,7 @@ mk_bounce_error(_Lang, _State) -> -spec get_delay() -> non_neg_integer(). get_delay() -> MaxDelay = ejabberd_config:get_option(s2s_max_retry_delay, 300), - crypto:rand_uniform(1, MaxDelay). + randoms:uniform(MaxDelay). -spec set_idle_timeout(state()) -> state(). set_idle_timeout(#{on_route := send, server := LServer} = State) -> diff --git a/src/mod_http_upload.erl b/src/mod_http_upload.erl index 6d981e9ec..8d986d0d3 100644 --- a/src/mod_http_upload.erl +++ b/src/mod_http_upload.erl @@ -658,7 +658,7 @@ make_rand_string(S, N) -> make_rand_string([make_rand_char() | S], N - 1). -spec make_rand_char() -> char(). make_rand_char() -> - map_int_to_char(crypto:rand_uniform(0, 62)). + map_int_to_char(randoms:uniform(0, 61)). -spec map_int_to_char(0..61) -> char(). diff --git a/src/randoms.erl b/src/randoms.erl index ad07b47c2..7686edcff 100644 --- a/src/randoms.erl +++ b/src/randoms.erl @@ -32,6 +32,20 @@ -define(THRESHOLD, 16#10000000000000000). +-ifdef(RAND_UNIFORM). +get_string() -> + R = rand:uniform(?THRESHOLD), + integer_to_binary(R). + +uniform() -> + rand:uniform(). + +uniform(N) -> + rand:uniform(N). + +uniform(N, M) -> + rand:uniform(M-N+1) + N-1. +-else. get_string() -> R = crypto:rand_uniform(0, ?THRESHOLD), integer_to_binary(R). @@ -44,6 +58,7 @@ uniform(N) -> uniform(N, M) -> crypto:rand_uniform(N, M+1). +-endif. -ifdef(STRONG_RAND_BYTES). bytes(N) -> From 793ca45ddac37fbb529c9f064452982c50b4ea01 Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Thu, 17 Aug 2017 22:14:54 +0300 Subject: [PATCH 60/64] Add OTP 20.0 to Travis testsing platforms --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 2b1c92190..6e1533a61 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,7 @@ otp_release: - 17.5 - 18.3 - 19.2 + - 20.0 services: - riak From ee0a8d296608920d9c167534d4279ac5b3bc4040 Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Fri, 18 Aug 2017 10:20:27 +0300 Subject: [PATCH 61/64] Preserve correct order of deserialized XML elements Fixes #1939 --- src/prosody2ejabberd.erl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/prosody2ejabberd.erl b/src/prosody2ejabberd.erl index b6647675f..2c7dabb48 100644 --- a/src/prosody2ejabberd.erl +++ b/src/prosody2ejabberd.erl @@ -518,5 +518,5 @@ deserialize([{_, S}|T], #xmlel{children = Els} = El, Acc) when is_binary(S) -> deserialize(T, El#xmlel{children = [{xmlcdata, S}|Els]}, Acc); deserialize([{_, L}|T], #xmlel{children = Els} = El, Acc) when is_list(L) -> deserialize(T, El#xmlel{children = deserialize(L) ++ Els}, Acc); -deserialize([], El, Acc) -> - [El|Acc]. +deserialize([], #xmlel{children = Els} = El, Acc) -> + [El#xmlel{children = lists:reverse(Els)}|Acc]. From 0b02d428368a70350eeca3f9a60ec55489a944b0 Mon Sep 17 00:00:00 2001 From: Badlop Date: Fri, 18 Aug 2017 14:09:49 +0200 Subject: [PATCH 62/64] Fix mod_multicast start and reading of configured limits (#1949) --- src/mod_multicast.erl | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/mod_multicast.erl b/src/mod_multicast.erl index 4d3e481a7..e10315b7a 100644 --- a/src/mod_multicast.erl +++ b/src/mod_multicast.erl @@ -998,8 +998,10 @@ build_service_limit_record(LimitOpts) -> build_limit_record(LimitOptsR, remote)}. get_from_limitopts(LimitOpts, SenderT) -> - {SenderT, Result} = lists:keyfind(SenderT, 1, LimitOpts), - Result. + case lists:keyfind(SenderT, 1, LimitOpts) of + false -> []; + {SenderT, Result} -> Result + end. build_remote_limit_record(LimitOpts, SenderT) -> build_limit_record(LimitOpts, SenderT). @@ -1120,10 +1122,10 @@ mod_opt_type(host) -> fun iolist_to_binary/1; mod_opt_type({limits, Type}) when (Type == local) or (Type == remote) -> fun(L) -> lists:map( - fun ({message, infinite}) -> infinite; - ({presence, infinite}) -> infinite; - ({message, I}) when is_integer(I) -> I; - ({presence, I}) when is_integer(I) -> I + fun ({message, infinite} = O) -> O; + ({presence, infinite} = O) -> O; + ({message, I} = O) when is_integer(I) -> O; + ({presence, I} = O) when is_integer(I) -> O end, L) end; mod_opt_type(_) -> [access, host, {limits, local}, {limits, remote}]. From 13ad754eccbcc0305aae903b188c87dbbc77d31e Mon Sep 17 00:00:00 2001 From: Holger Weiss Date: Fri, 18 Aug 2017 16:44:32 +0200 Subject: [PATCH 63/64] Suppress push notifications for online clients When a client enabled push notifications during the current session, notifications should be suppressed as long as the client is online. Suppressing the notification didn't work for the case where the notification was triggered by MAM, but this is now fixed. --- src/ejabberd_sm.erl | 3 ++- src/mod_push.erl | 3 ++- test/push_tests.erl | 2 -- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ejabberd_sm.erl b/src/ejabberd_sm.erl index 5771a5bbf..d93e66473 100644 --- a/src/ejabberd_sm.erl +++ b/src/ejabberd_sm.erl @@ -317,7 +317,8 @@ get_session_sids(User, Server) -> LUser = jid:nodeprep(User), LServer = jid:nameprep(Server), Mod = get_sm_backend(LServer), - online(get_sessions(Mod, LUser, LServer)). + OnlineSs = online(get_sessions(Mod, LUser, LServer)), + [SID || #session{sid = SID} <- OnlineSs]. -spec set_offline_info(sid(), binary(), binary(), binary(), info()) -> ok. diff --git a/src/mod_push.erl b/src/mod_push.erl index 55db7c0e9..90f4ba55e 100644 --- a/src/mod_push.erl +++ b/src/mod_push.erl @@ -546,8 +546,9 @@ delete_sessions(LUser, LServer, LookupFun, Mod) -> -> [push_session()]. drop_online_sessions(LUser, LServer, Clients) -> SessIDs = ejabberd_sm:get_session_sids(LUser, LServer), + ?WARNING_MSG("SessIDs: ~p", [SessIDs]), [Client || {TS, _, _, _} = Client <- Clients, - not lists:keyfind(TS, 1, SessIDs)]. + lists:keyfind(TS, 1, SessIDs) == false]. %%-------------------------------------------------------------------- %% Caching. diff --git a/test/push_tests.erl b/test/push_tests.erl index 4b49cc8fe..04840bad5 100644 --- a/test/push_tests.erl +++ b/test/push_tests.erl @@ -134,8 +134,6 @@ mam_slave(Config) -> self_presence(Config, available), ct:comment("Receiving message from offline storage"), recv_test_message(Config), - ct:comment("Re-enabling push notifications"), - ok = enable_push(Config), ct:comment("Enabling MAM"), ok = enable_mam(Config), ct:comment("Letting the master know that we're ready"), From ba9a79c89c116c73c60253aacbd8b6a608e661a2 Mon Sep 17 00:00:00 2001 From: Holger Weiss Date: Fri, 18 Aug 2017 16:50:08 +0200 Subject: [PATCH 64/64] Apply cosmetic changes to previous commit --- src/ejabberd_sm.erl | 4 ++-- src/mod_push.erl | 1 - test/push_tests.erl | 2 ++ 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/ejabberd_sm.erl b/src/ejabberd_sm.erl index d93e66473..96dbb4e83 100644 --- a/src/ejabberd_sm.erl +++ b/src/ejabberd_sm.erl @@ -317,8 +317,8 @@ get_session_sids(User, Server) -> LUser = jid:nodeprep(User), LServer = jid:nameprep(Server), Mod = get_sm_backend(LServer), - OnlineSs = online(get_sessions(Mod, LUser, LServer)), - [SID || #session{sid = SID} <- OnlineSs]. + Sessions = online(get_sessions(Mod, LUser, LServer)), + [SID || #session{sid = SID} <- Sessions]. -spec set_offline_info(sid(), binary(), binary(), binary(), info()) -> ok. diff --git a/src/mod_push.erl b/src/mod_push.erl index 90f4ba55e..2ca0bf525 100644 --- a/src/mod_push.erl +++ b/src/mod_push.erl @@ -546,7 +546,6 @@ delete_sessions(LUser, LServer, LookupFun, Mod) -> -> [push_session()]. drop_online_sessions(LUser, LServer, Clients) -> SessIDs = ejabberd_sm:get_session_sids(LUser, LServer), - ?WARNING_MSG("SessIDs: ~p", [SessIDs]), [Client || {TS, _, _, _} = Client <- Clients, lists:keyfind(TS, 1, SessIDs) == false]. diff --git a/test/push_tests.erl b/test/push_tests.erl index 04840bad5..b1f3a8b78 100644 --- a/test/push_tests.erl +++ b/test/push_tests.erl @@ -134,6 +134,8 @@ 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"),