diff --git a/src/ejabberd_c2s.erl b/src/ejabberd_c2s.erl index d51aef42b..cdb769717 100644 --- a/src/ejabberd_c2s.erl +++ b/src/ejabberd_c2s.erl @@ -1229,7 +1229,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 de313b6ca..9152b8746 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, @@ -57,24 +51,58 @@ -include_lib("exmpp/include/exmpp.hrl"). %% hook handlers --export([receive_packet/3, - receive_packet/4, - presence_probe/3, - remove_connection/3]). +-export([user_send_packet/3]). -include("ejabberd.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 @@ -94,152 +122,30 @@ 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({U, S, R}, Retry) -> - BJID = exmpp_jid:to_binary(U, S, R), - case catch mnesia:dirty_read({user_caps, BJID}) of - [#user_caps{caps=waiting}] -> - timer:sleep(2000), - get_caps({U, S, R}, Retry-1); - [#user_caps{caps=Caps}] -> - Caps; - _ -> - nothing - end. - -%% clear_caps removes user caps from database -clear_caps(JID) -> - R = exmpp_jid:prep_resource(JID), - BJID = exmpp_jid:to_binary(JID), - BUID = exmpp_jid:bare_to_binary(JID), - 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(U, S) -> - BUID = exmpp_jid:bare_to_binary(U, S), - case catch mnesia:dirty_read({user_caps_resources, BUID}) 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, Packet) when ?IS_PRESENCE(Packet) -> - case exmpp_presence:get_type(Packet) of - 'probe' -> - ok; - 'error' -> - ok; - 'invisible' -> - ok; - 'subscribe' -> - ok; - 'subscribed' -> - ok; - 'unsubscribe' -> - ok; - 'unsubscribed' -> - ok; - 'unavailable' -> - {_, S1, _} = jlib:short_prepd_jid(From), - case jlib:short_prepd_jid(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 - _ -> - ServerString = exmpp_jid:prep_domain_as_list(To), - Els = Packet#xmlel.children, - note_caps(ServerString, From, read_caps(Els)) +%%==================================================================== +%% Hooks +%%==================================================================== +user_send_packet(From, To, #xmlel{name = 'presence', attrs = Attrs, children = Els}) -> + case exmpp_jid:bare_compare(From, To) of + true -> + Type = exmpp_xml:get_attribute_from_list_as_list(Attrs, 'type', ""), + if Type == ""; Type == "available" -> + case read_caps(Els) of + nothing -> + ok; + #caps{version = Version, exts = Exts} = Caps -> + Server = exmpp_jid:prep_domain_as_list(From), + feature_request(Server, From, Caps, [Version | Exts]) + end; + true -> + ok + end; + false -> + 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, _) -> - ServerString = exmpp_jid:prep_domain_as_list(To), - wait_caps(ServerString, From). - -remove_connection(_SID, JID, _Info) -> - clear_caps(JID). - -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 %%==================================================================== @@ -247,197 +153,79 @@ binary_to_features(L) -> [binary_to_list(I) || I <- L]. 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 + mnesia:add_table_copy(caps_features, node(), disc_copies), HostB = list_to_binary(Host), - ejabberd_hooks:add(user_receive_packet, HostB, ?MODULE, receive_packet, 30), - ejabberd_hooks:add(s2s_receive_packet, HostB, ?MODULE, receive_packet, 30), - ejabberd_hooks:add(presence_probe_hook, HostB, ?MODULE, presence_probe, 20), - ejabberd_hooks:add(sm_remove_connection_hook, HostB, ?MODULE, remove_connection, 20), + ejabberd_hooks:add(user_send_packet, HostB, ?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 = exmpp_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. - %#jid{node = U, domain = S, resource = R} = From, - U = exmpp_jid:prep_node(From), - S = exmpp_jid:prep_domain(From), - R = exmpp_jid:resource(From), - BJID = exmpp_jid:to_binary(From), - mnesia:transaction(fun() -> - mnesia:dirty_write(#user_caps{jid = BJID, caps = caps_to_binary(Caps)}), - case ejabberd_sm:get_user_resources(U, S) of - [] -> - % only store resource of caps aware external contacts - BUID = exmpp_jid:bare_to_binary(From), - mnesia:dirty_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, 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(), - Query = exmpp_xml:set_attribute( - #xmlel{ns = ?NS_DISCO_INFO, name = 'query'}, - 'node', lists:concat([Node, "#", SubNode])), - Stanza = exmpp_iq:get(?NS_JABBER_CLIENT, Query, ID), - ejabberd_local:register_iq_response_handler - (list_to_binary(Host), ID, ?MODULE, handle_disco_response), - ejabberd_router:route(exmpp_jid:make(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 = exmpp_jid:to_binary(From), - mnesia:dirty_write(#user_caps{jid = BJID, caps = waiting}), - {noreply, State}; -handle_cast({disco_response, From, _To, #iq{id = ID, type = Type, payload = Payload}}, - #state{disco_requests = Requests} = State) -> - case {Type, Payload} of - {result, #xmlel{name = 'query', children = Els}} -> - case ?DICT:find(ID, Requests) of - {ok, BinaryNode} -> - Features = - lists:flatmap(fun(#xmlel{name = 'feature'} = F) -> - [exmpp_xml:get_attribute_as_list(F, 'var', "")]; - (_) -> - [] - 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", [exmpp_jid:to_list(From), SubEls]); - {result, Payload} -> - ?DEBUG("Invalid IQ contents from ~s:~n~p", [exmpp_jid:to_binary(From), Payload]); - _ -> - %% 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(list_to_binary(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_Rec) -> - Host = exmpp_jid:prep_domain_as_list(To), - Proc = gen_mod:get_module_proc(Host, ?PROCNAME), - gen_server:cast(Proc, {disco_response, From, To, IQ_Rec}). +handle_cast(_Msg, State) -> + {noreply, State}. handle_info(_Info, State) -> {noreply, State}. terminate(_Reason, State) -> HostB = list_to_binary(State#state.host), - ejabberd_hooks:delete(user_receive_packet, HostB, ?MODULE, receive_packet, 30), - ejabberd_hooks:delete(s2s_receive_packet, HostB, ?MODULE, receive_packet, 30), - ejabberd_hooks:delete(presence_probe_hook, HostB, ?MODULE, presence_probe, 20), - ejabberd_hooks:delete(sm_remove_connection_hook, HostB, ?MODULE, remove_connection, 20), + ejabberd_hooks:delete(user_send_packet, HostB, ?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, + ns = ?NS_DISCO_INFO, + payload = [#xmlel{ns = ?NS_DISCO_INFO, name = 'query', + attrs = [?XMLATTR('node', Node ++ "#" ++ SubNode)]}]}, + F = fun(IQReply) -> + feature_response( + IQReply, Host, From, Caps, SubNodes) + end, + ejabberd_local:route_iq( + exmpp_jid:make("", Host, ""), From, IQ, F); + _ -> + feature_request(Host, From, Caps, Tail) + end; +feature_request(_Host, _From, _Caps, []) -> + ok. + +feature_response(#iq{type = result, payload = El}, + Host, From, Caps, [SubNode | SubNodes]) -> + Features = lists:flatmap( + fun(#xmlel{name = 'feature', attrs = FAttrs}) -> + [exmpp_xml:get_attribute_from_list_as_list(FAttrs, 'var', "")]; + (_) -> + [] + end, El#xmlel.children), + 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 45e41ca10..2d8e4d821 100644 --- a/src/mod_pubsub/mod_pubsub.erl +++ b/src/mod_pubsub/mod_pubsub.erl @@ -64,6 +64,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, @@ -212,6 +213,7 @@ init([ServerHost, Opts]) -> ejabberd_router:register_route(Host), case lists:member(?PEPNODE, Plugins) of true -> + ejabberd_hooks:add(feature_check_packet, ServerHostB, ?MODULE, feature_check_packet, 75), ejabberd_hooks:add(disco_local_identity, ServerHostB, ?MODULE, disco_local_identity, 75), ejabberd_hooks:add(disco_local_features, ServerHostB, ?MODULE, disco_local_features, 75), ejabberd_hooks:add(disco_local_items, ServerHostB, ?MODULE, disco_local_items, 75), @@ -518,18 +520,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} = LJID, - 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 = exmpp_jid:make(U, S, R), self() ! {presence, User, Server, [Resource], PeerJID}; @@ -544,8 +541,7 @@ send_loop(State) -> end, Contacts); _ -> ok - end - end; + end; true -> ok end, @@ -553,54 +549,31 @@ 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:short_prepd_bare_jid(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 + Host = State#state.host, + Owner = jlib:short_prepd_bare_jid(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) -> + 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, ResourcesCaps); - _ -> - ok - end - end, tree_action(Host, get_nodes, [Owner, JID])); - false -> - ok - end + end, Resources); + _ -> + ok + end + end, tree_action(Host, get_nodes, [Owner, JID])) end), send_loop(State); stop -> @@ -894,6 +867,7 @@ terminate(_Reason, #state{host = Host, ServerHostB = list_to_binary(ServerHost), case lists:member(?PEPNODE, Plugins) of true -> + ejabberd_hooks:delete(feature_check_packet, ServerHostB, ?MODULE, feature_check_packet, 75), ejabberd_hooks:delete(disco_local_identity, ServerHostB, ?MODULE, disco_local_identity, 75), ejabberd_hooks:delete(disco_local_features, ServerHostB, ?MODULE, disco_local_features, 75), ejabberd_hooks:delete(disco_local_items, ServerHostB, ?MODULE, disco_local_items, 75), @@ -960,8 +934,7 @@ do_route(ServerHost, Access, Plugins, Host, From, To, Packet) -> #iq{type = get, ns = ?NS_DISCO_ITEMS, payload = SubEl} -> QAttrs = SubEl#xmlel.attrs, - Node = exmpp_xml:get_attribute_from_list_as_list(QAttrs, - 'node', ""), + Node = exmpp_xml:get_attribute_from_list_as_list(QAttrs, 'node', ""), Res = case iq_disco_items(Host, Node, From) of {result, IQRes} -> Result = #xmlel{ns = ?NS_DISCO_ITEMS, @@ -3149,7 +3122,7 @@ get_options_for_subs(NodeID, Subs) -> % {result, false} % end -broadcast_stanza(Host, Node, _NodeId, _Type, NodeOptions, SubsByDepth, NotifyType, BaseStanza, SHIM) -> +broadcast_stanza(Host, _Node, _NodeId, _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), @@ -3202,15 +3175,9 @@ broadcast_stanza(Host, Node, _NodeId, _Type, NodeOptions, SubsByDepth, NotifyTyp Contacts when is_list(Contacts) -> lists:foreach(fun({U, S, _}) -> spawn(fun() -> - Rs = lists:foldl(fun(R, Acc) -> - case is_caps_notify(LServer, Node, {U, S, R}) of - true -> [R | Acc]; - false -> Acc - end - end, [], user_resources(U, S)), lists:foreach(fun(R) -> ejabberd_router:route(Sender, exmpp_jid:make(U, S, R), Stanza) - end, Rs) + end, user_resources(U, S)) end) end, Contacts); _ -> @@ -3271,24 +3238,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 @@ -3871,3 +3822,38 @@ subid_shim(SubIDs) -> attrs = [?XMLATTR('name', <<"SubID">>)], children = [?XMLCDATA(SubID)]} || SubID <- SubIDs]. + +feature_check_packet(allow, _User, Server, Pres, {From, _To, El}, in) -> + Host = host(Server), + case exmpp_jid:prep_domain_as_list(From) 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. diff --git a/src/mod_pubsub/mod_pubsub_odbc.erl b/src/mod_pubsub/mod_pubsub_odbc.erl index 5c7d18783..dc93e4f48 100644 --- a/src/mod_pubsub/mod_pubsub_odbc.erl +++ b/src/mod_pubsub/mod_pubsub_odbc.erl @@ -64,6 +64,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, @@ -212,6 +213,7 @@ init([ServerHost, Opts]) -> ejabberd_router:register_route(Host), case lists:member(?PEPNODE, Plugins) of true -> + ejabberd_hooks:add(feature_check_packet, ServerHostB, ?MODULE, feature_check_packet, 75), ejabberd_hooks:add(disco_local_identity, ServerHostB, ?MODULE, disco_local_identity, 75), ejabberd_hooks:add(disco_local_features, ServerHostB, ?MODULE, disco_local_features, 75), ejabberd_hooks:add(disco_local_items, ServerHostB, ?MODULE, disco_local_items, 75), @@ -323,18 +325,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} = LJID, - 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 = exmpp_jid:make(U, S, R), self() ! {presence, User, Server, [Resource], PeerJID}; @@ -349,8 +346,7 @@ send_loop(State) -> end, Contacts); _ -> ok - end - end; + end; true -> ok end, @@ -358,54 +354,31 @@ 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:short_prepd_bare_jid(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 + Host = State#state.host, + Owner = jlib:short_prepd_bare_jid(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) -> + 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, ResourcesCaps); - _ -> - ok - end - end, tree_action(Host, get_nodes, [Owner, JID])); - false -> - ok - end + end, Resources); + _ -> + ok + end + end, tree_action(Host, get_nodes, [Owner, JID])) end), send_loop(State); stop -> @@ -699,6 +672,7 @@ terminate(_Reason, #state{host = Host, ServerHostB = list_to_binary(ServerHost), case lists:member(?PEPNODE, Plugins) of true -> + ejabberd_hooks:delete(feature_check_packet, ServerHostB, ?MODULE, feature_check_packet, 75), ejabberd_hooks:delete(disco_local_identity, ServerHostB, ?MODULE, disco_local_identity, 75), ejabberd_hooks:delete(disco_local_features, ServerHostB, ?MODULE, disco_local_features, 75), ejabberd_hooks:delete(disco_local_items, ServerHostB, ?MODULE, disco_local_items, 75), @@ -765,9 +739,8 @@ do_route(ServerHost, Access, Plugins, Host, From, To, Packet) -> #iq{type = get, ns = ?NS_DISCO_ITEMS, payload = SubEl} = IQ -> QAttrs = SubEl#xmlel.attrs, - Node = exmpp_xml:get_attribute_from_list_as_list(QAttrs, - 'node', ""), - Rsm = jlib:rsm_decode(IQ), + Node = exmpp_xml:get_attribute_from_list_as_list(QAttrs, 'node', ""), + Rsm = jlib:rsm_decode(IQ), Res = case iq_disco_items(Host, Node, From, Rsm) of {result, IQRes} -> Result = #xmlel{ns = ?NS_DISCO_ITEMS, @@ -2960,7 +2933,7 @@ get_options_for_subs(NodeID, Subs) -> % {result, false} % end -broadcast_stanza(Host, Node, _NodeId, _Type, NodeOptions, SubsByDepth, NotifyType, BaseStanza, SHIM) -> +broadcast_stanza(Host, _Node, _NodeId, _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), @@ -3013,15 +2986,9 @@ broadcast_stanza(Host, Node, _NodeId, _Type, NodeOptions, SubsByDepth, NotifyTyp Contacts when is_list(Contacts) -> lists:foreach(fun({U, S, _}) -> spawn(fun() -> - Rs = lists:foldl(fun(R, Acc) -> - case is_caps_notify(LServer, Node, {U, S, R}) of - true -> [R | Acc]; - false -> Acc - end - end, [], user_resources(U, S)), lists:foreach(fun(R) -> ejabberd_router:route(Sender, exmpp_jid:make(U, S, R), Stanza) - end, Rs) + end, user_resources(U, S)) end) end, Contacts); _ -> @@ -3082,24 +3049,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 @@ -3738,3 +3689,38 @@ subid_shim(SubIDs) -> attrs = [?XMLATTR('name', <<"SubID">>)], children = [?XMLCDATA(SubID)]} || SubID <- SubIDs]. + +feature_check_packet(allow, _User, Server, Pres, {From, _To, El}, in) -> + Host = host(Server), + case exmpp_jid:prep_domain_as_list(From) 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. diff --git a/src/mod_pubsub/pubsub_odbc.patch b/src/mod_pubsub/pubsub_odbc.patch index 78a9d255e..cefcfd8d2 100644 --- a/src/mod_pubsub/pubsub_odbc.patch +++ b/src/mod_pubsub/pubsub_odbc.patch @@ -1,5 +1,5 @@ ---- mod_pubsub.erl 2010-03-05 13:25:53.000000000 +0100 -+++ mod_pubsub_odbc.erl 2010-03-05 13:28:45.000000000 +0100 +--- mod_pubsub.erl 2010-03-05 15:35:25.000000000 +0100 ++++ mod_pubsub_odbc.erl 2010-03-05 15:37:52.000000000 +0100 @@ -42,7 +42,7 @@ %%% 6.2.3.1, 6.2.3.5, and 6.3. For information on subscription leases see %%% XEP-0060 section 12.18. @@ -22,7 +22,7 @@ %% exports for hooks -export([presence_probe/3, -@@ -103,7 +103,7 @@ +@@ -104,7 +104,7 @@ string_to_affiliation/1, extended_error/2, extended_error/3, @@ -31,7 +31,7 @@ ]). %% API and gen_server callbacks -@@ -122,7 +122,7 @@ +@@ -123,7 +123,7 @@ -export([send_loop/1 ]). @@ -40,7 +40,7 @@ -define(LOOPNAME, ejabberd_mod_pubsub_loop). -define(PLUGIN_PREFIX, "node_"). -define(TREE_PREFIX, "nodetree_"). -@@ -221,8 +221,6 @@ +@@ -223,8 +223,6 @@ ok end, ejabberd_router:register_route(Host), @@ -49,7 +49,7 @@ init_nodes(Host, ServerHost, NodeTree, Plugins), State = #state{host = Host, server_host = ServerHost, -@@ -281,206 +279,15 @@ +@@ -283,206 +281,15 @@ init_nodes(Host, ServerHost, _NodeTree, Plugins) -> %% TODO, this call should be done plugin side @@ -260,7 +260,7 @@ send_loop(State) -> receive -@@ -492,17 +299,15 @@ +@@ -494,17 +301,15 @@ %% for each node From is subscribed to %% and if the node is so configured, send the last published item to From lists:foreach(fun(PType) -> @@ -284,7 +284,7 @@ true -> % resource not concerned about that subscription ok -@@ -683,8 +488,7 @@ +@@ -656,8 +461,7 @@ end; disco_sm_items(Acc, From, To, NodeB, _Lang) -> @@ -294,7 +294,7 @@ %% TODO, use iq_disco_items(Host, Node, From) Host = exmpp_jid:prep_domain_as_list(To), LJID = jlib:short_prepd_bare_jid(To), -@@ -718,6 +522,7 @@ +@@ -691,6 +495,7 @@ %% ------- %% presence hooks handling functions %% @@ -302,7 +302,7 @@ presence_probe(Peer, JID, Pid) -> case exmpp_jid:full_compare(Peer, JID) of true -> %% JID are equals -@@ -784,10 +589,10 @@ +@@ -757,10 +562,10 @@ lists:foreach(fun(PType) -> {result, Subscriptions} = node_action(Host, PType, get_entity_subscriptions, [Host, Entity]), lists:foreach(fun @@ -315,22 +315,21 @@ true -> node_action(Host, PType, unsubscribe_node, [NodeId, Entity, JID, all]); false -> -@@ -958,11 +763,12 @@ +@@ -932,10 +737,11 @@ end, ejabberd_router:route(To, From, Res); #iq{type = get, ns = ?NS_DISCO_ITEMS, - payload = SubEl} -> + payload = SubEl} = IQ -> QAttrs = SubEl#xmlel.attrs, - Node = exmpp_xml:get_attribute_from_list_as_list(QAttrs, - 'node', ""), + Node = exmpp_xml:get_attribute_from_list_as_list(QAttrs, 'node', ""), - Res = case iq_disco_items(Host, Node, From) of -+ Rsm = jlib:rsm_decode(IQ), ++ Rsm = jlib:rsm_decode(IQ), + Res = case iq_disco_items(Host, Node, From, Rsm) of {result, IQRes} -> Result = #xmlel{ns = ?NS_DISCO_ITEMS, name = 'query', attrs = QAttrs, -@@ -1082,7 +888,7 @@ +@@ -1055,7 +861,7 @@ [] -> ["leaf"]; %% No sub-nodes: it's a leaf node _ -> @@ -339,7 +338,7 @@ {result, []} -> ["collection"]; {result, _} -> ["leaf", "collection"]; _ -> [] -@@ -1098,8 +904,9 @@ +@@ -1071,8 +877,9 @@ []; true -> [#xmlel{ns = ?NS_DISCO_INFO, name = 'feature', attrs = [?XMLATTR('var', ?NS_PUBSUB_s)]} | @@ -351,7 +350,7 @@ end, features(Type))] end, %% TODO: add meta-data info (spec section 5.4) -@@ -1128,8 +935,9 @@ +@@ -1101,8 +908,9 @@ #xmlel{ns = ?NS_DISCO_INFO, name = 'feature', attrs = [?XMLATTR('var', ?NS_PUBSUB_s)]}, #xmlel{ns = ?NS_DISCO_INFO, name = 'feature', attrs = [?XMLATTR('var', ?NS_ADHOC_s)]}, #xmlel{ns = ?NS_DISCO_INFO, name = 'feature', attrs = [?XMLATTR('var', ?NS_VCARD_s)]}] ++ @@ -363,7 +362,7 @@ end, features(Host, Node))}; ?NS_ADHOC_b -> command_disco_info(Host, Node, From); -@@ -1139,7 +947,7 @@ +@@ -1112,7 +920,7 @@ node_disco_info(Host, Node, From) end. @@ -372,7 +371,7 @@ case tree_action(Host, get_subnodes, [Host, <<>>, From]) of Nodes when is_list(Nodes) -> {result, lists:map( -@@ -1152,7 +960,7 @@ +@@ -1125,7 +933,7 @@ Other -> Other end; @@ -381,7 +380,7 @@ %% TODO: support localization of this string CommandItems = [ #xmlel{ns = ?NS_DISCO_ITEMS, name = 'item', -@@ -1161,10 +969,10 @@ +@@ -1134,10 +942,10 @@ ?XMLATTR('name', "Get Pending") ]}], {result, CommandItems}; @@ -394,7 +393,7 @@ case string:tokens(Item, "!") of [_SNode, _ItemID] -> {result, []}; -@@ -1172,10 +980,10 @@ +@@ -1145,10 +953,10 @@ Node = string_to_node(SNode), Action = fun(#pubsub_node{type = Type, id = NodeId}) -> @@ -408,7 +407,7 @@ end, Nodes = lists:map( fun(#pubsub_node{nodeid = {_, SubNode}}) -> -@@ -1186,9 +994,10 @@ +@@ -1159,9 +967,10 @@ Items = lists:map( fun(#pubsub_item{itemid = {RN, _}}) -> {result, Name} = node_call(Type, get_item_name, [Host, Node, RN]), @@ -421,7 +420,7 @@ end, case transaction(Host, Node, Action, sync_dirty) of {result, {_, Result}} -> {result, Result}; -@@ -1319,7 +1128,8 @@ +@@ -1292,7 +1101,8 @@ (_, Acc) -> Acc end, [], exmpp_xml:remove_cdata_from_list(Els)), @@ -431,7 +430,7 @@ {get, 'subscriptions'} -> get_subscriptions(Host, Node, From, Plugins); {get, 'affiliations'} -> -@@ -1341,8 +1151,9 @@ +@@ -1314,8 +1124,9 @@ end. iq_pubsub_owner(Host, ServerHost, From, IQType, SubEl, Lang) -> @@ -443,7 +442,7 @@ case Action of [#xmlel{name = Name, attrs = Attrs, children = Els}] -> Node = string_to_node(exmpp_xml:get_attribute_from_list_as_list(Attrs, 'node', "")), -@@ -1476,7 +1287,8 @@ +@@ -1449,7 +1260,8 @@ _ -> [] end end, @@ -453,7 +452,7 @@ sync_dirty) of {result, Res} -> Res; Err -> Err -@@ -1520,7 +1332,7 @@ +@@ -1493,7 +1305,7 @@ %%% authorization handling @@ -462,7 +461,7 @@ Lang = "en", %% TODO fix {U, S, R} = Subscriber, Stanza = #xmlel{ns = ?NS_JABBER_CLIENT, name = 'message', children = -@@ -1550,7 +1362,7 @@ +@@ -1523,7 +1335,7 @@ lists:foreach(fun(Owner) -> {U, S, R} = Owner, ejabberd_router:route(service_jid(Host), exmpp_jid:make(U, S, R), Stanza) @@ -471,7 +470,7 @@ find_authorization_response(Packet) -> Els = Packet#xmlel.children, -@@ -1592,7 +1404,7 @@ +@@ -1565,7 +1377,7 @@ end, Stanza = event_stanza( [#xmlel{ns = ?NS_PUBSUB_EVENT, name = 'subscription', attrs = @@ -480,7 +479,7 @@ }]), ejabberd_router:route(service_jid(Host), JID, Stanza). -@@ -1603,14 +1415,14 @@ +@@ -1576,14 +1388,14 @@ {{value, {_, [SNode]}}, {value, {_, [SSubscriber]}}, {value, {_, [SAllow]}}} -> Node = string_to_node(SNode), @@ -498,7 +497,7 @@ {result, Subscriptions} = node_call(Type, get_subscriptions, [NodeId, Subscriber]), if not IsApprover -> -@@ -1805,7 +1617,7 @@ +@@ -1778,7 +1590,7 @@ end, Reply = #xmlel{ns = ?NS_PUBSUB, name = 'pubsub', children = [#xmlel{ns = ?NS_PUBSUB, name = 'create', attrs = nodeAttr(Node)}]}, @@ -507,7 +506,7 @@ {result, {Result, broadcast}} -> %%Lang = "en", %% TODO: fix %%OwnerKey = jlib:jid_tolower(jlib:jid_remove_resource(Owner)), -@@ -1914,7 +1726,7 @@ +@@ -1887,7 +1699,7 @@ %%
  • The node does not exist.
  • %% subscribe_node(Host, Node, From, JID, Configuration) -> @@ -516,7 +515,7 @@ {result, GoodSubOpts} -> GoodSubOpts; _ -> invalid end, -@@ -1924,7 +1736,7 @@ +@@ -1897,7 +1709,7 @@ _:_ -> {undefined, undefined, undefined} end, @@ -525,7 +524,7 @@ Features = features(Type), SubscribeFeature = lists:member("subscribe", Features), OptionsFeature = lists:member("subscription-options", Features), -@@ -1943,9 +1755,13 @@ +@@ -1916,9 +1728,13 @@ {"", "", ""} -> {false, false}; _ -> @@ -542,7 +541,7 @@ end end, if -@@ -2278,7 +2094,7 @@ +@@ -2251,7 +2067,7 @@ %%

    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. @@ -551,7 +550,7 @@ MaxItems = if SMaxItems == "" -> get_max_items_node(Host); -@@ -2317,11 +2133,11 @@ +@@ -2290,11 +2106,11 @@ node_call(Type, get_items, [NodeId, From, AccessModel, PresenceSubscription, RosterGroup, @@ -565,7 +564,7 @@ SendItems = case ItemIDs of [] -> Items; -@@ -2334,7 +2150,7 @@ +@@ -2307,7 +2123,7 @@ %% number of items sent to MaxItems: {result, #xmlel{ns = ?NS_PUBSUB, name = 'pubsub', children = [#xmlel{ns = ?NS_PUBSUB, name = 'items', attrs = nodeAttr(Node), children = @@ -574,7 +573,7 @@ Error -> Error end -@@ -2366,17 +2182,29 @@ +@@ -2339,17 +2155,29 @@ %% @doc

    Resend the items of a node to the user.

    %% @todo use cache-last-item feature send_items(Host, Node, NodeId, Type, LJID, last) -> @@ -611,7 +610,7 @@ send_items(Host, Node, NodeId, Type, {LU, LS, LR} = LJID, Number) -> ToSend = case node_action(Host, Type, get_items, [NodeId, LJID]) of {result, []} -> -@@ -2506,29 +2334,12 @@ +@@ -2479,29 +2307,12 @@ error -> {error, 'bad-request'}; _ -> @@ -644,7 +643,7 @@ end, Entities), {result, []}; _ -> -@@ -2583,11 +2394,11 @@ +@@ -2556,11 +2367,11 @@ end. read_sub(Subscriber, Node, NodeID, SubID, Lang) -> @@ -658,7 +657,7 @@ OptionsEl = #xmlel{ns = ?NS_PUBSUB, name = 'options', attrs = [ ?XMLATTR('jid', exmpp_jid:to_binary(Subscriber)), ?XMLATTR('Subid', SubID) | nodeAttr(Node)], -@@ -2614,7 +2425,7 @@ +@@ -2587,7 +2398,7 @@ end. set_options_helper(Configuration, JID, NodeID, SubID, Type) -> @@ -667,7 +666,7 @@ {result, GoodSubOpts} -> GoodSubOpts; _ -> invalid end, -@@ -2644,7 +2455,7 @@ +@@ -2617,7 +2428,7 @@ write_sub(_Subscriber, _NodeID, _SubID, invalid) -> {error, extended_error('bad-request', "invalid-options")}; write_sub(Subscriber, NodeID, SubID, Options) -> @@ -676,7 +675,7 @@ {error, notfound} -> {error, extended_error('not-acceptable', "invalid-subid")}; {result, _} -> -@@ -2817,8 +2628,8 @@ +@@ -2790,8 +2601,8 @@ ?XMLATTR('subsription', subscription_to_string(Sub)) | nodeAttr(Node)]}]}]}, ejabberd_router:route(service_jid(Host), JID, Stanza) end, @@ -687,7 +686,7 @@ true -> Result = lists:foldl(fun({JID, Subscription, SubId}, Acc) -> -@@ -3107,7 +2918,7 @@ +@@ -3080,7 +2891,7 @@ {Depth, [{N, get_node_subs(N)} || N <- Nodes]} end, tree_call(Host, get_parentnodes_tree, [Host, Node, service_jid(Host)]))} end, @@ -696,7 +695,7 @@ {result, CollSubs} -> CollSubs; _ -> [] end. -@@ -3121,9 +2932,9 @@ +@@ -3094,9 +2905,9 @@ get_options_for_subs(NodeID, Subs) -> lists:foldl(fun({JID, subscribed, SubID}, Acc) -> @@ -708,7 +707,7 @@ _ -> Acc end; (_, Acc) -> -@@ -3131,7 +2942,7 @@ +@@ -3104,7 +2915,7 @@ end, [], Subs). % TODO: merge broadcast code that way @@ -717,7 +716,7 @@ %broadcast(Host, Node, NodeId, Type, NodeOptions, Feature, Force, ElName, SubEls) -> % case (get_option(NodeOptions, Feature) or Force) of % true -> -@@ -3352,6 +3163,30 @@ +@@ -3303,6 +3114,30 @@ Result end. @@ -748,7 +747,7 @@ %% @spec (Host, Options) -> MaxItems %% Host = host() %% Options = [Option] -@@ -3747,7 +3582,13 @@ +@@ -3698,7 +3533,13 @@ tree_action(Host, Function, Args) -> ?DEBUG("tree_action ~p ~p ~p",[Host,Function,Args]), Fun = fun() -> tree_call(Host, Function, Args) end, @@ -763,7 +762,7 @@ %% @doc

    node plugin call.

    node_call(Type, Function, Args) -> -@@ -3767,13 +3608,13 @@ +@@ -3718,13 +3559,13 @@ node_action(Host, Type, Function, Args) -> ?DEBUG("node_action ~p ~p ~p ~p",[Host,Type,Function,Args]), @@ -779,7 +778,7 @@ case tree_call(Host, get_node, [Host, Node]) of N when is_record(N, pubsub_node) -> case Action(N) of -@@ -3786,8 +3627,15 @@ +@@ -3737,8 +3578,15 @@ end end, Trans). @@ -797,7 +796,7 @@ {result, Result} -> {result, Result}; {error, Error} -> {error, Error}; {atomic, {result, Result}} -> {result, Result}; -@@ -3795,6 +3643,15 @@ +@@ -3746,6 +3594,15 @@ {aborted, Reason} -> ?ERROR_MSG("transaction return internal error: ~p~n", [{aborted, Reason}]), {error, 'internal-server-error'}; @@ -813,7 +812,7 @@ {'EXIT', Reason} -> ?ERROR_MSG("transaction return internal error: ~p~n", [{'EXIT', Reason}]), {error, 'internal-server-error'}; -@@ -3803,6 +3660,16 @@ +@@ -3754,6 +3611,16 @@ {error, 'internal-server-error'} end.