diff --git a/src/ejabberd_c2s.erl b/src/ejabberd_c2s.erl index 5b5feaed2..6df6766ca 100644 --- a/src/ejabberd_c2s.erl +++ b/src/ejabberd_c2s.erl @@ -1273,7 +1273,19 @@ handle_info({route, From, To, Packet}, StateName, StateData) -> {From, To, Packet}, in]) of allow -> - {true, Attrs, StateData}; + case ejabberd_hooks:run_fold( + feature_check_packet, StateData#state.server, + allow, + [StateData#state.user, + StateData#state.server, + StateData#state.pres_last, + {From, To, Packet}, + in]) of + allow -> + {true, Attrs, StateData}; + deny -> + {false, Attrs, StateData} + end; deny -> {false, Attrs, StateData} end; diff --git a/src/mod_caps.erl b/src/mod_caps.erl index fe408349a..9250c5c03 100644 --- a/src/mod_caps.erl +++ b/src/mod_caps.erl @@ -33,13 +33,7 @@ -behaviour(gen_mod). -export([read_caps/1, - get_caps/1, - note_caps/3, - wait_caps/2, - clear_caps/1, - get_features/2, - get_user_resources/2, - handle_disco_response/3]). + get_features/1]). %% gen_mod callbacks -export([start/2, start_link/2, @@ -55,25 +49,59 @@ ]). %% hook handlers --export([receive_packet/3, - receive_packet/4, - presence_probe/3, - remove_connection/3]). +-export([user_send_packet/3]). -include("ejabberd.hrl"). -include("jlib.hrl"). -define(PROCNAME, ejabberd_mod_caps). --define(DICT, dict). --define(CAPS_QUERY_TIMEOUT, 60000). % 1mn without answer, consider client never answer -record(caps, {node, version, exts}). -record(caps_features, {node_pair, features = []}). --record(user_caps, {jid, caps}). --record(user_caps_resources, {uid, resource}). --record(state, {host, - disco_requests = ?DICT:new(), - feature_queries = []}). + +-record(state, {host}). + +%%==================================================================== +%% API +%%==================================================================== +start_link(Host, Opts) -> + Proc = gen_mod:get_module_proc(Host, ?PROCNAME), + gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []). + +start(Host, Opts) -> + Proc = gen_mod:get_module_proc(Host, ?PROCNAME), + ChildSpec = + {Proc, + {?MODULE, start_link, [Host, Opts]}, + transient, + 1000, + worker, + [?MODULE]}, + supervisor:start_child(ejabberd_sup, ChildSpec). + +stop(Host) -> + Proc = gen_mod:get_module_proc(Host, ?PROCNAME), + gen_server:call(Proc, stop), + supervisor:terminate_child(ejabberd_sup, Proc), + supervisor:delete_child(ejabberd_sup, Proc). + +%% get_features returns a list of features implied by the given caps +%% record (as extracted by read_caps) or 'unknown' if features are +%% not completely collected at the moment. +get_features(nothing) -> + []; +get_features(#caps{node = Node, version = Version, exts = Exts}) -> + SubNodes = [Version | Exts], + lists:foldl( + fun(SubNode, Acc) -> + case mnesia:dirty_read({caps_features, + node_to_binary(Node, SubNode)}) of + [] -> + Acc; + [#caps_features{features = Features}] -> + binary_to_features(Features) ++ Acc + end + end, [], SubNodes). %% read_caps takes a list of XML elements (the child elements of a %% stanza) and returns an opaque value representing the @@ -81,6 +109,7 @@ %% capabilities are advertised. read_caps(Els) -> read_caps(Els, nothing). + read_caps([{xmlelement, "c", Attrs, _Els} | Tail], Result) -> case xml:get_attr_s("xmlns", Attrs) of ?NS_CAPS -> @@ -103,351 +132,110 @@ read_caps([_ | Tail], Result) -> read_caps([], Result) -> Result. -%% get_caps reads user caps from database -%% here we handle a simple retry loop, to avoid race condition -%% when asking caps while we still did not called note_caps -%% timeout is set to 10s -%% this is to be improved, but without altering performances. -%% if we did not get user presence 10s after getting presence_probe -%% we assume it has no caps -get_caps(LJID) -> - get_caps(LJID, 5). -get_caps(_, 0) -> - nothing; -get_caps(LJID, Retry) -> - case catch mnesia:dirty_read({user_caps, jid_to_binary(LJID)}) of - [#user_caps{caps=waiting}] -> - timer:sleep(2000), - get_caps(LJID, Retry-1); - [#user_caps{caps=Caps}] -> - Caps; - _ -> - nothing - end. - -%% clear_caps removes user caps from database -clear_caps(JID) -> - {U, S, R} = jlib:jid_tolower(JID), - BJID = jid_to_binary(JID), - BUID = jid_to_binary({U, S, []}), - catch mnesia:dirty_delete({user_caps, BJID}), - catch mnesia:dirty_delete_object(#user_caps_resources{uid = BUID, resource = list_to_binary(R)}), - ok. - -%% give default user resource -get_user_resources(LUser, LServer) -> - case catch mnesia:dirty_read({user_caps_resources, jid_to_binary({LUser, LServer, []})}) of - {'EXIT', _} -> - []; - Resources -> - lists:map(fun(#user_caps_resources{resource=R}) -> binary_to_list(R) end, Resources) - end. - -%% note_caps should be called to make the module request disco -%% information. Host is the host that asks, From is the full JID that -%% sent the caps packet, and Caps is what read_caps returned. -note_caps(Host, From, Caps) -> - Proc = gen_mod:get_module_proc(Host, ?PROCNAME), - gen_server:cast(Proc, {note_caps, From, Caps}). - -%% wait_caps should be called just before note_caps -%% it allows to lock get_caps usage for code using presence_probe -%% that may run before we get any chance to note_caps. -wait_caps(Host, From) -> - Proc = gen_mod:get_module_proc(Host, ?PROCNAME), - gen_server:cast(Proc, {wait_caps, From}). - -%% get_features returns a list of features implied by the given caps -%% record (as extracted by read_caps). It may block, and may signal a -%% timeout error. -get_features(Host, Caps) -> - case Caps of - nothing -> - []; - #caps{} -> - Proc = gen_mod:get_module_proc(Host, ?PROCNAME), - gen_server:call(Proc, {get_features, Caps}) - end. - -start_link(Host, Opts) -> - Proc = gen_mod:get_module_proc(Host, ?PROCNAME), - gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []). - -start(Host, Opts) -> - Proc = gen_mod:get_module_proc(Host, ?PROCNAME), - ChildSpec = - {Proc, - {?MODULE, start_link, [Host, Opts]}, - transient, - 1000, - worker, - [?MODULE]}, - supervisor:start_child(ejabberd_sup, ChildSpec). - -stop(Host) -> - Proc = gen_mod:get_module_proc(Host, ?PROCNAME), - gen_server:call(Proc, stop). - -receive_packet(From, To, {xmlelement, "presence", Attrs, Els}) -> - case xml:get_attr_s("type", Attrs) of - "probe" -> - ok; - "error" -> - ok; - "invisible" -> - ok; - "subscribe" -> - ok; - "subscribed" -> - ok; - "unsubscribe" -> - ok; - "unsubscribed" -> - ok; - "unavailable" -> - {_, S1, _} = jlib:jid_tolower(From), - case jlib:jid_tolower(To) of - {_, S1, _} -> ok; - _ -> clear_caps(From) - end; - %% TODO: probe client, and clean only if no answers - %% as far as protocol does not allow inter-server communication to - %% let remote server handle it's own caps to decide which user is to be - %% notified, we must keep a cache of online status of external contacts - %% this is absolutely not scallable, but we have no choice for now - %% we can only use unavailable presence, but if remote user just remove a local user - %% from its roster, then it's considered as offline, so he does not receive local PEP - %% anymore until he login again. - %% This is tracked in EJAB-943 - _ -> - note_caps(To#jid.lserver, From, read_caps(Els)) +%%==================================================================== +%% Hooks +%%==================================================================== +user_send_packet(#jid{luser = User, lserver = Server} = From, + #jid{luser = User, lserver = Server, lresource = ""}, + {xmlelement, "presence", Attrs, Els}) -> + Type = xml:get_attr_s("type", Attrs), + if Type == ""; Type == "available" -> + case read_caps(Els) of + nothing -> + ok; + #caps{version = Version, exts = Exts} = Caps -> + feature_request(Server, From, Caps, [Version | Exts]) + end; + true -> + ok end; -receive_packet(_, _, _) -> +user_send_packet(_From, _To, _Packet) -> ok. -receive_packet(_JID, From, To, Packet) -> - receive_packet(From, To, Packet). - -presence_probe(From, To, _) -> - wait_caps(To#jid.lserver, From). - -remove_connection(_SID, JID, _Info) -> - clear_caps(JID). - -jid_to_binary(JID) -> - {U, S, R} = jlib:jid_tolower(JID), - list_to_binary(jlib:jid_to_string({U, S, R})). - -caps_to_binary(#caps{node = Node, version = Version, exts = Exts}) -> - BExts = [list_to_binary(Ext) || Ext <- Exts], - #caps{node = list_to_binary(Node), version = list_to_binary(Version), exts = BExts}. - -node_to_binary(Node, SubNode) -> - {list_to_binary(Node), list_to_binary(SubNode)}. - -features_to_binary(L) -> [list_to_binary(I) || I <- L]. -binary_to_features(L) -> [binary_to_list(I) || I <- L]. - %%==================================================================== %% gen_server callbacks %%==================================================================== - init([Host, _Opts]) -> mnesia:create_table(caps_features, [{disc_copies, [node()]}, + {local_content, true}, {attributes, record_info(fields, caps_features)}]), - mnesia:create_table(user_caps, - [{ram_copies, [node()]}, - {attributes, record_info(fields, user_caps)}]), - mnesia:create_table(user_caps_resources, - [{ram_copies, [node()]}, - {type, bag}, - {attributes, record_info(fields, user_caps_resources)}]), - mnesia:delete_table(user_caps_default), - mnesia:clear_table(user_caps), % clean in case of explicitely set to disc_copies - mnesia:clear_table(user_caps_resources), % clean in case of explicitely set to disc_copies - ejabberd_hooks:add(user_receive_packet, Host, ?MODULE, receive_packet, 30), - ejabberd_hooks:add(s2s_receive_packet, Host, ?MODULE, receive_packet, 30), - ejabberd_hooks:add(presence_probe_hook, Host, ?MODULE, presence_probe, 20), - ejabberd_hooks:add(sm_remove_connection_hook, Host, ?MODULE, remove_connection, 20), + mnesia:add_table_copy(caps_features, node(), disc_copies), + ejabberd_hooks:add(user_send_packet, Host, + ?MODULE, user_send_packet, 75), {ok, #state{host = Host}}. -maybe_get_features(#caps{node = Node, version = Version, exts = Exts}) -> - SubNodes = [Version | Exts], - %% Make sure that we have all nodes we need to know. - %% If a single one is missing, we wait for more disco - %% responses. - case lists:foldl(fun(SubNode, Acc) -> - case Acc of - fail -> fail; - _ -> - case mnesia:dirty_read({caps_features, {Node, SubNode}}) of - [] -> fail; - [#caps_features{features = Features}] -> Features ++ Acc %% TODO binary - end - end - end, [], SubNodes) of - fail -> wait; - Features -> {ok, Features} - end. - -timestamp() -> - {MegaSecs, Secs, _MicroSecs} = now(), - MegaSecs * 1000000 + Secs. - -handle_call({get_features, Caps}, From, State) -> - case maybe_get_features(Caps) of - {ok, Features} -> - {reply, binary_to_features(Features), State}; - wait -> - gen_server:cast(self(), visit_feature_queries), - Timeout = timestamp() + 10, - FeatureQueries = State#state.feature_queries, - NewFeatureQueries = [{From, Caps, Timeout} | FeatureQueries], - NewState = State#state{feature_queries = NewFeatureQueries}, - {noreply, NewState} - end; - handle_call(stop, _From, State) -> - {stop, normal, ok, State}. + {stop, normal, ok, State}; +handle_call(_Req, _From, State) -> + {reply, {error, badarg}, State}. -handle_cast({note_caps, From, nothing}, State) -> - BJID = jid_to_binary(From), - catch mnesia:dirty_delete({user_caps, BJID}), - {noreply, State}; -handle_cast({note_caps, From, - #caps{node = Node, version = Version, exts = Exts} = Caps}, - #state{host = Host, disco_requests = Requests} = State) -> - %% XXX: this leads to race conditions where ejabberd will send - %% lots of caps disco requests. - {U, S, R} = jlib:jid_tolower(From), - BJID = jid_to_binary(From), - mnesia:transaction(fun() -> - mnesia:write(#user_caps{jid = BJID, caps = caps_to_binary(Caps)}), - case ejabberd_sm:get_user_resources(U, S) of - [] -> - % only store resources of caps aware external contacts - BUID = jid_to_binary({U, S, []}), - mnesia:write(#user_caps_resources{uid = BUID, resource = list_to_binary(R)}); - _ -> - ok - end - end), - %% Now, find which of these are not already in the database. - SubNodes = [Version | Exts], - case lists:foldl(fun(SubNode, Acc) -> - case mnesia:dirty_read({caps_features, node_to_binary(Node, SubNode)}) of - [] -> - [SubNode | Acc]; - _ -> - Acc - end - end, [], SubNodes) of - [] -> - {noreply, State}; - Missing -> - %% For each unknown caps "subnode", we send a disco request. - NewRequests = lists:foldl( - fun(SubNode, Dict) -> - ID = randoms:get_string(), - Stanza = - {xmlelement, "iq", - [{"type", "get"}, - {"id", ID}], - [{xmlelement, "query", - [{"xmlns", ?NS_DISCO_INFO}, - {"node", lists:concat([Node, "#", SubNode])}], - []}]}, - ejabberd_local:register_iq_response_handler - (Host, ID, ?MODULE, handle_disco_response), - ejabberd_router:route(jlib:make_jid("", Host, ""), From, Stanza), - timer:send_after(?CAPS_QUERY_TIMEOUT, self(), {disco_timeout, ID, BJID}), - ?DICT:store(ID, node_to_binary(Node, SubNode), Dict) - end, Requests, Missing), - {noreply, State#state{disco_requests = NewRequests}} - end; -handle_cast({wait_caps, From}, State) -> - BJID = jid_to_binary(From), - mnesia:dirty_write(#user_caps{jid = BJID, caps = waiting}), - {noreply, State}; -handle_cast({disco_response, From, _To, - #iq{type = Type, id = ID, - sub_el = SubEls}}, - #state{disco_requests = Requests} = State) -> - case {Type, SubEls} of - {result, [{xmlelement, "query", _Attrs, Els}]} -> - case ?DICT:find(ID, Requests) of - {ok, BinaryNode} -> - Features = - lists:flatmap(fun({xmlelement, "feature", FAttrs, _}) -> - [xml:get_attr_s("var", FAttrs)]; - (_) -> - [] - end, Els), - mnesia:dirty_write(#caps_features{node_pair = BinaryNode, features = features_to_binary(Features)}), - gen_server:cast(self(), visit_feature_queries); - error -> - ?DEBUG("ID '~s' matches no query", [ID]) - end; - {error, _} -> - %% XXX: if we get error, we cache empty feature not to probe the client continuously - case ?DICT:find(ID, Requests) of - {ok, BinaryNode} -> - mnesia:dirty_write(#caps_features{node_pair = BinaryNode}), - gen_server:cast(self(), visit_feature_queries); - error -> - ?DEBUG("ID '~s' matches no query", [ID]) - end; - %gen_server:cast(self(), visit_feature_queries), - %?DEBUG("Error IQ reponse from ~s:~n~p", [jlib:jid_to_string(From), SubEls]); - {result, _} -> - ?DEBUG("Invalid IQ contents from ~s:~n~p", [jlib:jid_to_string(From), SubEls]); - _ -> - %% Can't do anything about errors - ok - end, - NewRequests = ?DICT:erase(ID, Requests), - {noreply, State#state{disco_requests = NewRequests}}; -handle_cast({disco_timeout, ID, BJID}, #state{host = Host, disco_requests = Requests} = State) -> - %% do not wait a response anymore for this IQ, client certainly will never answer - NewRequests = case ?DICT:is_key(ID, Requests) of - true -> - catch mnesia:dirty_delete({user_caps, BJID}), - ejabberd_local:unregister_iq_response_handler(Host, ID), - ?DICT:erase(ID, Requests); - false -> - Requests - end, - {noreply, State#state{disco_requests = NewRequests}}; -handle_cast(visit_feature_queries, #state{feature_queries = FeatureQueries} = State) -> - Timestamp = timestamp(), - NewFeatureQueries = - lists:foldl(fun({From, Caps, Timeout}, Acc) -> - case maybe_get_features(Caps) of - wait when Timeout > Timestamp -> [{From, Caps, Timeout} | Acc]; - wait -> Acc; - {ok, Features} -> - gen_server:reply(From, Features), - Acc - end - end, [], FeatureQueries), - {noreply, State#state{feature_queries = NewFeatureQueries}}. - -handle_disco_response(From, To, IQ) -> - #jid{lserver = Host} = To, - Proc = gen_mod:get_module_proc(Host, ?PROCNAME), - gen_server:cast(Proc, {disco_response, From, To, IQ}). +handle_cast(_Msg, State) -> + {noreply, State}. handle_info(_Info, State) -> {noreply, State}. terminate(_Reason, State) -> Host = State#state.host, - ejabberd_hooks:delete(user_receive_packet, Host, ?MODULE, receive_packet, 30), - ejabberd_hooks:delete(s2s_receive_packet, Host, ?MODULE, receive_packet, 30), - ejabberd_hooks:delete(presence_probe_hook, Host, ?MODULE, presence_probe, 20), - ejabberd_hooks:delete(sm_remove_connection_hook, Host, ?MODULE, remove_connection, 20), + ejabberd_hooks:delete(user_send_packet, Host, + ?MODULE, user_send_packet, 75), ok. code_change(_OldVsn, State, _Extra) -> {ok, State}. + +%%==================================================================== +%% Aux functions +%%==================================================================== +feature_request(Host, From, Caps, [SubNode | Tail] = SubNodes) -> + Node = Caps#caps.node, + BinaryNode = node_to_binary(Node, SubNode), + case mnesia:dirty_read({caps_features, BinaryNode}) of + [] -> + IQ = #iq{type = get, + xmlns = ?NS_DISCO_INFO, + sub_el = [{xmlelement, "query", + [{"xmlns", ?NS_DISCO_INFO}, + {"node", Node ++ "#" ++ SubNode}], + []}]}, + F = fun(IQReply) -> + feature_response( + IQReply, Host, From, Caps, SubNodes) + end, + ejabberd_local:route_iq( + jlib:make_jid("", Host, ""), From, IQ, F); + _ -> + feature_request(Host, From, Caps, Tail) + end; +feature_request(_Host, _From, _Caps, []) -> + ok. + +feature_response(#iq{type = result, + sub_el = [{xmlelement, _, _, Els}]}, + Host, From, Caps, [SubNode | SubNodes]) -> + Features = lists:flatmap( + fun({xmlelement, "feature", FAttrs, _}) -> + [xml:get_attr_s("var", FAttrs)]; + (_) -> + [] + end, Els), + BinaryNode = node_to_binary(Caps#caps.node, SubNode), + mnesia:dirty_write( + #caps_features{node_pair = BinaryNode, + features = features_to_binary(Features)}), + feature_request(Host, From, Caps, SubNodes); +feature_response(timeout, _Host, _From, _Caps, _SubNodes) -> + ok; +feature_response(_IQResult, Host, From, Caps, [SubNode | SubNodes]) -> + %% We got type=error or invalid type=result stanza, so + %% we cache empty feature not to probe the client permanently + BinaryNode = node_to_binary(Caps#caps.node, SubNode), + mnesia:dirty_write(#caps_features{node_pair = BinaryNode}), + feature_request(Host, From, Caps, SubNodes). + +node_to_binary(Node, SubNode) -> + {list_to_binary(Node), list_to_binary(SubNode)}. + +features_to_binary(L) -> [list_to_binary(I) || I <- L]. +binary_to_features(L) -> [binary_to_list(I) || I <- L]. diff --git a/src/mod_pubsub/mod_pubsub.erl b/src/mod_pubsub/mod_pubsub.erl index 7bd1fae77..d8d3fdfe2 100644 --- a/src/mod_pubsub/mod_pubsub.erl +++ b/src/mod_pubsub/mod_pubsub.erl @@ -63,6 +63,7 @@ in_subscription/6, out_subscription/4, remove_user/2, + feature_check_packet/6, disco_local_identity/5, disco_local_features/5, disco_local_items/5, @@ -208,6 +209,7 @@ init([ServerHost, Opts]) -> gen_iq_handler:add_iq_handler(ejabberd_sm, ServerHost, ?NS_PUBSUB_OWNER, ?MODULE, iq_sm, IQDisc), case lists:member(?PEPNODE, Plugins) of true -> + ejabberd_hooks:add(feature_check_packet, ServerHost, ?MODULE, feature_check_packet, 75), ejabberd_hooks:add(disco_local_identity, ServerHost, ?MODULE, disco_local_identity, 75), ejabberd_hooks:add(disco_local_features, ServerHost, ?MODULE, disco_local_features, 75), ejabberd_hooks:add(disco_local_items, ServerHost, ?MODULE, disco_local_items, 75), @@ -514,18 +516,13 @@ send_loop(State) -> %% this makes that hack only work for local domain by now if not State#state.ignore_pep_from_offline -> {User, Server, Resource} = jlib:jid_tolower(JID), - case mod_caps:get_caps({User, Server, Resource}) of - nothing -> - %% we don't have caps, no need to handle PEP items - ok; - _ -> case catch ejabberd_c2s:get_subscribed(Pid) of Contacts when is_list(Contacts) -> lists:foreach( fun({U, S, R}) -> case S of ServerHost -> %% local contacts - case ejabberd_sm:get_user_resources(U, S) of + case user_resources(U, S) of [] -> %% offline PeerJID = jlib:make_jid(U, S, R), self() ! {presence, User, Server, [Resource], PeerJID}; @@ -540,8 +537,7 @@ send_loop(State) -> end, Contacts); _ -> ok - end - end; + end; true -> ok end, @@ -549,54 +545,35 @@ send_loop(State) -> {presence, User, Server, Resources, JID} -> %% get resources caps and check if processing is needed spawn(fun() -> - {HasCaps, ResourcesCaps} = lists:foldl(fun(Resource, {R, L}) -> - case mod_caps:get_caps({User, Server, Resource}) of - nothing -> {R, L}; - Caps -> {true, [{Resource, Caps} | L]} - end - end, {false, []}, Resources), - case HasCaps of - true -> - Host = State#state.host, - ServerHost = State#state.server_host, - Owner = jlib:jid_remove_resource(jlib:jid_tolower(JID)), - lists:foreach(fun(#pubsub_node{nodeid = {_, Node}, type = Type, id = NodeId, options = Options}) -> + Host = State#state.host, + ServerHost = State#state.server_host, + Owner = jlib:jid_remove_resource(jlib:jid_tolower(JID)), + lists:foreach(fun(#pubsub_node{nodeid = {_, Node}, type = Type, id = NodeId, options = Options}) -> case get_option(Options, send_last_published_item) of on_sub_and_presence -> - lists:foreach(fun({Resource, Caps}) -> - CapsNotify = case catch mod_caps:get_features(ServerHost, Caps) of - Features when is_list(Features) -> lists:member(node_to_string(Node) ++ "+notify", Features); - _ -> false - end, - case CapsNotify of - true -> - 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, NodeId, Type, LJID, last); - true -> - ok - end; - false -> - ok - end - end, ResourcesCaps); + 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, NodeId, Type, LJID, last); + true -> + ok + end + end, Resources); _ -> ok end - end, tree_action(Host, get_nodes, [Owner, JID])); - false -> - ok - end + end, tree_action(Host, get_nodes, [Owner, JID])) end), send_loop(State); stop -> @@ -878,6 +855,7 @@ terminate(_Reason, #state{host = Host, ejabberd_router:unregister_route(Host), case lists:member(?PEPNODE, Plugins) of true -> + ejabberd_hooks:delete(feature_check_packet, ServerHost, ?MODULE, feature_check_packet, 75), ejabberd_hooks:delete(disco_local_identity, ServerHost, ?MODULE, disco_local_identity, 75), ejabberd_hooks:delete(disco_local_features, ServerHost, ?MODULE, disco_local_features, 75), ejabberd_hooks:delete(disco_local_items, ServerHost, ?MODULE, disco_local_items, 75), @@ -3118,10 +3096,7 @@ broadcast_stanza(Host, Node, _NodeId, _Type, NodeOptions, SubsByDepth, NotifyTyp spawn(fun() -> LJIDs = lists:foldl(fun(R, Acc) -> LJID = {U, S, R}, - case is_caps_notify(LServer, Node, LJID) of - true -> [LJID | Acc]; - false -> Acc - end + [LJID | Acc] end, [], user_resources(U, S)), lists:foreach(fun(To) -> ejabberd_router:route(Sender, jlib:make_jid(To), Stanza) @@ -3186,24 +3161,8 @@ subscribed_nodes_by_jid(NotifyType, SubsByDepth) -> {_, JIDSubs} = lists:foldl(DepthsToDeliver, {[], []}, SubsByDepth), JIDSubs. -%% If we don't know the resource, just pick first if any -%% If no resource available, check if caps anyway (remote online) user_resources(User, Server) -> - case ejabberd_sm:get_user_resources(User, Server) of - [] -> mod_caps:get_user_resources(User, Server); - Rs -> Rs - end. - -is_caps_notify(Host, Node, LJID) -> - case mod_caps:get_caps(LJID) of - nothing -> - false; - Caps -> - case catch mod_caps:get_features(Host, Caps) of - Features when is_list(Features) -> lists:member(node_to_string(Node) ++ "+notify", Features); - _ -> false - end - end. + ejabberd_sm:get_user_resources(User, Server). %%%%%%% Configuration handling @@ -3785,3 +3744,38 @@ subid_shim(SubIDs) -> [{xmlelement, "header", [{"name", "SubID"}], [{xmlcdata, SubID}]} || SubID <- SubIDs]. +feature_check_packet(allow, _User, Server, Pres, {#jid{lserver = LServer}, _To, {xmlelement, "message", _, _} = El}, in) -> + Host = host(Server), + case LServer of + %% If the sender Server equals Host, the message comes from the Pubsub server + Host -> + allow; + %% Else, the message comes from PEP + _ -> + case xml:get_subtag(El, "event") of + {xmlelement, _, Attrs, _} = EventEl -> + case xml:get_attr_s("xmlns", Attrs) of + ?NS_PUBSUB_EVENT -> + Feature = xml:get_path_s(EventEl, [{elem, "items"}, {attr, "node"}]), + case is_feature_supported(Pres, Feature) of + true -> + allow; + false -> + deny + end; + _ -> + allow + end; + _ -> + allow + end + end; + +feature_check_packet(Acc, _User, _Server, _Pres, _Packet, _Direction) -> + Acc. + +is_feature_supported({xmlelement, "presence", _, Els}, Feature) -> + case mod_caps:read_caps(Els) of + nothing -> false; + Caps -> lists:member(Feature ++ "+notify", mod_caps:get_features(Caps)) + end.