aboutsummaryrefslogtreecommitdiff
path: root/src/mod_delegation.erl
diff options
context:
space:
mode:
Diffstat (limited to 'src/mod_delegation.erl')
-rw-r--r--src/mod_delegation.erl854
1 files changed, 339 insertions, 515 deletions
diff --git a/src/mod_delegation.erl b/src/mod_delegation.erl
index f2d1a13b5..2cf9525fc 100644
--- a/src/mod_delegation.erl
+++ b/src/mod_delegation.erl
@@ -1,538 +1,362 @@
-%%%--------------------------------------------------------------------------------------
+%%%-------------------------------------------------------------------
%%% File : mod_delegation.erl
%%% Author : Anna Mukharram <amuhar3@gmail.com>
-%%% Purpose : This module is an implementation for XEP-0355: Namespace Delegation
-%%%--------------------------------------------------------------------------------------
-
+%%% Purpose : XEP-0355: Namespace Delegation
+%%%
+%%%
+%%% ejabberd, Copyright (C) 2002-2016 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_delegation).
-author('amuhar3@gmail.com').
--behaviour(gen_mod).
-
-protocol({xep, 0355, '0.3'}).
--export([start/2, stop/1, depends/2, mod_opt_type/1]).
-
--export([advertise_delegations/1, process_iq/3,
- disco_local_features/5, disco_sm_features/5,
- disco_local_identity/5, disco_sm_identity/5, disco_info/5, clean/0]).
-
--include_lib("stdlib/include/ms_transform.hrl").
-
--include("ejabberd_service.hrl").
-
--define(CLEAN_INTERVAL, timer:minutes(10)).
-
-%%%--------------------------------------------------------------------------------------
-%%% API
-%%%--------------------------------------------------------------------------------------
+-behaviour(gen_server).
+-behaviour(gen_mod).
-start(Host, _Opts) ->
- mod_disco:register_feature(Host, ?NS_DELEGATION),
- %% start timer for hooks_tmp table cleaning
- timer:apply_after(?CLEAN_INTERVAL, ?MODULE, clean, []),
+%% API
+-export([start_link/2]).
+-export([start/2, stop/1, mod_opt_type/1, depends/2]).
+%% gen_server callbacks
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
+ terminate/2, code_change/3]).
+-export([component_connected/1, component_disconnected/2,
+ ejabberd_local/1, ejabberd_sm/1, decode_iq_subel/1,
+ disco_local_features/5, disco_sm_features/5,
+ disco_local_identity/5, disco_sm_identity/5]).
+
+-include("ejabberd.hrl").
+-include("logger.hrl").
+-include("xmpp.hrl").
+
+-type disco_acc() :: {error, stanza_error()} | {result, [binary()]} | empty.
+-record(state, {server_host = <<"">> :: binary(),
+ delegations = dict:new() :: ?TDICT}).
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+start_link(Host, Opts) ->
+ Proc = gen_mod:get_module_proc(Host, ?MODULE),
+ gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []).
+
+start(Host, Opts) ->
+ Proc = gen_mod:get_module_proc(Host, ?MODULE),
+ PingSpec = {Proc, {?MODULE, start_link, [Host, Opts]},
+ transient, 2000, worker, [?MODULE]},
+ supervisor:start_child(ejabberd_sup, PingSpec).
+stop(Host) ->
+ Proc = gen_mod:get_module_proc(Host, ?MODULE),
+ gen_server:call(Proc, stop),
+ supervisor:delete_child(ejabberd_sup, Proc).
+
+mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
+mod_opt_type(namespaces) -> validate_fun();
+mod_opt_type(_) ->
+ [namespaces, iqdisc].
+
+depends(_, _) ->
+ [].
+
+-spec decode_iq_subel(xmpp_element()) -> xmpp_element();
+ (xmlel()) -> xmlel().
+%% Tell gen_iq_handler not to auto-decode IQ payload
+decode_iq_subel(El) ->
+ El.
+
+-spec component_connected(binary()) -> ok.
+component_connected(Host) ->
+ lists:foreach(
+ fun(ServerHost) ->
+ Proc = gen_mod:get_module_proc(ServerHost, ?MODULE),
+ gen_server:cast(Proc, {component_connected, Host})
+ end, ?MYHOSTS).
+
+-spec component_disconnected(binary(), binary()) -> ok.
+component_disconnected(Host, _Reason) ->
+ lists:foreach(
+ fun(ServerHost) ->
+ Proc = gen_mod:get_module_proc(ServerHost, ?MODULE),
+ gen_server:cast(Proc, {component_disconnected, Host})
+ end, ?MYHOSTS).
+
+-spec ejabberd_local(iq()) -> iq().
+ejabberd_local(IQ) ->
+ process_iq(IQ, ejabberd_local).
+
+-spec ejabberd_sm(iq()) -> iq().
+ejabberd_sm(IQ) ->
+ process_iq(IQ, ejabberd_sm).
+
+-spec disco_local_features(disco_acc(), jid(), jid(), binary(), binary()) -> disco_acc().
+disco_local_features(Acc, From, To, Node, Lang) ->
+ disco_features(Acc, From, To, Node, Lang, ejabberd_local).
+
+-spec disco_sm_features(disco_acc(), jid(), jid(), binary(), binary()) -> disco_acc().
+disco_sm_features(Acc, From, To, Node, Lang) ->
+ disco_features(Acc, From, To, Node, Lang, ejabberd_sm).
+
+-spec disco_local_identity(disco_acc(), jid(), jid(), binary(), binary()) -> disco_acc().
+disco_local_identity(Acc, From, To, Node, Lang) ->
+ disco_identity(Acc, From, To, Node, Lang, ejabberd_local).
+
+-spec disco_sm_identity(disco_acc(), jid(), jid(), binary(), binary()) -> disco_acc().
+disco_sm_identity(Acc, From, To, Node, Lang) ->
+ disco_identity(Acc, From, To, Node, Lang, ejabberd_sm).
+
+%%%===================================================================
+%%% gen_server callbacks
+%%%===================================================================
+init([Host, _Opts]) ->
+ ejabberd_hooks:add(component_connected, ?MODULE,
+ component_connected, 50),
+ ejabberd_hooks:add(component_disconnected, ?MODULE,
+ component_disconnected, 50),
ejabberd_hooks:add(disco_local_features, Host, ?MODULE,
- disco_local_features, 500), %% This hook should be the last
+ disco_local_features, 50),
+ ejabberd_hooks:add(disco_sm_features, Host, ?MODULE,
+ disco_sm_features, 50),
ejabberd_hooks:add(disco_local_identity, Host, ?MODULE,
- disco_local_identity, 500),
+ disco_local_identity, 50),
ejabberd_hooks:add(disco_sm_identity, Host, ?MODULE,
- disco_sm_identity, 500),
- ejabberd_hooks:add(disco_sm_features, Host, ?MODULE,
- disco_sm_features, 500),
- ejabberd_hooks:add(disco_info, Host, ?MODULE,
- disco_info, 500).
-
-
-stop(Host) ->
- mod_disco:unregister_feature(Host, ?NS_DELEGATION),
- ejabberd_hooks:delete(disco_local_features, Host, ?MODULE,
- disco_local_features, 500),
- ejabberd_hooks:delete(disco_local_identity, Host, ?MODULE,
- disco_local_identity, 500),
- ejabberd_hooks:delete(disco_sm_identity, Host, ?MODULE,
- disco_sm_identity, 500),
- ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE,
- disco_sm_features, 500),
- ejabberd_hooks:delete(disco_info, Host, ?MODULE,
- disco_info, 500).
-
-depends(_Host, _Opts) -> [].
-
-mod_opt_type(_Opt) -> [].
-
-%%%--------------------------------------------------------------------------------------
-%%% 4.2 Functions to advertise service of delegated namespaces
-%%%--------------------------------------------------------------------------------------
-attribute_tag(Attrs) ->
- lists:map(fun(Attr) ->
- #xmlel{name = <<"attribute">>, attrs = [{<<"name">> , Attr}]}
- end, Attrs).
-
-delegations(From, To, Delegations) ->
- {Elem0, DelegatedNs} =
- lists:foldl(fun({Ns, FiltAttr}, {Acc, AccNs}) ->
- case ets:insert_new(delegated_namespaces,
- {Ns, FiltAttr, self(), To, {}, {}}) of
- true ->
- Attrs =
- if
- FiltAttr == [] ->
- ?DEBUG("namespace ~s is delegated to ~s with"
- " no filtering attributes ~n",[Ns, To]),
- [];
- true ->
- ?DEBUG("namespace ~s is delegated to ~s with"
- " ~p filtering attributes ~n",[Ns, To, FiltAttr]),
- attribute_tag(FiltAttr)
- end,
- add_iq_handlers(Ns),
- {[#xmlel{name = <<"delegated">>,
- attrs = [{<<"namespace">>, Ns}],
- children = Attrs}| Acc], [{Ns, FiltAttr}|AccNs]};
- false -> {Acc, AccNs}
- end
- end, {[], []}, Delegations),
- case Elem0 of
- [] -> {ignore, DelegatedNs};
- _ ->
- Elem1 = #xmlel{name = <<"delegation">>,
- attrs = [{<<"xmlns">>, ?NS_DELEGATION}],
- children = Elem0},
- Id = randoms:get_string(),
- {#xmlel{name = <<"message">>,
- attrs = [{<<"id">>, Id}, {<<"from">>, From}, {<<"to">>, To}],
- children = [Elem1]}, DelegatedNs}
- end.
-
-add_iq_handlers(Ns) ->
- lists:foreach(fun(Host) ->
- IQDisc =
- gen_mod:get_module_opt(Host, ?MODULE, iqdisc,
- fun gen_iq_handler:check_type/1, one_queue),
- gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
- Ns, ?MODULE,
- process_iq, IQDisc),
- gen_iq_handler:add_iq_handler(ejabberd_local, Host,
- Ns, ?MODULE,
- process_iq, IQDisc)
- end, ?MYHOSTS).
-
-advertise_delegations(#state{delegations = []}) -> [];
-advertise_delegations(StateData) ->
- {Delegated, DelegatedNs} =
- delegations(?MYNAME, StateData#state.host, StateData#state.delegations),
- if
- Delegated /= ignore ->
- ejabberd_service:send_element(StateData, Delegated),
- % server asks available features for delegated namespaces
- disco_info(StateData#state{delegations = DelegatedNs});
- true -> ok
- end,
- DelegatedNs.
-
-%%%--------------------------------------------------------------------------------------
-%%% Delegated namespaces hook
-%%%--------------------------------------------------------------------------------------
-
-check_filter_attr([], _Children) -> true;
-check_filter_attr(_FilterAttr, []) -> false;
-check_filter_attr(FilterAttr, [#xmlel{} = Stanza|_]) ->
- Attrs = proplists:get_keys(Stanza#xmlel.attrs),
- lists:all(fun(Attr) ->
- lists:member(Attr, Attrs)
- end, FilterAttr);
-check_filter_attr(_FilterAttr, _Children) -> false.
-
--spec get_client_server([attr()]) -> {jid(), jid()}.
-
-get_client_server(Attrs) ->
- Client = fxml:get_attr_s(<<"from">>, Attrs),
- ClientJID = jid:from_string(Client),
- ServerJID = jid:from_string(ClientJID#jid.lserver),
- {ClientJID, ServerJID}.
-
-decapsulate_result(#xmlel{children = []}) -> ok;
-decapsulate_result(#xmlel{children = Children}) ->
- decapsulate_result0(Children).
-
-decapsulate_result0([]) -> ok;
-decapsulate_result0([#xmlel{name = <<"delegation">>,
- attrs = [{<<"xmlns">>, ?NS_DELEGATION}]} = Packet]) ->
- decapsulate_result1(Packet#xmlel.children);
-decapsulate_result0(_Children) -> ok.
-
-decapsulate_result1([]) -> ok;
-decapsulate_result1([#xmlel{name = <<"forwarded">>,
- attrs = [{<<"xmlns">>, ?NS_FORWARD}]} = Packet]) ->
- decapsulate_result2(Packet#xmlel.children);
-decapsulate_result1(_Children) -> ok.
-
-decapsulate_result2([]) -> ok;
-decapsulate_result2([#xmlel{name = <<"iq">>, attrs = Attrs} = Packet]) ->
- Ns = fxml:get_attr_s(<<"xmlns">>, Attrs),
- if
- Ns /= <<"jabber:client">> ->
- ok;
- true -> Packet
+ disco_sm_identity, 50),
+ {ok, #state{server_host = Host}}.
+
+handle_call(get_delegations, _From, State) ->
+ {reply, {ok, State#state.delegations}, State};
+handle_call(_Request, _From, State) ->
+ Reply = ok,
+ {reply, Reply, State}.
+
+handle_cast({component_connected, Host}, State) ->
+ ServerHost = State#state.server_host,
+ To = jid:make(Host),
+ NSAttrsAccessList = gen_mod:get_module_opt(
+ ServerHost, ?MODULE, namespaces,
+ validate_fun(), []),
+ lists:foreach(
+ fun({NS, _Attrs, Access}) ->
+ case acl:match_rule(ServerHost, Access, To) of
+ allow ->
+ send_disco_queries(ServerHost, Host, NS);
+ deny ->
+ ok
+ end
+ end, NSAttrsAccessList),
+ {noreply, State};
+handle_cast({disco_info, Type, Host, NS, Info}, State) ->
+ From = jid:make(State#state.server_host),
+ To = jid:make(Host),
+ case dict:find({NS, Type}, State#state.delegations) of
+ error ->
+ Msg = #message{from = From, to = To,
+ sub_els = [#delegation{delegated = [#delegated{ns = NS}]}]},
+ Delegations = dict:store({NS, Type}, {Host, Info}, State#state.delegations),
+ gen_iq_handler:add_iq_handler(Type, State#state.server_host, NS,
+ ?MODULE, Type, one_queue),
+ ejabberd_router:route(From, To, Msg),
+ ?INFO_MSG("Namespace '~s' is delegated to external component '~s'",
+ [NS, Host]),
+ {noreply, State#state{delegations = Delegations}};
+ {ok, {AnotherHost, _}} ->
+ ?WARNING_MSG("Failed to delegate namespace '~s' to "
+ "external component '~s' because it's already "
+ "delegated to '~s'",
+ [NS, Host, AnotherHost]),
+ {noreply, State}
end;
-decapsulate_result2(_Children) -> ok.
-
--spec check_iq(xmlel(), xmlel()) -> xmlel() | ignore.
-
-check_iq(#xmlel{attrs = Attrs} = Packet,
- #xmlel{attrs = AttrsOrigin} = OriginPacket) ->
- % Id attribute of OriginPacket Must be equil to Packet Id attribute
- Id1 = fxml:get_attr_s(<<"id">>, Attrs),
- Id2 = fxml:get_attr_s(<<"id">>, AttrsOrigin),
- % From attribute of OriginPacket Must be equil to Packet To attribute
- From = fxml:get_attr_s(<<"from">>, AttrsOrigin),
- To = fxml:get_attr_s(<<"to">>, Attrs),
- % Type attribute Must be error or result
- Type = fxml:get_attr_s(<<"type">>, Attrs),
- if
- ((Type == <<"result">>) or (Type == <<"error">>)),
- Id1 == Id2, To == From ->
- NewPacket = jlib:remove_attr(<<"xmlns">>, Packet),
- %% We can send the decapsulated stanza from Server to Client (To)
- NewPacket;
- true ->
- %% service-unavailable
- Err = jlib:make_error_reply(OriginPacket, ?ERR_SERVICE_UNAVAILABLE),
- Err
- end;
-check_iq(_Packet, _OriginPacket) -> ignore.
-
--spec manage_service_result(atom(), atom(), binary(), xmlel()) -> ok.
-
-manage_service_result(HookRes, HookErr, Service, OriginPacket) ->
- fun(Packet) ->
- {ClientJID, ServerJID} = get_client_server(OriginPacket#xmlel.attrs),
- Server = ClientJID#jid.lserver,
-
- ets:delete(hooks_tmp, {HookRes, Server}),
- ets:delete(hooks_tmp, {HookErr, Server}),
- % Check Packet "from" attribute
- % It Must be equil to current service host
- From = fxml:get_attr_s(<<"from">> , Packet#xmlel.attrs),
- if
- From == Service ->
- % decapsulate iq result
- ResultIQ = decapsulate_result(Packet),
- ServResponse = check_iq(ResultIQ, OriginPacket),
- if
- ServResponse /= ignore ->
- ejabberd_router:route(ServerJID, ClientJID, ServResponse);
- true -> ok
- end;
- true ->
- % service unavailable
- Err = jlib:make_error_reply(OriginPacket, ?ERR_SERVICE_UNAVAILABLE),
- ejabberd_router:route(ServerJID, ClientJID, Err)
- end
+handle_cast({component_disconnected, Host}, State) ->
+ ServerHost = State#state.server_host,
+ Delegations =
+ dict:filter(
+ fun({NS, Type}, {H, _}) when H == Host ->
+ ?INFO_MSG("Remove delegation of namespace '~s' "
+ "from external component '~s'",
+ [NS, Host]),
+ gen_iq_handler:remove_iq_handler(Type, ServerHost, NS),
+ false;
+ (_, _) ->
+ true
+ end, State#state.delegations),
+ {noreply, State#state{delegations = Delegations}};
+handle_cast(_Msg, State) ->
+ {noreply, State}.
+
+handle_info(_Info, State) ->
+ {noreply, State}.
+
+terminate(_Reason, State) ->
+ %% Note: we don't remove component_* hooks because they are global
+ %% and might be registered within a module on another virtual host
+ ServerHost = State#state.server_host,
+ ejabberd_hooks:delete(disco_local_features, ServerHost, ?MODULE,
+ disco_local_features, 50),
+ ejabberd_hooks:delete(disco_sm_features, ServerHost, ?MODULE,
+ disco_sm_features, 50),
+ ejabberd_hooks:delete(disco_local_identity, ServerHost, ?MODULE,
+ disco_local_identity, 50),
+ ejabberd_hooks:delete(disco_sm_identity, ServerHost, ?MODULE,
+ disco_sm_identity, 50),
+ lists:foreach(
+ fun({NS, Type}) ->
+ gen_iq_handler:remove_iq_handler(Type, ServerHost, NS)
+ end, dict:fetch_keys(State#state.delegations)).
+
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+-spec get_delegations(binary()) -> ?TDICT.
+get_delegations(Host) ->
+ Proc = gen_mod:get_module_proc(Host, ?MODULE),
+ try gen_server:call(Proc, get_delegations) of
+ {ok, Delegations} -> Delegations
+ catch exit:{noproc, _} ->
+ %% No module is loaded for this virtual host
+ dict:new()
end.
--spec manage_service_error(atom(), atom(), xmlel()) -> ok.
-
-manage_service_error(HookRes, HookErr, OriginPacket) ->
- fun(_Packet) ->
- {ClientJID, ServerJID} = get_client_server(OriginPacket#xmlel.attrs),
- Server = ClientJID#jid.lserver,
- ets:delete(hooks_tmp, {HookRes, Server}),
- ets:delete(hooks_tmp, {HookErr, Server}),
- Err = jlib:make_error_reply(OriginPacket, ?ERR_SERVICE_UNAVAILABLE),
- ejabberd_router:route(ServerJID, ClientJID, Err)
+-spec process_iq(iq(), ejabberd_local | ejabberd_sm) -> ignore | iq().
+process_iq(#iq{to = To, lang = Lang, sub_els = [SubEl]} = IQ, Type) ->
+ LServer = To#jid.lserver,
+ NS = xmpp:get_ns(SubEl),
+ Delegations = get_delegations(LServer),
+ case dict:find({NS, Type}, Delegations) of
+ {ok, {Host, _}} ->
+ Delegation = #delegation{
+ forwarded = #forwarded{xml_els = [xmpp:encode(IQ)]}},
+ NewFrom = jid:make(LServer),
+ NewTo = jid:make(Host),
+ ejabberd_local:route_iq(
+ NewFrom, NewTo,
+ #iq{type = set,
+ from = NewFrom,
+ to = NewTo,
+ sub_els = [Delegation]},
+ fun(Result) -> process_iq_result(IQ, Result) end),
+ ignore;
+ error ->
+ Txt = <<"Failed to map delegated namespace to external component">>,
+ xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang))
end.
-
--spec forward_iq(binary(), binary(), xmlel()) -> ok.
-
-forward_iq(Server, Service, Packet) ->
- Elem0 = #xmlel{name = <<"forwarded">>,
- attrs = [{<<"xmlns">>, ?NS_FORWARD}], children = [Packet]},
- Elem1 = #xmlel{name = <<"delegation">>,
- attrs = [{<<"xmlns">>, ?NS_DELEGATION}], children = [Elem0]},
- Id = randoms:get_string(),
- Elem2 = #xmlel{name = <<"iq">>,
- attrs = [{<<"from">>, Server}, {<<"to">>, Service},
- {<<"type">>, <<"set">>}, {<<"id">>, Id}],
- children = [Elem1]},
-
- HookRes = {iq, result, Id},
- HookErr = {iq, error, Id},
-
- FunRes = manage_service_result(HookRes, HookErr, Service, Packet),
- FunErr = manage_service_error(HookRes, HookErr, Packet),
-
- Timestamp = p1_time_compat:system_time(seconds),
- ets:insert(hooks_tmp, {{HookRes, Server}, FunRes, Timestamp}),
- ets:insert(hooks_tmp, {{HookErr, Server}, FunErr, Timestamp}),
-
- From = jid:make(<<"">>, Server, <<"">>),
- To = jid:make(<<"">>, Service, <<"">>),
- ejabberd_router:route(From, To, Elem2).
-
-process_iq(From, #jid{lresource = <<"">>} = To,
- #iq{type = Type, xmlns = XMLNS} = IQ) ->
- %% check if stanza directed to server
- %% or directed to the bare JID of the sender
- case ((Type == get) or (Type == set)) of
- true ->
- Packet = jlib:iq_to_xml(IQ),
- #xmlel{name = <<"iq">>, attrs = Attrs, children = Children} = Packet,
- AttrsNew = [{<<"xmlns">>, <<"jabber:client">>} | Attrs],
- AttrsNew2 = jlib:replace_from_to_attrs(jid:to_string(From),
- jid:to_string(To), AttrsNew),
- case ets:lookup(delegated_namespaces, XMLNS) of
- [{XMLNS, FiltAttr, _Pid, ServiceHost, _, _}] ->
- case check_filter_attr(FiltAttr, Children) of
- true ->
- forward_iq(From#jid.server, ServiceHost,
- Packet#xmlel{attrs = AttrsNew2});
- _ -> ok
- end;
- [] -> ok
- end,
- ignore;
- _ ->
- ignore
+-spec process_iq_result(iq(), iq()) -> ok.
+process_iq_result(#iq{from = From, to = To, id = ID, lang = Lang} = IQ,
+ #iq{type = result} = ResIQ) ->
+ try
+ #delegation{forwarded = #forwarded{xml_els = [SubEl]}} =
+ xmpp:get_subtag(ResIQ, #delegation{}),
+ case xmpp:decode(SubEl, ?NS_CLIENT, [ignore_els]) of
+ #iq{from = To, to = From, type = Type, id = ID} = Reply
+ when Type == error; Type == result ->
+ ejabberd_router:route(To, From, Reply)
+ end
+ catch _:_ ->
+ ?ERROR_MSG("got iq-result with invalid delegated "
+ "payload:~n~s", [xmpp:pp(ResIQ)]),
+ Txt = <<"External component failure">>,
+ Err = xmpp:err_internal_server_error(Txt, Lang),
+ ejabberd_router:route_error(To, From, IQ, Err)
end;
-process_iq(_From, _To, _IQ) -> ignore.
-
-%%%--------------------------------------------------------------------------------------
-%%% 7. Discovering Support
-%%%--------------------------------------------------------------------------------------
-
-decapsulate_features(#xmlel{attrs = Attrs} = Packet, Node) ->
- case fxml:get_attr_s(<<"node">>, Attrs) of
- Node ->
- PREFIX = << ?NS_DELEGATION/binary, "::" >>,
- Size = byte_size(PREFIX),
- BARE_PREFIX = << ?NS_DELEGATION/binary, ":bare:" >>,
- SizeBare = byte_size(BARE_PREFIX),
-
- Features = [Feat || #xmlel{attrs = [{<<"var">>, Feat}]} <-
- fxml:get_subtags(Packet, <<"feature">>)],
-
- Identity = [I || I <- fxml:get_subtags(Packet, <<"identity">>)],
-
- Exten = [I || I <- fxml:get_subtags_with_xmlns(Packet, <<"x">>, ?NS_XDATA)],
-
- case Node of
- << PREFIX:Size/binary, NS/binary >> ->
- ets:update_element(delegated_namespaces, NS,
- {5, {Features, Identity, Exten}});
- << BARE_PREFIX:SizeBare/binary, NS/binary >> ->
- ets:update_element(delegated_namespaces, NS,
- {6, {Features, Identity, Exten}});
- _ -> ok
- end;
- _ -> ok
- end;
-decapsulate_features(_Packet, _Node) -> ok.
-
--spec disco_result(atom(), atom(), binary()) -> ok.
-
-disco_result(HookRes, HookErr, Node) ->
- fun(Packet) ->
- Tag = fxml:get_subtag_with_xmlns(Packet, <<"query">>, ?NS_DISCO_INFO),
- decapsulate_features(Tag, Node),
-
- ets:delete(hooks_tmp, {HookRes, ?MYNAME}),
- ets:delete(hooks_tmp, {HookErr, ?MYNAME})
- end.
-
--spec disco_error(atom(), atom()) -> ok.
-
-disco_error(HookRes, HookErr) ->
- fun(_Packet) ->
- ets:delete(hooks_tmp, {HookRes, ?MYNAME}),
- ets:delete(hooks_tmp, {HookErr, ?MYNAME})
- end.
-
--spec disco_info(state()) -> ok.
-
-disco_info(StateData) ->
- disco_info(StateData, <<"::">>),
- disco_info(StateData, <<":bare:">>).
-
--spec disco_info(state(), binary()) -> ok.
-
-disco_info(StateData, Sep) ->
- lists:foreach(fun({Ns, _FilterAttr}) ->
- Id = randoms:get_string(),
- Node = << ?NS_DELEGATION/binary, Sep/binary, Ns/binary >>,
-
- HookRes = {iq, result, Id},
- HookErr = {iq, error, Id},
-
- FunRes = disco_result(HookRes, HookErr, Node),
- FunErr = disco_error(HookRes, HookErr),
-
- Timestamp = p1_time_compat:system_time(seconds),
- ets:insert(hooks_tmp, {{HookRes, ?MYNAME}, FunRes, Timestamp}),
- ets:insert(hooks_tmp, {{HookErr, ?MYNAME}, FunErr, Timestamp}),
-
- Tag = #xmlel{name = <<"query">>,
- attrs = [{<<"xmlns">>, ?NS_DISCO_INFO},
- {<<"node">>, Node}],
- children = []},
- DiscoReq = #xmlel{name = <<"iq">>,
- attrs = [{<<"type">>, <<"get">>}, {<<"id">>, Id},
- {<<"from">>, ?MYNAME},
- {<<"to">>, StateData#state.host }],
- children = [Tag]},
- ejabberd_service:send_element(StateData, DiscoReq)
-
- end, StateData#state.delegations).
-
-
-disco_features(Acc, Bare) ->
- Fun = fun(Feat) ->
- ets:foldl(fun({Ns, _, _, _, _, _}, A) ->
- A or str:prefix(Ns, Feat)
- end, false, delegated_namespaces)
- end,
- % delete feature namespace which is delegated to service
- Features = lists:filter(fun ({{Feature, _Host}}) ->
- not Fun(Feature);
- (Feature) when is_binary(Feature) ->
- not Fun(Feature)
- end, Acc),
- % add service features
- FeaturesList =
- ets:foldl(fun({_, _, _, _, {Feats, _, _}, {FeatsBare, _, _}}, A) ->
- if
- Bare -> A ++ FeatsBare;
- true -> A ++ Feats
- end;
- (_, A) -> A
- end, Features, delegated_namespaces),
- {result, FeaturesList}.
-
-disco_identity(Acc, Bare) ->
- % filter delegated identites
- Fun = fun(Ident) ->
- ets:foldl(fun({_, _, _, _, {_ , I, _}, {_ , IBare, _}}, A) ->
- Identity =
- if
- Bare -> IBare;
- true -> I
- end,
- (fxml:get_attr_s(<<"category">> , Ident) ==
- fxml:get_attr_s(<<"category">>, Identity)) and
- (fxml:get_attr_s(<<"type">> , Ident) ==
- fxml:get_attr_s(<<"type">>, Identity)) or A;
- (_, A) -> A
- end, false, delegated_namespaces)
- end,
-
- Identities =
- lists:filter(fun (#xmlel{attrs = Attrs}) ->
- not Fun(Attrs)
- end, Acc),
- % add service features
- ets:foldl(fun({_, _, _, _, {_, I, _}, {_, IBare, _}}, A) ->
- if
- Bare -> A ++ IBare;
- true -> A ++ I
- end;
- (_, A) -> A
- end, Identities, delegated_namespaces).
-
-%% xmlns from value element
-
--spec get_field_value([xmlel()]) -> binary().
-
-get_field_value([]) -> <<"">>;
-get_field_value([Elem| Elems]) ->
- case (fxml:get_attr_s(<<"var">>, Elem#xmlel.attrs) == <<"FORM_TYPE">>) and
- (fxml:get_attr_s(<<"type">>, Elem#xmlel.attrs) == <<"hidden">>) of
- true ->
- Ns = fxml:get_subtag_cdata(Elem, <<"value">>),
- if
- Ns /= <<"">> -> Ns;
- true -> get_field_value(Elems)
- end;
- _ -> get_field_value(Elems)
- end.
-
-get_info(Acc, Bare) ->
- Fun = fun(Feat) ->
- ets:foldl(fun({Ns, _, _, _, _, _}, A) ->
- (A or str:prefix(Ns, Feat))
- end, false, delegated_namespaces)
- end,
- Exten = lists:filter(fun(Xmlel) ->
- Tags = fxml:get_subtags(Xmlel, <<"field">>),
- case get_field_value(Tags) of
- <<"">> -> true;
- Value -> not Fun(Value)
- end
- end, Acc),
- ets:foldl(fun({_, _, _, _, {_, _, Ext}, {_, _, ExtBare}}, A) ->
- if
- Bare -> A ++ ExtBare;
- true -> A ++ Ext
- end;
- (_, A) -> A
- end, Exten, delegated_namespaces).
-
-%% 7.2.1 General Case
-
-disco_local_features({error, _Error} = Acc, _From, _To, _Node, _Lang) ->
- Acc;
-disco_local_features(Acc, _From, _To, <<>>, _Lang) ->
- FeatsOld = case Acc of
- {result, I} -> I;
- _ -> []
- end,
- disco_features(FeatsOld, false);
-disco_local_features(Acc, _From, _To, _Node, _Lang) ->
- Acc.
-
-disco_local_identity(Acc, _From, _To, <<>>, _Lang) ->
- disco_identity(Acc, false);
-disco_local_identity(Acc, _From, _To, _Node, _Lang) ->
- Acc.
-
-%% 7.2.2 Rediction Of Bare JID Disco Info
-
-disco_sm_features({error, ?ERR_ITEM_NOT_FOUND}, _From,
- #jid{lresource = <<"">>}, <<>>, _Lang) ->
- disco_features([], true);
-disco_sm_features({error, _Error} = Acc, _From, _To, _Node, _Lang) ->
- Acc;
-disco_sm_features(Acc, _From, #jid{lresource = <<"">>}, <<>>, _Lang) ->
- FeatsOld = case Acc of
- {result, I} -> I;
- _ -> []
- end,
- disco_features(FeatsOld, true);
-disco_sm_features(Acc, _From, _To, _Node, _Lang) ->
- Acc.
-
-disco_sm_identity(Acc, _From, #jid{lresource = <<"">>}, <<>>, _Lang) ->
- disco_identity(Acc, true);
-disco_sm_identity(Acc, _From, _To, _Node, _Lang) ->
+process_iq_result(#iq{from = From, to = To}, #iq{type = error} = ResIQ) ->
+ Err = xmpp:set_from_to(ResIQ, To, From),
+ ejabberd_router:route(To, From, Err);
+process_iq_result(#iq{from = From, to = To, lang = Lang} = IQ, timeout) ->
+ Txt = <<"External component timeout">>,
+ Err = xmpp:err_internal_server_error(Txt, Lang),
+ ejabberd_router:route_error(To, From, IQ, Err).
+
+-spec send_disco_queries(binary(), binary(), binary()) -> ok.
+send_disco_queries(LServer, Host, NS) ->
+ From = jid:make(LServer),
+ To = jid:make(Host),
+ lists:foreach(
+ fun({Type, Node}) ->
+ ejabberd_local:route_iq(
+ From, To, #iq{type = get, from = From, to = To,
+ sub_els = [#disco_info{node = Node}]},
+ fun(#iq{type = result, sub_els = [SubEl]}) ->
+ try xmpp:decode(SubEl) of
+ #disco_info{} = Info->
+ Proc = gen_mod:get_module_proc(LServer, ?MODULE),
+ gen_server:cast(
+ Proc, {disco_info, Type, Host, NS, Info});
+ _ ->
+ ok
+ catch _:{xmpp_codec, _} ->
+ ok
+ end;
+ (_) ->
+ ok
+ end)
+ end, [{ejabberd_local, <<(?NS_DELEGATION)/binary, "::", NS/binary>>},
+ {ejabberd_sm, <<(?NS_DELEGATION)/binary, ":bare:", NS/binary>>}]).
+
+-spec disco_features(disco_acc(), jid(), jid(), binary(), binary(),
+ ejabberd_local | ejabberd_sm) -> disco_acc().
+disco_features(Acc, _From, To, <<"">>, _Lang, Type) ->
+ Delegations = get_delegations(To#jid.lserver),
+ Features = my_features(Type) ++
+ lists:flatmap(
+ fun({{_, T}, {_, Info}}) when T == Type ->
+ Info#disco_info.features;
+ (_) ->
+ []
+ end, dict:to_list(Delegations)),
+ case Acc of
+ empty when Features /= [] -> {result, Features};
+ {result, Fs} -> {result, Fs ++ Features};
+ _ -> Acc
+ end;
+disco_features(Acc, _, _, _, _, _) ->
Acc.
-disco_info(Acc, #jid{}, #jid{lresource = <<"">>}, <<>>, _Lang) ->
- get_info(Acc, true);
-disco_info(Acc, _Host, _Mod, <<>>, _Lang) ->
- get_info(Acc, false);
-disco_info(Acc, _Host, _Mod, _Node, _Lang) ->
+-spec disco_identity(disco_acc(), jid(), jid(), binary(), binary(),
+ ejabberd_local | ejabberd_sm) -> disco_acc().
+disco_identity(Acc, _From, To, <<"">>, _Lang, Type) ->
+ Delegations = get_delegations(To#jid.lserver),
+ Identities = lists:flatmap(
+ fun({{_, T}, {_, Info}}) when T == Type ->
+ Info#disco_info.identities;
+ (_) ->
+ []
+ end, dict:to_list(Delegations)),
+ case Acc of
+ empty when Identities /= [] -> {result, Identities};
+ {result, Ids} -> {result, Ids ++ Identities};
+ Acc -> Acc
+ end;
+disco_identity(Acc, _From, _To, _Node, _Lang, _Type) ->
Acc.
-%% clean hooks_tmp table
-
-clean() ->
- ?DEBUG("cleaning ~p ETS table~n", [hooks_tmp]),
- Now = p1_time_compat:system_time(seconds),
- catch ets:select_delete(hooks_tmp,
- ets:fun2ms(fun({_, _, Timestamp}) ->
- Now - 300 >= Timestamp
- end)),
- %% start timer for table cleaning
- timer:apply_after(?CLEAN_INTERVAL, ?MODULE, clean, []).
+my_features(ejabberd_local) -> [?NS_DELEGATION];
+my_features(ejabberd_sm) -> [].
+
+validate_fun() ->
+ fun(L) ->
+ lists:map(
+ fun({NS, Opts}) ->
+ Attrs = proplists:get_value(filtering, Opts, []),
+ Access = proplists:get_value(access, Opts, none),
+ {NS, Attrs, Access}
+ end, L)
+ end.