Packages
ejabberd
16.9.0
26.4.0
26.3.0
26.2.0
26.1.0
25.10.0
25.8.0
25.7.0
25.4.0
25.3.0
24.12.0
24.10.0
24.7.0
24.6.0
24.2.6
23.10.0
23.4.0
23.1.0
22.10.0
22.5.0
21.12.0
21.7.0
21.4.0
21.1.0
20.12.0
20.7.0
20.4.0
20.3.0
20.2.0
20.1.0
19.9.1
19.9.0
19.8.0
19.5.0
19.2.0
18.12.1
18.12.0
18.6.0
18.4.0
18.3.0
18.1.0
17.11.0
17.9.0
17.6.0
17.3.0
17.1.0
16.12.0-beta1
16.9.0
16.8.0
16.6.2
16.6.1
16.6.0
16.4.1
16.4.0
16.3.0
16.2.0
16.1.0-beta1
Robust, Ubiquitous and Massively Scalable Messaging Platform (XMPP, MQTT, SIP Server)
Current section
Files
Jump to
Current section
Files
src/mod_delegation.erl
%%%--------------------------------------------------------------------------------------
%%% File : mod_delegation.erl
%%% Author : Anna Mukharram <amuhar3@gmail.com>
%%% Purpose : This module is an implementation for XEP-0355: Namespace Delegation
%%%--------------------------------------------------------------------------------------
-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
%%%--------------------------------------------------------------------------------------
start(Host, _Opts) ->
mod_disco:register_feature(Host, ?NS_DELEGATION),
%% start timer for hooks_tmp table cleaning
timer:apply_after(?CLEAN_INTERVAL, ?MODULE, clean, []),
ejabberd_hooks:add(disco_local_features, Host, ?MODULE,
disco_local_features, 500), %% This hook should be the last
ejabberd_hooks:add(disco_local_identity, Host, ?MODULE,
disco_local_identity, 500),
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
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
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)
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
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) ->
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) ->
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, []).