./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 register_iq_response_handler/4,
48 register_iq_response_handler/5,
49 unregister_iq_handler/2,
50 unregister_host/1,
51 unregister_iq_response_handler/2,
52 sync/0
53 ]).
54
55 %% Hooks callbacks
56
57 -export([node_cleanup/2,
58 disco_local_features/1]).
59
60 %% gen_server callbacks
61 -export([init/1, handle_call/3, handle_cast/2, handle_info/2,
62 terminate/2, code_change/3]).
63
64 -export([do_route/4]).
65
66 %% For testing only
67 -export([get_iq_callback/1]).
68
69 -ignore_xref([disco_local_features/1, do_route/4, get_iq_callback/1,
70 node_cleanup/2, process_iq_reply/4, register_iq_response_handler/4,
71 register_iq_response_handler/5, start_link/0, unregister_iq_response_handler/2]).
72
73 -include("mongoose.hrl").
74 -include("jlib.hrl").
75 -include("session.hrl").
76
77 -record(state, {}).
78
79 -type id() :: any().
80 -record(iq_response, {id :: id(),
81 module,
82 function,
83 timer}).
84
85 -define(IQTABLE, local_iqtable).
86 -define(NSTABLE, local_nstable).
87
88 %% This value is used in SIP and Megaco for a transaction lifetime.
89 -define(IQ_TIMEOUT, 32000).
90
91 %%====================================================================
92 %% API
93 %%====================================================================
94 %%--------------------------------------------------------------------
95 %% Function: start_link() -> {ok, Pid} | ignore | {error, Error}
96 %% Description: Starts the server
97 %%--------------------------------------------------------------------
98 -spec start_link() -> 'ignore' | {'error', _} | {'ok', pid()}.
99 start_link() ->
100 82 gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
101
102 -spec process_iq(Acc :: mongoose_acc:t(),
103 From :: jid:jid(),
104 To :: jid:jid(),
105 El :: exml:element()
106 ) -> mongoose_acc:t().
107 process_iq(Acc0, From, To, El) ->
108 166 {IQ, Acc} = mongoose_iq:info(Acc0),
109 166 process_iq(IQ, Acc, From, To, El).
110
111 process_iq(#iq{ type = Type } = IQReply, Acc, From, To, _El)
112 when Type == result; Type == error ->
113 50 process_iq_reply(From, To, Acc, IQReply);
114 process_iq(#iq{ xmlns = XMLNS } = IQ, Acc, From, To, _El) ->
115 116 Host = To#jid.lserver,
116 116 case ets:lookup(?IQTABLE, {XMLNS, Host}) of
117 [{_, IQHandler}] ->
118 116 gen_iq_component:handle(IQHandler, Acc, From, To, IQ);
119 [] ->
120
:-(
T = <<"Local server does not implement this feature">>,
121
:-(
ejabberd_router:route_error_reply(To, From, Acc,
122 mongoose_xmpp_errors:feature_not_implemented(<<"en">>, T))
123 end;
124 process_iq(_, Acc, From, To, El) ->
125
:-(
{Acc1, Err} = jlib:make_error_reply(Acc, El, mongoose_xmpp_errors:bad_request()),
126
:-(
ejabberd_router:route(To, From, Acc1, Err).
127
128 -spec process_iq_reply(From :: jid:jid(),
129 To :: jid:jid(),
130 mongoose_acc:t(),
131 IQ :: jlib:iq() ) -> mongoose_acc:t().
132 process_iq_reply(From, To, Acc, #iq{id = ID} = IQ) ->
133 50 case get_iq_callback(ID) of
134 {ok, undefined, Function} ->
135 50 Function(From, To, Acc, IQ);
136 {ok, Module, Function} ->
137
:-(
Module:Function(From, To, Acc, IQ);
138 _ ->
139
:-(
Acc
140 end.
141
142
143 -spec process_packet(Acc :: mongoose_acc:t(),
144 From :: jid:jid(),
145 To ::jid:jid(),
146 El :: exml:element(),
147 Extra :: map()) -> mongoose_acc:t().
148 process_packet(Acc, From, To, El, _Extra) ->
149 17074 try
150 17074 do_route(Acc, From, To, El)
151 catch
152 Class:Reason:Stacktrace ->
153
:-(
?LOG_ERROR(#{what => routing_error, acc => Acc,
154
:-(
class => Class, reason => Reason, stacktrace => Stacktrace})
155 end.
156
157 -spec route_iq(From :: jid:jid(),
158 To :: jid:jid(),
159 Acc :: mongoose_acc:t(),
160 IQ :: jlib:iq(),
161 F :: fun()) -> mongoose_acc:t().
162 route_iq(From, To, Acc, IQ, F) ->
163 44 route_iq(From, To, Acc, IQ, F, undefined).
164
165
166 -spec route_iq(From :: jid:jid(),
167 To :: jid:jid(),
168 Acc :: mongoose_acc:t(),
169 IQ :: jlib:iq(),
170 F :: fun(),
171 Timeout :: undefined | integer()) -> mongoose_acc:t().
172 route_iq(From, To, Acc, #iq{type = Type} = IQ, F, Timeout) when is_function(F) ->
173 56 Packet = case Type == set orelse Type == get of
174 true ->
175 56 ID = mongoose_bin:gen_from_crypto(),
176 56 Host = From#jid.lserver,
177 56 register_iq_response_handler(Host, ID, undefined, F, Timeout),
178 56 jlib:iq_to_xml(IQ#iq{id = ID});
179 false ->
180
:-(
jlib:iq_to_xml(IQ)
181 end,
182 56 ejabberd_router:route(From, To, Acc, Packet).
183
184 register_iq_response_handler(Host, ID, Module, Function) ->
185
:-(
register_iq_response_handler(Host, ID, Module, Function, undefined).
186
187 -spec register_iq_response_handler(_Host :: jid:server(),
188 ID :: id(),
189 Module :: atom(),
190 Function :: fun(),
191 Timeout :: 'undefined' | pos_integer()) -> any().
192 register_iq_response_handler(_Host, ID, Module, Function, Timeout0) ->
193 56 Timeout = case Timeout0 of
194 undefined ->
195 44 ?IQ_TIMEOUT;
196 N when is_integer(N), N > 0 ->
197 12 N
198 end,
199 56 TRef = erlang:start_timer(Timeout, ejabberd_local, ID),
200 56 mnesia:dirty_write(#iq_response{id = ID,
201 module = Module,
202 function = Function,
203 timer = TRef}).
204
205 -spec register_iq_handler(Domain :: jid:server(), Namespace :: binary(),
206 IQHandler :: mongoose_iq_handler:t()) -> ok.
207 register_iq_handler(Domain, XMLNS, IQHandler) ->
208 194 ejabberd_local ! {register_iq_handler, Domain, XMLNS, IQHandler},
209 194 ok.
210
211 -spec sync() -> ok.
212 sync() ->
213 178 gen_server:call(ejabberd_local, sync).
214
215 -spec unregister_iq_response_handler(_Host :: jid:server(),
216 ID :: id()) -> 'ok'.
217 unregister_iq_response_handler(_Host, ID) ->
218
:-(
catch get_iq_callback(ID),
219
:-(
ok.
220
221 -spec unregister_iq_handler(Domain :: jid:server(), Namespace :: binary()) -> ok.
222 unregister_iq_handler(Domain, XMLNS) ->
223 161 ejabberd_local ! {unregister_iq_handler, Domain, XMLNS},
224 161 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 25 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 139 Features = [Feature || {_, Feature} <- ets:lookup(?NSTABLE, LServer)],
246 139 mongoose_disco:add_features(Features, Acc);
247 disco_local_features(Acc) ->
248 5 Acc.
249
250 %%====================================================================
251 %% API
252 %%====================================================================
253
254 node_cleanup(Acc, Node) ->
255
:-(
F = fun() ->
256
:-(
Keys = mnesia:select(
257 iq_response,
258 [{#iq_response{timer = '$1', id = '$2', _ = '_'},
259 [{'==', {node, '$1'}, Node}],
260 ['$2']}]),
261
:-(
lists:foreach(fun(Key) ->
262
:-(
mnesia:delete({iq_response, Key})
263 end, Keys)
264 end,
265
:-(
Res = mnesia:async_dirty(F),
266
:-(
maps:put(?MODULE, Res, Acc).
267
268 %%====================================================================
269 %% gen_server callbacks
270 %%====================================================================
271
272 %%--------------------------------------------------------------------
273 %% Function: init(Args) -> {ok, State} |
274 %% {ok, State, Timeout} |
275 %% ignore |
276 %% {stop, Reason}
277 %% Description: Initiates the server
278 %%--------------------------------------------------------------------
279 init([]) ->
280 82 catch ets:new(?IQTABLE, [named_table, protected, {read_concurrency, true}]),
281 82 catch ets:new(?NSTABLE, [named_table, bag, protected, {read_concurrency, true}]),
282 82 update_table(),
283 82 mnesia:create_table(iq_response,
284 [{ram_copies, [node()]},
285 {attributes, record_info(fields, iq_response)}]),
286 82 mnesia:add_table_copy(iq_response, node(), ram_copies),
287 82 ejabberd_hooks:add(hooks()),
288 82 {ok, #state{}}.
289
290 %%--------------------------------------------------------------------
291 %% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
292 %% {reply, Reply, State, Timeout} |
293 %% {noreply, State} |
294 %% {noreply, State, Timeout} |
295 %% {stop, Reason, Reply, State} |
296 %% {stop, Reason, State}
297 %% Description: Handling call messages
298 %%--------------------------------------------------------------------
299 handle_call({unregister_host, Host}, _From, State) ->
300 7 Node = node(),
301 7 [ejabberd_c2s:stop(Pid)
302 7 || #session{sid = {_, Pid}} <- ejabberd_sm:get_vh_session_list(Host),
303 3 node(Pid) =:= Node],
304 7 do_unregister_host(Host),
305 7 {reply, ok, State};
306 handle_call({register_host, Host}, _From, State) ->
307 25 do_register_host(Host),
308 25 {reply, ok, State};
309 handle_call(sync, _From, State) ->
310 178 {reply, ok, State};
311 handle_call(_Request, _From, State) ->
312
:-(
Reply = ok,
313
:-(
{reply, Reply, State}.
314
315 %%--------------------------------------------------------------------
316 %% Function: handle_cast(Msg, State) -> {noreply, State} |
317 %% {noreply, State, Timeout} |
318 %% {stop, Reason, State}
319 %% Description: Handling cast messages
320 %%--------------------------------------------------------------------
321 handle_cast(_Msg, State) ->
322
:-(
{noreply, State}.
323
324 %%--------------------------------------------------------------------
325 %% Function: handle_info(Info, State) -> {noreply, State} |
326 %% {noreply, State, Timeout} |
327 %% {stop, Reason, State}
328 %% Description: Handling all non call/cast messages
329 %%--------------------------------------------------------------------
330 handle_info({route, Acc, From, To, El}, State) ->
331
:-(
process_packet(Acc, From, To, El, #{}),
332
:-(
{noreply, State};
333 handle_info({register_iq_handler, Host, XMLNS, IQHandler}, State) ->
334 194 ets:insert(?NSTABLE, {Host, XMLNS}),
335 194 ets:insert(?IQTABLE, {{XMLNS, Host}, IQHandler}),
336 194 {noreply, State};
337 handle_info({unregister_iq_handler, Host, XMLNS}, State) ->
338 161 case ets:lookup(?IQTABLE, {XMLNS, Host}) of
339 [{_, IQHandler}] ->
340 161 gen_iq_component:stop_iq_handler(IQHandler),
341 161 ets:delete_object(?NSTABLE, {Host, XMLNS}),
342 161 ets:delete(?IQTABLE, {XMLNS, Host});
343 _ ->
344
:-(
ok
345 end,
346 161 {noreply, State};
347 handle_info({timeout, _TRef, ID}, State) ->
348 6 process_iq_timeout(ID),
349 6 {noreply, State};
350 handle_info(_Info, State) ->
351
:-(
{noreply, State}.
352
353 %%--------------------------------------------------------------------
354 %% Function: terminate(Reason, State) -> void()
355 %% Description: This function is called by a gen_server when it is about to
356 %% terminate. It should be the opposite of Module:init/1 and do any necessary
357 %% cleaning up. When it returns, the gen_server terminates with Reason.
358 %% The return value is ignored.
359 %%--------------------------------------------------------------------
360 terminate(_Reason, _State) ->
361
:-(
ejabberd_hooks:delete(hooks()).
362
363 %%--------------------------------------------------------------------
364 %% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
365 %% Description: Convert process state when code is changed
366 %%--------------------------------------------------------------------
367 code_change(_OldVsn, State, _Extra) ->
368
:-(
{ok, State}.
369
370 %%--------------------------------------------------------------------
371 %%% Internal functions
372 %%--------------------------------------------------------------------
373
374 hooks() ->
375 82 [{node_cleanup, global, ?MODULE, node_cleanup, 50} |
376 421 [{disco_local_features, HostType, ?MODULE, disco_local_features, 99} ||
377 82 HostType <- ?ALL_HOST_TYPES]].
378
379 -spec do_route(Acc :: mongoose_acc:t(),
380 From :: jid:jid(),
381 To :: jid:jid(),
382 El :: exml:element()) -> mongoose_acc:t().
383 do_route(Acc, From, To, El) ->
384 17074 ?LOG_DEBUG(#{what => local_routing, acc => Acc}),
385 17074 case directed_to(To) of
386 user ->
387 16906 ejabberd_sm:route(From, To, Acc, El);
388 server ->
389 166 case El#xmlel.name of
390 <<"iq">> ->
391 166 process_iq(Acc, From, To, El);
392 _ ->
393
:-(
Acc
394 end;
395 local_resource ->
396 2 case mongoose_acc:stanza_type(Acc) of
397
:-(
<<"error">> -> Acc;
398
:-(
<<"result">> -> Acc;
399 2 _ -> bounce_resource_packet(Acc, From, To, El)
400 end
401 end.
402
403 -spec directed_to(jid:jid()) -> user | server | local_resource.
404 directed_to(To) ->
405 17074 directed_to(To#jid.luser, To#jid.lresource).
406
407 directed_to(<<>>, <<>>) ->
408 166 server;
409 directed_to(<<>>, _) ->
410 2 local_resource;
411 directed_to(_, _) ->
412 16906 user.
413
414 -spec update_table() -> ok | {atomic|aborted, _}.
415 update_table() ->
416 82 case catch mnesia:table_info(iq_response, attributes) of
417 [id, module, function] ->
418
:-(
mnesia:delete_table(iq_response);
419 [id, module, function, timer] ->
420 55 ok;
421 {'EXIT', _} ->
422 27 ok
423 end.
424
425 -spec get_iq_callback(ID :: id()) -> 'error' | {'ok', Mod :: atom(), fun() | atom()}.
426 get_iq_callback(ID) ->
427 56 case mnesia:dirty_read(iq_response, ID) of
428 [#iq_response{module = Module, timer = TRef,
429 function = Function}] ->
430 56 cancel_timer(TRef),
431 56 mnesia:dirty_delete(iq_response, ID),
432 56 {ok, Module, Function};
433 _ ->
434
:-(
error
435 end.
436
437 -spec process_iq_timeout(id()) -> id().
438 process_iq_timeout(ID) ->
439 6 spawn(fun process_iq_timeout/0) ! ID.
440
441 -spec process_iq_timeout() -> ok | any().
442 process_iq_timeout() ->
443 6 receive
444 ID ->
445 6 case get_iq_callback(ID) of
446 {ok, undefined, Function} ->
447 6 Function(undefined, undefined, undefined, timeout);
448 _ ->
449
:-(
ok
450 end
451 after 5000 ->
452
:-(
ok
453 end.
454
455 -spec cancel_timer(reference()) -> 'ok'.
456 cancel_timer(TRef) ->
457 56 case erlang:cancel_timer(TRef) of
458 false ->
459 6 receive
460 {timeout, TRef, _} ->
461
:-(
ok
462 after 0 ->
463 6 ok
464 end;
465 _ ->
466 50 ok
467 end.
468
469 do_register_host(Host) ->
470 25 mongoose_router:register_route(Host, mongoose_packet_handler:new(?MODULE)).
471
472 do_unregister_host(Host) ->
473 7 mongoose_router:unregister_route(Host).
Line Hits Source