./ct_report/coverage/ejabberd_local.COVER.html

1 %%%----------------------------------------------------------------------
2 %%% File : ejabberd_local.erl
3 %%% Author : Alexey Shchepin <alexey@process-one.net>
4 %%% Purpose : Route local packets
5 %%% Created : 30 Nov 2002 by Alexey Shchepin <alexey@process-one.net>
6 %%%
7 %%%
8 %%% ejabberd, Copyright (C) 2002-2011 ProcessOne
9 %%%
10 %%% This program is free software; you can redistribute it and/or
11 %%% modify it under the terms of the GNU General Public License as
12 %%% published by the Free Software Foundation; either version 2 of the
13 %%% License, or (at your option) any later version.
14 %%%
15 %%% This program is distributed in the hope that it will be useful,
16 %%% but WITHOUT ANY WARRANTY; without even the implied warranty of
17 %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 %%% General Public License for more details.
19 %%%
20 %%% You should have received a copy of the GNU General Public License
21 %%% along with this program; if not, write to the Free Software
22 %%% Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
23 %%%
24 %%%----------------------------------------------------------------------
25
26 %%%----------------------------------------------------------------------
27 %%% FIXME: the code in this module uses Host term to identify domain
28 %%% name, not a host type.
29 %%%----------------------------------------------------------------------
30
31 -module(ejabberd_local).
32 -author('alexey@process-one.net').
33
34 -behaviour(gen_server).
35 -behaviour(mongoose_packet_handler).
36 -behaviour(gen_iq_component).
37
38 %% API
39 -export([start_link/0]).
40
41 -export([process_packet/5,
42 route_iq/5,
43 route_iq/6,
44 process_iq_reply/4,
45 register_iq_handler/3,
46 register_host/1,
47 unregister_iq_handler/2,
48 unregister_host/1,
49 sync/0
50 ]).
51
52 %% RPC callbacks
53 -export([get_iq_callback/1]).
54
55 %% Hooks callbacks
56
57 -export([disco_local_features/1]).
58
59 %% gen_server callbacks
60 -export([init/1, handle_call/3, handle_cast/2, handle_info/2,
61 terminate/2, code_change/3]).
62
63 -export([do_route/4]).
64
65 -ignore_xref([disco_local_features/1, do_route/4, get_iq_callback/1,
66 process_iq_reply/4, start_link/0]).
67
68 -include("mongoose.hrl").
69 -include("jlib.hrl").
70 -include("session.hrl").
71
72 -record(state, {}).
73
74 -type id() :: binary().
75 -type callback() :: fun((From :: jid:jid(), To :: jid:jid(),
76 Acc :: mongoose_acc:t(), IQ :: jlib:iq()) ->
77 mongoose_acc:t()) |
78 fun((From :: undefined, To :: undefined,
79 Acc :: undefined, IQ :: timeout) ->
80 undefined).
81
82 -define(IQTABLE, local_iqtable).
83 -define(NSTABLE, local_nstable).
84 -define(IQRESPONSE, local_iqresponse).
85
86 %% This value is used in SIP and Megaco for a transaction lifetime.
87 -define(IQ_TIMEOUT, 32000).
88
89 %%====================================================================
90 %% API
91 %%====================================================================
92 %%--------------------------------------------------------------------
93 %% Function: start_link() -> {ok, Pid} | ignore | {error, Error}
94 %% Description: Starts the server
95 %%--------------------------------------------------------------------
96 -spec start_link() -> 'ignore' | {'error', _} | {'ok', pid()}.
97 start_link() ->
98 83 gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
99
100 -spec process_iq(Acc :: mongoose_acc:t(),
101 From :: jid:jid(),
102 To :: jid:jid(),
103 El :: exml:element()
104 ) -> mongoose_acc:t().
105 process_iq(Acc0, From, To, El) ->
106 171 {IQ, Acc} = mongoose_iq:info(Acc0),
107 171 process_iq(IQ, Acc, From, To, El).
108
109 process_iq(#iq{ type = Type } = IQReply, Acc, From, To, _El)
110 when Type == result; Type == error ->
111 51 process_iq_reply(From, To, Acc, IQReply);
112 process_iq(#iq{ xmlns = XMLNS } = IQ, Acc, From, To, _El) ->
113 120 Host = To#jid.lserver,
114 120 case ets:lookup(?IQTABLE, {XMLNS, Host}) of
115 [{_, IQHandler}] ->
116 120 gen_iq_component:handle(IQHandler, Acc, From, To, IQ);
117 [] ->
118
:-(
T = <<"Local server does not implement this feature">>,
119
:-(
ejabberd_router:route_error_reply(To, From, Acc,
120 mongoose_xmpp_errors:feature_not_implemented(<<"en">>, T))
121 end;
122 process_iq(_, Acc, From, To, El) ->
123
:-(
{Acc1, Err} = jlib:make_error_reply(Acc, El, mongoose_xmpp_errors:bad_request()),
124
:-(
ejabberd_router:route(To, From, Acc1, Err).
125
126 -spec process_iq_reply(From :: jid:jid(),
127 To :: jid:jid(),
128 Acc :: mongoose_acc:t(),
129 IQ :: jlib:iq()) -> mongoose_acc:t().
130 process_iq_reply(From, To, Acc, #iq{id = ID} = IQ) ->
131 51 case get_iq_callback_in_cluster(ID, Acc) of
132 {ok, Callback} ->
133 51 Callback(From, To, Acc, IQ);
134 _ ->
135
:-(
Acc
136 end.
137
138 -spec get_iq_callback_in_cluster(id(), mongoose_acc:t()) ->
139 {ok, callback()} | {error, term()}.
140 get_iq_callback_in_cluster(ID, Acc) ->
141 %% We store information from which node the request is originating in the ID
142 51 case parse_iq_id(ID) of
143 local_node ->
144 50 get_iq_callback(ID);
145 {remote_node, NodeName} ->
146 1 rpc:call(NodeName, ?MODULE, get_iq_callback, [ID]);
147 {error, Reason} ->
148
:-(
?LOG_ERROR(#{what => parse_iq_id_failed,
149
:-(
reason => Reason, acc => Acc}),
150
:-(
{error, Reason}
151 end.
152
153 -spec process_packet(Acc :: mongoose_acc:t(),
154 From :: jid:jid(),
155 To ::jid:jid(),
156 El :: exml:element(),
157 Extra :: map()) -> mongoose_acc:t().
158 process_packet(Acc, From, To, El, _Extra) ->
159 18951 try
160 18951 do_route(Acc, From, To, El)
161 catch
162 Class:Reason:Stacktrace ->
163
:-(
?LOG_ERROR(#{what => routing_error, acc => Acc,
164
:-(
class => Class, reason => Reason, stacktrace => Stacktrace})
165 end.
166
167 -spec route_iq(From :: jid:jid(),
168 To :: jid:jid(),
169 Acc :: mongoose_acc:t(),
170 IQ :: jlib:iq(),
171 F :: fun()) -> mongoose_acc:t().
172 route_iq(From, To, Acc, IQ, F) ->
173 44 route_iq(From, To, Acc, IQ, F, undefined).
174
175
176 %% Send an iq and wait for response
177 %% This function is used to route IQs from the server to the client.
178 %% A callback function would be called once a response is received from the client.
179 -spec route_iq(From :: jid:jid(),
180 To :: jid:jid(),
181 Acc :: mongoose_acc:t(),
182 IQ :: jlib:iq(),
183 Callback :: callback(),
184 Timeout :: undefined | integer()) -> mongoose_acc:t().
185 route_iq(From, To, Acc, #iq{type = Type} = IQ, Callback, Timeout)
186 when is_function(Callback) ->
187 58 Packet = case Type == set orelse Type == get of
188 true ->
189 58 ID = make_iq_id(),
190 58 register_iq_response_handler(ID, Callback, Timeout),
191 58 jlib:iq_to_xml(IQ#iq{id = ID});
192 false ->
193
:-(
jlib:iq_to_xml(IQ)
194 end,
195 58 ejabberd_router:route(From, To, Acc, Packet).
196
197 -spec register_iq_response_handler(
198 ID :: id(),
199 Callback :: callback(),
200 Timeout :: undefined | pos_integer()) -> any().
201 register_iq_response_handler(ID, Callback, Timeout0) ->
202 58 Timeout = case Timeout0 of
203 undefined ->
204 45 ?IQ_TIMEOUT;
205 N when is_integer(N), N > 0 ->
206 13 N
207 end,
208 58 TRef = erlang:start_timer(Timeout, ejabberd_local, ID),
209 58 ets:insert(?IQRESPONSE, {ID, Callback, TRef}).
210
211 -spec register_iq_handler(Domain :: jid:server(), Namespace :: binary(),
212 IQHandler :: mongoose_iq_handler:t()) -> ok.
213 register_iq_handler(Domain, XMLNS, IQHandler) ->
214 190 ejabberd_local ! {register_iq_handler, Domain, XMLNS, IQHandler},
215 190 ok.
216
217 -spec sync() -> ok.
218 sync() ->
219 173 gen_server:call(ejabberd_local, sync).
220
221 -spec unregister_iq_handler(Domain :: jid:server(), Namespace :: binary()) -> ok.
222 unregister_iq_handler(Domain, XMLNS) ->
223 151 ejabberd_local ! {unregister_iq_handler, Domain, XMLNS},
224 151 ok.
225
226 -spec bounce_resource_packet(Acc :: mongoose_acc:t(),
227 From :: jid:jid(),
228 To :: jid:jid(),
229 El :: exml:element()) -> mongoose_acc:t().
230 bounce_resource_packet(Acc, From, To, El) ->
231 2 {Acc1, Err} = jlib:make_error_reply(Acc, El, mongoose_xmpp_errors:item_not_found()),
232 2 ejabberd_router:route(To, From, Acc1, Err),
233 2 Acc.
234
235 -spec register_host(Host :: jid:server()) -> ok.
236 register_host(Host) ->
237 26 gen_server:call(?MODULE, {register_host, Host}).
238
239 -spec unregister_host(Host :: jid:server()) -> ok.
240 unregister_host(Host) ->
241 7 gen_server:call(?MODULE, {unregister_host, Host}).
242
243 -spec disco_local_features(mongoose_disco:feature_acc()) -> mongoose_disco:feature_acc().
244 disco_local_features(Acc = #{to_jid := #jid{lserver = LServer}, node := <<>>}) ->
245 142 Features = [Feature || {_, Feature} <- ets:lookup(?NSTABLE, LServer)],
246 142 mongoose_disco:add_features(Features, Acc);
247 disco_local_features(Acc) ->
248 5 Acc.
249
250 %%====================================================================
251 %% gen_server callbacks
252 %%====================================================================
253
254 %%--------------------------------------------------------------------
255 %% Function: init(Args) -> {ok, State} |
256 %% {ok, State, Timeout} |
257 %% ignore |
258 %% {stop, Reason}
259 %% Description: Initiates the server
260 %%--------------------------------------------------------------------
261 init([]) ->
262 83 catch ets:new(?IQTABLE, [named_table, protected, {read_concurrency, true}]),
263 83 catch ets:new(?NSTABLE, [named_table, bag, protected, {read_concurrency, true}]),
264 83 catch ets:new(?IQRESPONSE, [named_table, public]),
265 83 ejabberd_hooks:add(hooks()),
266 83 {ok, #state{}}.
267
268 %%--------------------------------------------------------------------
269 %% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
270 %% {reply, Reply, State, Timeout} |
271 %% {noreply, State} |
272 %% {noreply, State, Timeout} |
273 %% {stop, Reason, Reply, State} |
274 %% {stop, Reason, State}
275 %% Description: Handling call messages
276 %%--------------------------------------------------------------------
277 handle_call({unregister_host, Host}, _From, State) ->
278 7 Node = node(),
279 7 [ejabberd_c2s:stop(Pid)
280 7 || #session{sid = {_, Pid}} <- ejabberd_sm:get_vh_session_list(Host),
281 3 node(Pid) =:= Node],
282 7 do_unregister_host(Host),
283 7 {reply, ok, State};
284 handle_call({register_host, Host}, _From, State) ->
285 26 do_register_host(Host),
286 26 {reply, ok, State};
287 handle_call(sync, _From, State) ->
288 173 {reply, ok, State};
289 handle_call(_Request, _From, State) ->
290
:-(
Reply = ok,
291
:-(
{reply, Reply, State}.
292
293 %%--------------------------------------------------------------------
294 %% Function: handle_cast(Msg, State) -> {noreply, State} |
295 %% {noreply, State, Timeout} |
296 %% {stop, Reason, State}
297 %% Description: Handling cast messages
298 %%--------------------------------------------------------------------
299 handle_cast(_Msg, State) ->
300
:-(
{noreply, State}.
301
302 %%--------------------------------------------------------------------
303 %% Function: handle_info(Info, State) -> {noreply, State} |
304 %% {noreply, State, Timeout} |
305 %% {stop, Reason, State}
306 %% Description: Handling all non call/cast messages
307 %%--------------------------------------------------------------------
308 handle_info({route, Acc, From, To, El}, State) ->
309
:-(
spawn(fun() -> process_packet(Acc, From, To, El, #{}) end),
310
:-(
{noreply, State};
311 handle_info({register_iq_handler, Host, XMLNS, IQHandler}, State) ->
312 190 ets:insert(?NSTABLE, {Host, XMLNS}),
313 190 ets:insert(?IQTABLE, {{XMLNS, Host}, IQHandler}),
314 190 {noreply, State};
315 handle_info({unregister_iq_handler, Host, XMLNS}, State) ->
316 151 case ets:lookup(?IQTABLE, {XMLNS, Host}) of
317 [{_, IQHandler}] ->
318 151 gen_iq_component:stop_iq_handler(IQHandler),
319 151 ets:delete_object(?NSTABLE, {Host, XMLNS}),
320 151 ets:delete(?IQTABLE, {XMLNS, Host});
321 _ ->
322
:-(
ok
323 end,
324 151 {noreply, State};
325 handle_info({timeout, _TRef, ID}, State) ->
326 7 process_iq_timeout(ID),
327 7 {noreply, State};
328 handle_info(_Info, State) ->
329
:-(
{noreply, State}.
330
331 %%--------------------------------------------------------------------
332 %% Function: terminate(Reason, State) -> void()
333 %% Description: This function is called by a gen_server when it is about to
334 %% terminate. It should be the opposite of Module:init/1 and do any necessary
335 %% cleaning up. When it returns, the gen_server terminates with Reason.
336 %% The return value is ignored.
337 %%--------------------------------------------------------------------
338 terminate(_Reason, _State) ->
339
:-(
ejabberd_hooks:delete(hooks()).
340
341 %%--------------------------------------------------------------------
342 %% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
343 %% Description: Convert process state when code is changed
344 %%--------------------------------------------------------------------
345 code_change(_OldVsn, State, _Extra) ->
346
:-(
{ok, State}.
347
348 %%--------------------------------------------------------------------
349 %%% Internal functions
350 %%--------------------------------------------------------------------
351
352 hooks() ->
353 83 [{disco_local_features, HostType, ?MODULE, disco_local_features, 99}
354 83 || HostType <- ?ALL_HOST_TYPES].
355
356 -spec do_route(Acc :: mongoose_acc:t(),
357 From :: jid:jid(),
358 To :: jid:jid(),
359 El :: exml:element()) -> mongoose_acc:t().
360 do_route(Acc, From, To, El) ->
361 18951 ?LOG_DEBUG(#{what => local_routing, acc => Acc}),
362 18951 case directed_to(To) of
363 user ->
364 18778 ejabberd_sm:route(From, To, Acc, El);
365 server ->
366 171 case El#xmlel.name of
367 <<"iq">> ->
368 171 process_iq(Acc, From, To, El);
369 _ ->
370
:-(
Acc
371 end;
372 local_resource ->
373 2 case mongoose_acc:stanza_type(Acc) of
374
:-(
<<"error">> -> Acc;
375
:-(
<<"result">> -> Acc;
376 2 _ -> bounce_resource_packet(Acc, From, To, El)
377 end
378 end.
379
380 -spec directed_to(jid:jid()) -> user | server | local_resource.
381 directed_to(To) ->
382 18951 directed_to(To#jid.luser, To#jid.lresource).
383
384 directed_to(<<>>, <<>>) ->
385 171 server;
386 directed_to(<<>>, _) ->
387 2 local_resource;
388 directed_to(_, _) ->
389 18778 user.
390
391 -spec get_iq_callback(ID :: id()) -> error | {ok, fun()}.
392 get_iq_callback(ID) ->
393 58 case ets:lookup(?IQRESPONSE, ID) of
394 [{ID, Callback, TRef}] ->
395 58 erlang:cancel_timer(TRef),
396 58 ets:delete(?IQRESPONSE, ID),
397 58 {ok, Callback};
398 _ ->
399
:-(
error
400 end.
401
402 -spec process_iq_timeout(id()) -> ok.
403 process_iq_timeout(ID) ->
404 7 spawn(fun() -> process_iq_timeout2(ID) end), ok.
405
406 -spec process_iq_timeout2(id()) -> ok.
407 process_iq_timeout2(ID) ->
408 7 case get_iq_callback(ID) of
409 {ok, Function} ->
410 7 Function(undefined, undefined, undefined, timeout), ok;
411 _ ->
412
:-(
ok
413 end.
414
415 do_register_host(Host) ->
416 26 mongoose_router:register_route(Host, mongoose_packet_handler:new(?MODULE)).
417
418 do_unregister_host(Host) ->
419 7 mongoose_router:unregister_route(Host).
420
421 make_iq_id() ->
422 %% Attach NodeId, so we know to which node to forward the response
423 58 {ok, NodeId} = ejabberd_node_id:node_id(),
424 58 Rand = mongoose_bin:gen_from_crypto(),
425 58 <<(integer_to_binary(NodeId))/binary, "_", Rand/binary>>.
426
427 %% Parses ID, made by make_iq_id function
428 -spec parse_iq_id(ID :: binary()) ->
429 local_node | {remote_node, node()}
430 | {error, {unknown_node_id, term()} | bad_iq_format}.
431 parse_iq_id(ID) ->
432 51 {ok, NodeId} = ejabberd_node_id:node_id(),
433 51 BinNodeId = integer_to_binary(NodeId),
434 51 case binary:split(ID, <<"_">>) of
435 [BinNodeId, _Rest] ->
436 50 local_node;
437 [OtherBinNodeId, _Rest] ->
438 1 OtherNodeId = binary_to_integer(OtherBinNodeId),
439 1 case ejabberd_node_id:node_id_to_name(OtherNodeId) of
440 {ok, NodeName} ->
441 1 {remote_node, NodeName};
442 {error, Reason} ->
443
:-(
{error, {unknown_node_id, Reason}}
444 end;
445 _ ->
446
:-(
{error, bad_iq_format}
447 end.
Line Hits Source