25
1
mirror of https://github.com/processone/ejabberd.git synced 2024-11-24 16:23:40 +01:00

Use new API for IQ routing

Functions ejabberd_local:route_iq/2,3 are now depecated:
ejabberd_router:route_iq/2,3,4 should be used instead.
This commit is contained in:
Evgeniy Khramtsov 2017-11-10 18:02:22 +03:00
parent 66c9f6458d
commit 7a3092a859
9 changed files with 331 additions and 271 deletions

149
src/ejabberd_iq.erl Normal file
View File

@ -0,0 +1,149 @@
%%%-------------------------------------------------------------------
%%% @author xram <xram@debian.zinid.ru>
%%% @copyright (C) 2017, xram
%%% @doc
%%%
%%% @end
%%% Created : 10 Nov 2017 by xram <xram@debian.zinid.ru>
%%%-------------------------------------------------------------------
-module(ejabberd_iq).
-behaviour(gen_server).
%% API
-export([start_link/0, route/4, dispatch/1]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-include("xmpp.hrl").
-include("logger.hrl").
-record(state, {expire = infinity :: timeout()}).
%%%===================================================================
%%% API
%%%===================================================================
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
route(#iq{type = T} = IQ, Proc, Ctx, Timeout) when T == set; T == get ->
Expire = current_time() + Timeout,
Rnd = randoms:get_string(),
ID = encode_id(Expire, Rnd),
ets:insert(?MODULE, {{Expire, Rnd}, Proc, Ctx}),
gen_server:cast(?MODULE, {restart_timer, Expire}),
ejabberd_router:route(IQ#iq{id = ID}).
-spec dispatch(iq()) -> boolean().
dispatch(#iq{type = T, id = ID} = IQ) when T == error; T == result ->
case decode_id(ID) of
{ok, Expire, Rnd, Node} ->
ejabberd_cluster:send({?MODULE, Node}, {route, IQ, {Expire, Rnd}});
error ->
false
end;
dispatch(_) ->
false.
%%%===================================================================
%%% gen_server callbacks
%%%===================================================================
init([]) ->
ets:new(?MODULE, [named_table, ordered_set, public]),
{ok, #state{}}.
handle_call(Request, From, State) ->
{stop, {unexpected_call, Request, From}, State}.
handle_cast({restart_timer, Expire}, State) ->
State1 = State#state{expire = min(Expire, State#state.expire)},
noreply(State1);
handle_cast(Msg, State) ->
?WARNING_MSG("unexpected cast: ~p", [Msg]),
noreply(State).
handle_info({route, IQ, Key}, State) ->
case ets:lookup(?MODULE, Key) of
[{_, Proc, Ctx}] ->
callback(Proc, IQ, Ctx),
ets:delete(?MODULE, Key);
[] ->
ok
end,
noreply(State);
handle_info(timeout, State) ->
Expire = clean(ets:first(?MODULE)),
noreply(State#state{expire = Expire});
handle_info(Info, State) ->
?WARNING_MSG("unexpected info: ~p", [Info]),
noreply(State).
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%%===================================================================
%%% Internal functions
%%%===================================================================
current_time() ->
p1_time_compat:system_time(milli_seconds).
clean({Expire, _} = Key) ->
case current_time() of
Time when Time >= Expire ->
case ets:lookup(?MODULE, Key) of
[{_, Proc, Ctx}] ->
callback(Proc, timeout, Ctx),
ets:delete(?MODULE, Key);
[] ->
ok
end,
clean(ets:next(?MODULE, Key));
_ ->
Expire
end;
clean('$end_of_table') ->
infinity.
noreply(#state{expire = Expire} = State) ->
case Expire of
infinity ->
{noreply, State};
_ ->
Timeout = max(0, Expire - current_time()),
{noreply, State, Timeout}
end.
encode_id(Expire, Rnd) ->
ExpireBin = integer_to_binary(Expire),
Node = atom_to_binary(node(), utf8),
CheckSum = calc_checksum(<<ExpireBin/binary, Rnd/binary, Node/binary>>),
<<"rr-", ExpireBin/binary, $-, Rnd/binary, $-, CheckSum/binary, $-, Node/binary>>.
decode_id(<<"rr-", ID/binary>>) ->
try
[ExpireBin, Tail] = binary:split(ID, <<"-">>),
[Rnd, Rest] = binary:split(Tail, <<"-">>),
[CheckSum, NodeBin] = binary:split(Rest, <<"-">>),
CheckSum = calc_checksum(<<ExpireBin/binary, Rnd/binary, NodeBin/binary>>),
Node = erlang:binary_to_existing_atom(NodeBin, utf8),
Expire = binary_to_integer(ExpireBin),
{ok, Expire, Rnd, Node}
catch _:{badmatch, _} ->
error
end;
decode_id(_) ->
error.
calc_checksum(Data) ->
Key = ejabberd_config:get_option(shared_key),
base64:encode(crypto:hash(sha, <<Data/binary, Key/binary>>)).
callback(undefined, IQRes, Fun) ->
Fun(IQRes);
callback(Proc, IQRes, Ctx) ->
Proc ! {iq_reply, IQRes, Ctx}.

View File

@ -32,17 +32,21 @@
%% API %% API
-export([start/0, start_link/0]). -export([start/0, start_link/0]).
-export([route/1, route_iq/2, route_iq/3, process_iq/1, -export([route/1, process_iq/1,
process_iq_reply/1, get_features/1, get_features/1,
register_iq_handler/5, register_iq_response_handler/4, register_iq_handler/5,
register_iq_response_handler/5, unregister_iq_handler/2, unregister_iq_handler/2,
unregister_iq_response_handler/2, bounce_resource_packet/1, bounce_resource_packet/1,
host_up/1, host_down/1]). host_up/1, host_down/1]).
%% gen_server callbacks %% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, -export([init/1, handle_call/3, handle_cast/2,
handle_info/2, terminate/2, code_change/3]). handle_info/2, terminate/2, code_change/3]).
%% deprecated functions: use ejabberd_router:route_iq/3,4
-export([route_iq/2, route_iq/3]).
-deprecated([{route_iq, 2}, {route_iq, 3}]).
-include("ejabberd.hrl"). -include("ejabberd.hrl").
-include("logger.hrl"). -include("logger.hrl").
-include_lib("stdlib/include/ms_transform.hrl"). -include_lib("stdlib/include/ms_transform.hrl").
@ -50,18 +54,8 @@
-record(state, {}). -record(state, {}).
-record(iq_response, {id = <<"">> :: binary(),
module :: atom(),
function :: atom() | fun(),
timer = make_ref() :: reference()}).
-define(IQTABLE, local_iqtable). -define(IQTABLE, local_iqtable).
%% This value is used in SIP and Megaco for a transaction lifetime.
-define(IQ_TIMEOUT, 32000).
-type ping_timeout() :: non_neg_integer() | undefined.
%%==================================================================== %%====================================================================
%% API %% API
%%==================================================================== %%====================================================================
@ -99,17 +93,8 @@ process_iq(#iq{type = T, lang = Lang, sub_els = SubEls} = Packet)
end, end,
Err = xmpp:err_bad_request(Txt, Lang), Err = xmpp:err_bad_request(Txt, Lang),
ejabberd_router:route_error(Packet, Err); ejabberd_router:route_error(Packet, Err);
process_iq(#iq{type = T} = Packet) when T == result; T == error -> process_iq(#iq{type = T}) when T == result; T == error ->
process_iq_reply(Packet). ok.
-spec process_iq_reply(iq()) -> any().
process_iq_reply(#iq{id = ID} = IQ) ->
case get_iq_callback(ID) of
{ok, undefined, Function} -> Function(IQ), ok;
{ok, Module, Function} ->
Module:Function(IQ), ok;
_ -> nothing
end.
-spec route(stanza()) -> any(). -spec route(stanza()) -> any().
route(Packet) -> route(Packet) ->
@ -119,43 +104,13 @@ route(Packet) ->
[xmpp:pp(Packet), {E, {R, erlang:get_stacktrace()}}]) [xmpp:pp(Packet), {E, {R, erlang:get_stacktrace()}}])
end. end.
-spec route_iq(iq(), function()) -> any(). -spec route_iq(iq(), function()) -> ok.
route_iq(IQ, F) -> route_iq(IQ, Fun) ->
route_iq(IQ, F, undefined). route_iq(IQ, Fun, undefined).
-spec route_iq(iq(), function(), ping_timeout()) -> any(). -spec route_iq(iq(), function(), undefined | non_neg_integer()) -> ok.
route_iq(#iq{from = From, type = Type} = IQ, F, Timeout) route_iq(IQ, Fun, Timeout) ->
when is_function(F) -> ejabberd_router:route_iq(IQ, Fun, undefined, Timeout).
Packet = if Type == set; Type == get ->
ID = randoms:get_string(),
Host = From#jid.lserver,
register_iq_response_handler(Host, ID, undefined, F, Timeout),
IQ#iq{id = ID};
true ->
IQ
end,
ejabberd_router:route(Packet).
-spec register_iq_response_handler(binary(), binary(), module(),
atom() | function()) -> any().
register_iq_response_handler(Host, ID, Module,
Function) ->
register_iq_response_handler(Host, ID, Module, Function,
undefined).
-spec register_iq_response_handler(binary(), binary(), module(),
atom() | function(), ping_timeout()) -> any().
register_iq_response_handler(_Host, ID, Module,
Function, Timeout0) ->
Timeout = case Timeout0 of
undefined -> ?IQ_TIMEOUT;
N when is_integer(N), N > 0 -> N
end,
TRef = erlang:start_timer(Timeout, ?MODULE, ID),
mnesia:dirty_write(#iq_response{id = ID,
module = Module,
function = Function,
timer = TRef}).
-spec register_iq_handler(binary(), binary(), module(), function(), -spec register_iq_handler(binary(), binary(), module(), function(),
gen_iq_handler:opts()) -> ok. gen_iq_handler:opts()) -> ok.
@ -163,10 +118,6 @@ register_iq_handler(Host, XMLNS, Module, Fun, Opts) ->
gen_server:cast(?MODULE, gen_server:cast(?MODULE,
{register_iq_handler, Host, XMLNS, Module, Fun, Opts}). {register_iq_handler, Host, XMLNS, Module, Fun, Opts}).
-spec unregister_iq_response_handler(binary(), binary()) -> ok.
unregister_iq_response_handler(_Host, ID) ->
catch get_iq_callback(ID), ok.
-spec unregister_iq_handler(binary(), binary()) -> ok. -spec unregister_iq_handler(binary(), binary()) -> ok.
unregister_iq_handler(Host, XMLNS) -> unregister_iq_handler(Host, XMLNS) ->
gen_server:cast(?MODULE, {unregister_iq_handler, Host, XMLNS}). gen_server:cast(?MODULE, {unregister_iq_handler, Host, XMLNS}).
@ -204,9 +155,6 @@ init([]) ->
catch ets:new(?IQTABLE, [named_table, public, ordered_set, catch ets:new(?IQTABLE, [named_table, public, ordered_set,
{read_concurrency, true}]), {read_concurrency, true}]),
update_table(), update_table(),
ejabberd_mnesia:create(?MODULE, iq_response,
[{ram_copies, [node()]},
{attributes, record_info(fields, iq_response)}]),
{ok, #state{}}. {ok, #state{}}.
handle_call(_Request, _From, State) -> handle_call(_Request, _From, State) ->
@ -232,9 +180,6 @@ handle_cast(_Msg, State) -> {noreply, State}.
handle_info({route, Packet}, State) -> handle_info({route, Packet}, State) ->
route(Packet), route(Packet),
{noreply, State}; {noreply, State};
handle_info({timeout, _TRef, ID}, State) ->
process_iq_timeout(ID),
{noreply, State};
handle_info(Info, State) -> handle_info(Info, State) ->
?WARNING_MSG("unexpected info: ~p", [Info]), ?WARNING_MSG("unexpected info: ~p", [Info]),
{noreply, State}. {noreply, State}.
@ -269,15 +214,8 @@ do_route(Packet) ->
-spec update_table() -> ok. -spec update_table() -> ok.
update_table() -> update_table() ->
case catch mnesia:table_info(iq_response, attributes) of catch mnesia:delete_table(iq_response),
[id, module, function] -> ok.
mnesia:delete_table(iq_response),
ok;
[id, module, function, timer] ->
ok;
{'EXIT', _} ->
ok
end.
host_up(Host) -> host_up(Host) ->
Owner = case whereis(?MODULE) of Owner = case whereis(?MODULE) of
@ -296,41 +234,3 @@ host_down(Host) ->
ejabberd_router:unregister_route(Host, Owner), ejabberd_router:unregister_route(Host, Owner),
ejabberd_hooks:delete(local_send_to_resource_hook, Host, ejabberd_hooks:delete(local_send_to_resource_hook, Host,
?MODULE, bounce_resource_packet, 100). ?MODULE, bounce_resource_packet, 100).
-spec get_iq_callback(binary()) -> {ok, module(), atom() | function()} | error.
get_iq_callback(ID) ->
case mnesia:dirty_read(iq_response, ID) of
[#iq_response{module = Module, timer = TRef,
function = Function}] ->
cancel_timer(TRef),
mnesia:dirty_delete(iq_response, ID),
{ok, Module, Function};
_ ->
error
end.
-spec process_iq_timeout(binary()) -> any().
process_iq_timeout(ID) ->
spawn(fun process_iq_timeout/0) ! ID.
-spec process_iq_timeout() -> any().
process_iq_timeout() ->
receive
ID ->
case get_iq_callback(ID) of
{ok, undefined, Function} ->
Function(timeout);
_ ->
ok
end
after 5000 ->
ok
end.
-spec cancel_timer(reference()) -> ok.
cancel_timer(TRef) ->
case erlang:cancel_timer(TRef) of
false ->
receive {timeout, TRef, _} -> ok after 0 -> ok end;
_ -> ok
end.

View File

@ -37,6 +37,9 @@
%% API %% API
-export([route/1, -export([route/1,
route_error/2, route_error/2,
route_iq/2,
route_iq/3,
route_iq/4,
register_route/2, register_route/2,
register_route/3, register_route/3,
register_route/4, register_route/4,
@ -62,6 +65,9 @@
-export([route/3, route_error/4]). -export([route/3, route_error/4]).
-deprecated([{route, 3}, {route_error, 4}]). -deprecated([{route, 3}, {route_error, 4}]).
%% This value is used in SIP and Megaco for a transaction lifetime.
-define(IQ_TIMEOUT, 32000).
-include("ejabberd.hrl"). -include("ejabberd.hrl").
-include("logger.hrl"). -include("logger.hrl").
-include("ejabberd_router.hrl"). -include("ejabberd_router.hrl").
@ -136,6 +142,20 @@ route_error(From, To, Packet, #stanza_error{} = Err) ->
route(From, To, xmpp:make_error(Packet, Err)) route(From, To, xmpp:make_error(Packet, Err))
end. end.
-spec route_iq(iq(), term()) -> ok.
route_iq(IQ, State) ->
route_iq(IQ, State, undefined, ?IQ_TIMEOUT).
-spec route_iq(iq(), term(), pid() | atom()) -> ok.
route_iq(IQ, State, Proc) ->
route_iq(IQ, State, Proc, ?IQ_TIMEOUT).
-spec route_iq(iq(), term(), pid() | atom(), undefined | non_neg_integer()) -> ok.
route_iq(IQ, State, Proc, undefined) ->
route_iq(IQ, State, Proc, ?IQ_TIMEOUT);
route_iq(IQ, State, Proc, Timeout) ->
ejabberd_iq:route(IQ, Proc, State, Timeout).
-spec register_route(binary(), binary()) -> ok. -spec register_route(binary(), binary()) -> ok.
register_route(Domain, ServerHost) -> register_route(Domain, ServerHost) ->
register_route(Domain, ServerHost, undefined). register_route(Domain, ServerHost, undefined).
@ -339,18 +359,23 @@ do_route(OrigPacket) ->
drop -> drop ->
ok; ok;
Packet -> Packet ->
To = xmpp:get_to(Packet), case ejabberd_iq:dispatch(Packet) of
LDstDomain = To#jid.lserver, true ->
case find_routes(LDstDomain) of ok;
[] -> false ->
ejabberd_s2s:route(Packet); To = xmpp:get_to(Packet),
[Route] -> LDstDomain = To#jid.lserver,
do_route(Packet, Route); case find_routes(LDstDomain) of
Routes -> [] ->
From = xmpp:get_from(Packet), ejabberd_s2s:route(Packet);
balancing_route(From, To, Packet, Routes) [Route] ->
end, do_route(Packet, Route);
ok Routes ->
From = xmpp:get_from(Packet),
balancing_route(From, To, Packet, Routes)
end,
ok
end
end. end.
-spec do_route(stanza(), #route{}) -> any(). -spec do_route(stanza(), #route{}) -> any().

View File

@ -156,6 +156,8 @@ init([]) ->
permanent, 5000, worker, [cyrsasl]}, permanent, 5000, worker, [cyrsasl]},
PKIX = {ejabberd_pkix, {ejabberd_pkix, start_link, []}, PKIX = {ejabberd_pkix, {ejabberd_pkix, start_link, []},
permanent, 5000, worker, [ejabberd_pkix]}, permanent, 5000, worker, [ejabberd_pkix]},
IQ = {ejabberd_iq, {ejabberd_iq, start_link, []},
permanent, 5000, worker, [ejabberd_iq]},
{ok, {{one_for_one, 10, 1}, {ok, {{one_for_one, 10, 1},
[Hooks, [Hooks,
Cluster, Cluster,
@ -180,6 +182,7 @@ init([]) ->
SQLSupervisor, SQLSupervisor,
RiakSupervisor, RiakSupervisor,
RedisSupervisor, RedisSupervisor,
IQ,
Router, Router,
RouterMulticast, RouterMulticast,
Local, Local,

View File

@ -118,11 +118,11 @@ user_send_packet({#presence{type = available,
from = #jid{luser = U, lserver = LServer} = From, from = #jid{luser = U, lserver = LServer} = From,
to = #jid{luser = U, lserver = LServer, to = #jid{luser = U, lserver = LServer,
lresource = <<"">>}} = Pkt, lresource = <<"">>}} = Pkt,
State}) -> #{jid := To} = State}) ->
case read_caps(Pkt) of case read_caps(Pkt) of
nothing -> ok; nothing -> ok;
#caps{version = Version, exts = Exts} = Caps -> #caps{version = Version, exts = Exts} = Caps ->
feature_request(LServer, From, Caps, [Version | Exts]) feature_request(LServer, From, To, Caps, [Version | Exts])
end, end,
{Pkt, State}; {Pkt, State};
user_send_packet(Acc) -> user_send_packet(Acc) ->
@ -130,13 +130,13 @@ user_send_packet(Acc) ->
-spec user_receive_packet({stanza(), ejabberd_c2s:state()}) -> {stanza(), ejabberd_c2s:state()}. -spec user_receive_packet({stanza(), ejabberd_c2s:state()}) -> {stanza(), ejabberd_c2s:state()}.
user_receive_packet({#presence{from = From, type = available} = Pkt, user_receive_packet({#presence{from = From, type = available} = Pkt,
#{lserver := LServer} = State}) -> #{lserver := LServer, jid := To} = State}) ->
IsRemote = not ejabberd_router:is_my_host(From#jid.lserver), IsRemote = not ejabberd_router:is_my_host(From#jid.lserver),
if IsRemote -> if IsRemote ->
case read_caps(Pkt) of case read_caps(Pkt) of
nothing -> ok; nothing -> ok;
#caps{version = Version, exts = Exts} = Caps -> #caps{version = Version, exts = Exts} = Caps ->
feature_request(LServer, From, Caps, [Version | Exts]) feature_request(LServer, From, To, Caps, [Version | Exts])
end; end;
true -> ok true -> ok
end, end,
@ -298,7 +298,12 @@ handle_call(_Req, _From, State) ->
handle_cast(_Msg, State) -> {noreply, State}. handle_cast(_Msg, State) -> {noreply, State}.
handle_info(_Info, State) -> {noreply, State}. handle_info({iq_reply, IQReply, {Host, From, To, Caps, SubNodes}}, State) ->
feature_response(IQReply, Host, From, To, Caps, SubNodes),
{noreply, State};
handle_info(Info, State) ->
?WARNING_MSG("unexpected info: ~p", [Info]),
{noreply, State}.
terminate(_Reason, State) -> terminate(_Reason, State) ->
Host = State#state.host, Host = State#state.host,
@ -322,39 +327,37 @@ terminate(_Reason, State) ->
code_change(_OldVsn, State, _Extra) -> {ok, State}. code_change(_OldVsn, State, _Extra) -> {ok, State}.
-spec feature_request(binary(), jid(), caps(), [binary()]) -> any(). -spec feature_request(binary(), jid(), jid(), caps(), [binary()]) -> any().
feature_request(Host, From, Caps, feature_request(Host, From, To, Caps,
[SubNode | Tail] = SubNodes) -> [SubNode | Tail] = SubNodes) ->
Node = Caps#caps.node, Node = Caps#caps.node,
NodePair = {Node, SubNode}, NodePair = {Node, SubNode},
case ets_cache:lookup(caps_features_cache, NodePair, case ets_cache:lookup(caps_features_cache, NodePair,
caps_read_fun(Host, NodePair)) of caps_read_fun(Host, NodePair)) of
{ok, Fs} when is_list(Fs) -> {ok, Fs} when is_list(Fs) ->
feature_request(Host, From, Caps, Tail); feature_request(Host, From, To, Caps, Tail);
_ -> _ ->
LFrom = jid:tolower(From), LTo = jid:tolower(To),
case ets_cache:insert_new(caps_requests_cache, {LFrom, NodePair}, ok) of case ets_cache:insert_new(caps_requests_cache, {LTo, NodePair}, ok) of
true -> true ->
IQ = #iq{type = get, IQ = #iq{type = get,
from = jid:make(Host), from = From,
to = From, to = To,
sub_els = [#disco_info{node = <<Node/binary, "#", sub_els = [#disco_info{node = <<Node/binary, "#",
SubNode/binary>>}]}, SubNode/binary>>}]},
F = fun (IQReply) -> ejabberd_router:route_iq(
feature_response(IQReply, Host, From, Caps, IQ, {Host, From, To, Caps, SubNodes},
SubNodes) gen_mod:get_module_proc(Host, ?MODULE));
end,
ejabberd_local:route_iq(IQ, F);
false -> false ->
ok ok
end, end,
feature_request(Host, From, Caps, Tail) feature_request(Host, From, To, Caps, Tail)
end; end;
feature_request(_Host, _From, _Caps, []) -> ok. feature_request(_Host, _From, _To, _Caps, []) -> ok.
-spec feature_response(iq(), binary(), ljid(), caps(), [binary()]) -> any(). -spec feature_response(iq(), binary(), jid(), jid(), caps(), [binary()]) -> any().
feature_response(#iq{type = result, sub_els = [El]}, feature_response(#iq{type = result, sub_els = [El]},
Host, From, Caps, [SubNode | SubNodes]) -> Host, From, To, Caps, [SubNode | SubNodes]) ->
NodePair = {Caps#caps.node, SubNode}, NodePair = {Caps#caps.node, SubNode},
try try
DiscoInfo = xmpp:decode(El), DiscoInfo = xmpp:decode(El),
@ -374,10 +377,10 @@ feature_response(#iq{type = result, sub_els = [El]},
catch _:{xmpp_codec, _Why} -> catch _:{xmpp_codec, _Why} ->
ok ok
end, end,
feature_request(Host, From, Caps, SubNodes); feature_request(Host, From, To, Caps, SubNodes);
feature_response(_IQResult, Host, From, Caps, feature_response(_IQResult, Host, From, To, Caps,
[_SubNode | SubNodes]) -> [_SubNode | SubNodes]) ->
feature_request(Host, From, Caps, SubNodes). feature_request(Host, From, To, Caps, SubNodes).
-spec caps_read_fun(binary(), {binary(), binary()}) -spec caps_read_fun(binary(), {binary(), binary()})
-> fun(() -> {ok, [binary()] | non_neg_integer()} | error). -> fun(() -> {ok, [binary()] | non_neg_integer()} | error).

View File

@ -47,6 +47,7 @@
-type disco_acc() :: {error, stanza_error()} | {result, [binary()]} | empty. -type disco_acc() :: {error, stanza_error()} | {result, [binary()]} | empty.
-record(state, {server_host = <<"">> :: binary(), -record(state, {server_host = <<"">> :: binary(),
delegations = dict:new() :: ?TDICT}). delegations = dict:new() :: ?TDICT}).
-type state() :: #state{}.
%%%=================================================================== %%%===================================================================
%%% API %%% API
@ -161,27 +162,6 @@ handle_cast({component_connected, Host}, State) ->
end end
end, NSAttrsAccessList), end, NSAttrsAccessList),
{noreply, State}; {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, gen_iq_handler:iqdisc(Host)),
ejabberd_router:route(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;
handle_cast({component_disconnected, Host}, State) -> handle_cast({component_disconnected, Host}, State) ->
ServerHost = State#state.server_host, ServerHost = State#state.server_host,
Delegations = Delegations =
@ -199,7 +179,24 @@ handle_cast({component_disconnected, Host}, State) ->
handle_cast(_Msg, State) -> handle_cast(_Msg, State) ->
{noreply, State}. {noreply, State}.
handle_info(_Info, State) -> handle_info({iq_reply, ResIQ, {disco_info, Type, Host, NS}}, State) ->
{noreply,
case ResIQ of
#iq{type = result, sub_els = [SubEl]} ->
try xmpp:decode(SubEl) of
#disco_info{} = Info ->
process_disco_info(State, Type, Host, NS, Info)
catch _:{xmpp_codec, _} ->
State
end;
_ ->
State
end};
handle_info({iq_reply, ResIQ, #iq{} = IQ}, State) ->
process_iq_result(IQ, ResIQ),
{noreply, State};
handle_info(Info, State) ->
?WARNING_MSG("unexpected info: ~p", [Info]),
{noreply, State}. {noreply, State}.
terminate(_Reason, State) -> terminate(_Reason, State) ->
@ -246,12 +243,12 @@ process_iq(#iq{to = To, lang = Lang, sub_els = [SubEl]} = IQ, Type) ->
forwarded = #forwarded{xml_els = [xmpp:encode(IQ)]}}, forwarded = #forwarded{xml_els = [xmpp:encode(IQ)]}},
NewFrom = jid:make(LServer), NewFrom = jid:make(LServer),
NewTo = jid:make(Host), NewTo = jid:make(Host),
ejabberd_local:route_iq( ejabberd_router:route_iq(
#iq{type = set, #iq{type = set,
from = NewFrom, from = NewFrom,
to = NewTo, to = NewTo,
sub_els = [Delegation]}, sub_els = [Delegation]},
fun(Result) -> process_iq_result(IQ, Result) end), IQ, gen_mod:get_module_proc(LServer, ?MODULE)),
ignore; ignore;
error -> error ->
Txt = <<"Failed to map delegated namespace to external component">>, Txt = <<"Failed to map delegated namespace to external component">>,
@ -284,29 +281,41 @@ process_iq_result(#iq{lang = Lang} = IQ, timeout) ->
Err = xmpp:err_internal_server_error(Txt, Lang), Err = xmpp:err_internal_server_error(Txt, Lang),
ejabberd_router:route_error(IQ, Err). ejabberd_router:route_error(IQ, Err).
-spec process_disco_info(state(), ejabberd_local | ejabberd_sm,
binary(), binary(), disco_info()) -> state().
process_disco_info(State, Type, Host, NS, Info) ->
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, gen_iq_handler:iqdisc(Host)),
ejabberd_router:route(Msg),
?INFO_MSG("Namespace '~s' is delegated to external component '~s'",
[NS, Host]),
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]),
State
end.
-spec send_disco_queries(binary(), binary(), binary()) -> ok. -spec send_disco_queries(binary(), binary(), binary()) -> ok.
send_disco_queries(LServer, Host, NS) -> send_disco_queries(LServer, Host, NS) ->
From = jid:make(LServer), From = jid:make(LServer),
To = jid:make(Host), To = jid:make(Host),
lists:foreach( lists:foreach(
fun({Type, Node}) -> fun({Type, Node}) ->
ejabberd_local:route_iq( ejabberd_router:route_iq(
#iq{type = get, from = From, to = To, #iq{type = get, from = From, to = To,
sub_els = [#disco_info{node = Node}]}, sub_els = [#disco_info{node = Node}]},
fun(#iq{type = result, sub_els = [SubEl]}) -> {disco_info, Type, Host, NS},
try xmpp:decode(SubEl) of gen_mod:get_module_proc(LServer, ?MODULE))
#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>>}, end, [{ejabberd_local, <<(?NS_DELEGATION)/binary, "::", NS/binary>>},
{ejabberd_sm, <<(?NS_DELEGATION)/binary, ":bare:", NS/binary>>}]). {ejabberd_sm, <<(?NS_DELEGATION)/binary, ":bare:", NS/binary>>}]).

View File

@ -433,27 +433,31 @@ normal_state({route, ToNick,
{next_state, normal_state, StateData} {next_state, normal_state, StateData}
end; end;
normal_state({route, ToNick, normal_state({route, ToNick,
#iq{from = From, id = StanzaId, lang = Lang} = Packet}, #iq{from = From, type = Type, lang = Lang} = Packet},
StateData) -> StateData) ->
case {(StateData#state.config)#config.allow_query_users, case {(StateData#state.config)#config.allow_query_users,
is_user_online_iq(StanzaId, From, StateData)} of (?DICT):find(jid:tolower(From), StateData#state.users)} of
{true, {true, NewId, FromFull}} -> {true, {ok, #user{nick = FromNick}}} ->
case find_jid_by_nick(ToNick, StateData) of case find_jid_by_nick(ToNick, StateData) of
false -> false ->
ErrText = <<"Recipient is not in the conference room">>, ErrText = <<"Recipient is not in the conference room">>,
Err = xmpp:err_item_not_found(ErrText, Lang), Err = xmpp:err_item_not_found(ErrText, Lang),
ejabberd_router:route_error(Packet, Err); ejabberd_router:route_error(Packet, Err);
ToJID -> To ->
{ok, #user{nick = FromNick}} = FromJID = jid:replace_resource(StateData#state.jid, FromNick),
(?DICT):find(jid:tolower(FromFull), StateData#state.users), if Type == get; Type == set ->
{ToJID2, Packet2} = handle_iq_vcard(ToJID, NewId, Packet), ToJID = case is_vcard_request(Packet) of
ejabberd_router:route( true -> jid:remove_resource(To);
xmpp:set_from_to( false -> To
Packet2, end,
jid:replace_resource(StateData#state.jid, FromNick), ejabberd_router:route_iq(
ToJID2)) xmpp:set_from_to(Packet, FromJID, ToJID), Packet, self());
true ->
ejabberd_router:route(
xmpp:set_from_to(Packet, FromJID, To))
end
end; end;
{_, {false, _, _}} -> {true, error} ->
ErrText = <<"Only occupants are allowed to send queries " ErrText = <<"Only occupants are allowed to send queries "
"to the conference">>, "to the conference">>,
Err = xmpp:err_not_acceptable(ErrText, Lang), Err = xmpp:err_not_acceptable(ErrText, Lang),
@ -660,6 +664,18 @@ handle_info({captcha_failed, From}, normal_state,
{next_state, normal_state, NewState}; {next_state, normal_state, NewState};
handle_info(shutdown, _StateName, StateData) -> handle_info(shutdown, _StateName, StateData) ->
{stop, shutdown, StateData}; {stop, shutdown, StateData};
handle_info({iq_reply, #iq{type = Type, sub_els = Els},
#iq{from = From, to = To} = IQ}, StateName, StateData) ->
ejabberd_router:route(
xmpp:set_from_to(
IQ#iq{type = Type, sub_els = Els},
To, From)),
{next_state, StateName, StateData};
handle_info({iq_reply, timeout, IQ}, StateName, StateData) ->
Txt = <<"iq response timed out">>,
Err = xmpp:err_recipient_unavailable(Txt, IQ#iq.lang),
ejabberd_router:route_error(IQ, Err),
{next_state, StateName, StateData};
handle_info(_Info, StateName, StateData) -> handle_info(_Info, StateName, StateData) ->
{next_state, StateName, StateData}. {next_state, StateName, StateData}.
@ -920,6 +936,12 @@ process_voice_approval(From, Pkt, VoiceApproval, StateData) ->
StateData StateData
end. end.
-spec is_vcard_request(iq()) -> boolean().
is_vcard_request(#iq{type = T, sub_els = [El]}) ->
(T == get orelse T == set) andalso xmpp:get_ns(El) == ?NS_VCARD;
is_vcard_request(_) ->
false.
%% @doc Check if this non participant can send message to room. %% @doc Check if this non participant can send message to room.
%% %%
%% XEP-0045 v1.23: %% XEP-0045 v1.23:
@ -1129,59 +1151,6 @@ is_occupant_or_admin(JID, StateData) ->
_ -> false _ -> false
end. end.
%%%
%%% Handle IQ queries of vCard
%%%
-spec is_user_online_iq(binary(), jid(), state()) ->
{boolean(), binary(), jid()}.
is_user_online_iq(StanzaId, JID, StateData)
when JID#jid.lresource /= <<"">> ->
{is_user_online(JID, StateData), StanzaId, JID};
is_user_online_iq(StanzaId, JID, StateData)
when JID#jid.lresource == <<"">> ->
try stanzaid_unpack(StanzaId) of
{OriginalId, Resource} ->
JIDWithResource = jid:replace_resource(JID, Resource),
{is_user_online(JIDWithResource, StateData), OriginalId,
JIDWithResource}
catch
_:_ -> {is_user_online(JID, StateData), StanzaId, JID}
end.
-spec handle_iq_vcard(jid(), binary(), iq()) -> {jid(), iq()}.
handle_iq_vcard(ToJID, NewId, #iq{type = Type, sub_els = SubEls} = IQ) ->
ToBareJID = jid:remove_resource(ToJID),
case SubEls of
[SubEl] when Type == get, ToBareJID /= ToJID ->
case xmpp:get_ns(SubEl) of
?NS_VCARD ->
{ToBareJID, change_stanzaid(ToJID, IQ)};
_ ->
{ToJID, xmpp:set_id(IQ, NewId)}
end;
_ ->
{ToJID, xmpp:set_id(IQ, NewId)}
end.
-spec stanzaid_pack(binary(), binary()) -> binary().
stanzaid_pack(OriginalId, Resource) ->
<<"berd",
(base64:encode(<<"ejab\000",
OriginalId/binary, "\000",
Resource/binary>>))/binary>>.
-spec stanzaid_unpack(binary()) -> {binary(), binary()}.
stanzaid_unpack(<<"berd", StanzaIdBase64/binary>>) ->
StanzaId = base64:decode(StanzaIdBase64),
[<<"ejab">>, OriginalId, Resource] =
str:tokens(StanzaId, <<"\000">>),
{OriginalId, Resource}.
-spec change_stanzaid(jid(), iq()) -> iq().
change_stanzaid(ToJID, #iq{id = PreviousId} = Packet) ->
NewId = stanzaid_pack(PreviousId, ToJID#jid.lresource),
xmpp:set_id(Packet, NewId).
%% Decide the fate of the message and its sender %% Decide the fate of the message and its sender
%% Returns: continue_delivery | forget_message | {expulse_sender, Reason} %% Returns: continue_delivery | forget_message | {expulse_sender, Reason}
-spec decide_fate_message(message(), jid(), state()) -> -spec decide_fate_message(message(), jid(), state()) ->

View File

@ -132,7 +132,7 @@ handle_cast({start_ping, JID}, State) ->
handle_cast({stop_ping, JID}, State) -> handle_cast({stop_ping, JID}, State) ->
Timers = del_timer(JID, State#state.timers), Timers = del_timer(JID, State#state.timers),
{noreply, State#state{timers = Timers}}; {noreply, State#state{timers = Timers}};
handle_cast({iq_pong, JID, timeout}, State) -> handle_cast({iq_reply, timeout, JID}, State) ->
Timers = del_timer(JID, State#state.timers), Timers = del_timer(JID, State#state.timers),
ejabberd_hooks:run(user_ping_timeout, State#state.host, ejabberd_hooks:run(user_ping_timeout, State#state.host,
[JID]), [JID]),
@ -149,20 +149,19 @@ handle_cast({iq_pong, JID, timeout}, State) ->
_ -> ok _ -> ok
end, end,
{noreply, State#state{timers = Timers}}; {noreply, State#state{timers = Timers}};
handle_cast({iq_pong, _JID, _}, State) -> handle_cast({iq_reply, #iq{}, _JID}, State) ->
{noreply, State}; {noreply, State};
handle_cast(Msg, State) -> handle_cast(Msg, State) ->
?WARNING_MSG("unexpected cast: ~p", [Msg]), ?WARNING_MSG("unexpected cast: ~p", [Msg]),
{noreply, State}. {noreply, State}.
handle_info({timeout, _TRef, {ping, JID}}, State) -> handle_info({timeout, _TRef, {ping, JID}}, State) ->
From = jid:make(State#state.host), Host = State#state.host,
From = jid:remove_resource(JID),
IQ = #iq{from = From, to = JID, type = get, sub_els = [#ping{}]}, IQ = #iq{from = From, to = JID, type = get, sub_els = [#ping{}]},
Pid = self(), ejabberd_router:route_iq(IQ, JID,
F = fun (Response) -> gen_mod:get_module_proc(Host, ?MODULE),
gen_server:cast(Pid, {iq_pong, JID, Response}) State#state.ping_ack_timeout),
end,
ejabberd_local:route_iq(IQ, F, State#state.ping_ack_timeout),
Timers = add_timer(JID, State#state.ping_interval, Timers = add_timer(JID, State#state.ping_interval,
State#state.timers), State#state.timers),
{noreply, State#state{timers = Timers}}; {noreply, State#state{timers = Timers}};

View File

@ -46,6 +46,9 @@
%% API (used by mod_push_keepalive). %% API (used by mod_push_keepalive).
-export([notify/1, notify/3, notify/5]). -export([notify/1, notify/3, notify/5]).
%% For IQ callbacks
-export([delete_session/3]).
-include("ejabberd.hrl"). -include("ejabberd.hrl").
-include("ejabberd_commands.hrl"). -include("ejabberd_commands.hrl").
-include("logger.hrl"). -include("logger.hrl").
@ -426,7 +429,8 @@ notify(LUser, LServer, Clients) ->
HandleResponse = fun(#iq{type = result}) -> HandleResponse = fun(#iq{type = result}) ->
ok; ok;
(#iq{type = error}) -> (#iq{type = error}) ->
delete_session(LUser, LServer, TS); spawn(?MODULE, delete_session,
[LUser, LServer, TS]);
(timeout) -> (timeout) ->
ok % Hmm. ok % Hmm.
end, end,
@ -445,8 +449,7 @@ notify(LServer, PushLJID, Node, XData, HandleResponse) ->
to = jid:make(PushLJID), to = jid:make(PushLJID),
id = randoms:get_string(), id = randoms:get_string(),
sub_els = [PubSub]}, sub_els = [PubSub]},
ejabberd_local:route_iq(IQ, HandleResponse), ejabberd_router:route_iq(IQ, HandleResponse).
ok.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Internal functions. %% Internal functions.