./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/3]).
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([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 104 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 232 {IQ, Acc} = mongoose_iq:info(Acc0),
107 232 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 71 process_iq_reply(From, To, Acc, IQReply);
112 process_iq(#iq{ xmlns = XMLNS } = IQ, Acc, From, To, _El) ->
113 161 Host = To#jid.lserver,
114 161 case ets:lookup(?IQTABLE, {XMLNS, Host}) of
115 [{_, IQHandler}] ->
116 161 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 71 case get_iq_callback_in_cluster(ID, Acc) of
132 {ok, Callback} ->
133 71 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 71 case parse_iq_id(ID) of
143 local_node ->
144 70 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 45429 try
160 45429 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
:-(
Acc
166 end.
167
168 -spec route_iq(From :: jid:jid(),
169 To :: jid:jid(),
170 Acc :: mongoose_acc:t(),
171 IQ :: jlib:iq(),
172 F :: fun()) -> mongoose_acc:t().
173 route_iq(From, To, Acc, IQ, F) ->
174 74 route_iq(From, To, Acc, IQ, F, undefined).
175
176
177 %% Send an iq and wait for response
178 %% This function is used to route IQs from the server to the client.
179 %% A callback function would be called once a response is received from the client.
180 -spec route_iq(From :: jid:jid(),
181 To :: jid:jid(),
182 Acc :: mongoose_acc:t(),
183 IQ :: jlib:iq(),
184 Callback :: callback(),
185 Timeout :: undefined | integer()) -> mongoose_acc:t().
186 route_iq(From, To, Acc, #iq{type = Type} = IQ, Callback, Timeout)
187 when is_function(Callback) ->
188 76 Packet = case Type == set orelse Type == get of
189 true ->
190 76 ID = make_iq_id(),
191 76 register_iq_response_handler(ID, Callback, Timeout),
192 76 jlib:iq_to_xml(IQ#iq{id = ID});
193 false ->
194
:-(
jlib:iq_to_xml(IQ)
195 end,
196 76 ejabberd_router:route(From, To, Acc, Packet).
197
198 -spec register_iq_response_handler(
199 ID :: id(),
200 Callback :: callback(),
201 Timeout :: undefined | pos_integer()) -> any().
202 register_iq_response_handler(ID, Callback, Timeout0) ->
203 76 Timeout = case Timeout0 of
204 undefined ->
205 75 ?IQ_TIMEOUT;
206 N when is_integer(N), N > 0 ->
207 1 N
208 end,
209 76 TRef = erlang:start_timer(Timeout, ejabberd_local, ID),
210 76 ets:insert(?IQRESPONSE, {ID, Callback, TRef}).
211
212 -spec register_iq_handler(Domain :: jid:server(), Namespace :: binary(),
213 IQHandler :: mongoose_iq_handler:t()) -> ok.
214 register_iq_handler(Domain, XMLNS, IQHandler) ->
215 244 ejabberd_local ! {register_iq_handler, Domain, XMLNS, IQHandler},
216 244 ok.
217
218 -spec sync() -> ok.
219 sync() ->
220 220 gen_server:call(ejabberd_local, sync).
221
222 -spec unregister_iq_handler(Domain :: jid:server(), Namespace :: binary()) -> ok.
223 unregister_iq_handler(Domain, XMLNS) ->
224 195 ejabberd_local ! {unregister_iq_handler, Domain, XMLNS},
225 195 ok.
226
227 -spec bounce_resource_packet(Acc :: mongoose_acc:t(),
228 From :: jid:jid(),
229 To :: jid:jid(),
230 El :: exml:element()) -> mongoose_acc:t().
231 bounce_resource_packet(Acc, From, To, El) ->
232 2 {Acc1, Err} = jlib:make_error_reply(Acc, El, mongoose_xmpp_errors:item_not_found()),
233 2 ejabberd_router:route(To, From, Acc1, Err),
234 2 Acc.
235
236 -spec register_host(Host :: jid:server()) -> ok.
237 register_host(Host) ->
238 37 gen_server:call(?MODULE, {register_host, Host}).
239
240 -spec unregister_host(Host :: jid:server()) -> ok.
241 unregister_host(Host) ->
242 13 gen_server:call(?MODULE, {unregister_host, Host}).
243
244 -spec disco_local_features(mongoose_disco:feature_acc(),
245 map(),
246 map()) -> {ok, mongoose_disco:feature_acc()}.
247 disco_local_features(Acc = #{to_jid := #jid{lserver = LServer}, node := <<>>}, _, _) ->
248 229 Features = [Feature || {_, Feature} <- ets:lookup(?NSTABLE, LServer)],
249 229 {ok, mongoose_disco:add_features(Features, Acc)};
250 disco_local_features(Acc, _, _) ->
251 5 {ok, Acc}.
252
253 %%====================================================================
254 %% gen_server callbacks
255 %%====================================================================
256
257 %%--------------------------------------------------------------------
258 %% Function: init(Args) -> {ok, State} |
259 %% {ok, State, Timeout} |
260 %% ignore |
261 %% {stop, Reason}
262 %% Description: Initiates the server
263 %%--------------------------------------------------------------------
264 init([]) ->
265 104 catch ets:new(?IQTABLE, [named_table, protected, {read_concurrency, true}]),
266 104 catch ets:new(?NSTABLE, [named_table, bag, protected, {read_concurrency, true}]),
267 104 catch ets:new(?IQRESPONSE, [named_table, public]),
268 104 gen_hook:add_handlers(hooks()),
269 104 {ok, #state{}}.
270
271 %%--------------------------------------------------------------------
272 %% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
273 %% {reply, Reply, State, Timeout} |
274 %% {noreply, State} |
275 %% {noreply, State, Timeout} |
276 %% {stop, Reason, Reply, State} |
277 %% {stop, Reason, State}
278 %% Description: Handling call messages
279 %%--------------------------------------------------------------------
280 handle_call({unregister_host, Host}, _From, State) ->
281 13 Node = node(),
282 13 [mongoose_c2s:stop(Pid, host_was_unregistered)
283 13 || #session{sid = {_, Pid}} <- ejabberd_sm:get_vh_session_list(Host),
284 9 node(Pid) =:= Node],
285 13 do_unregister_host(Host),
286 13 {reply, ok, State};
287 handle_call({register_host, Host}, _From, State) ->
288 37 do_register_host(Host),
289 37 {reply, ok, State};
290 handle_call(sync, _From, State) ->
291 220 {reply, ok, State};
292 handle_call(_Request, _From, State) ->
293
:-(
Reply = ok,
294
:-(
{reply, Reply, State}.
295
296 %%--------------------------------------------------------------------
297 %% Function: handle_cast(Msg, State) -> {noreply, State} |
298 %% {noreply, State, Timeout} |
299 %% {stop, Reason, State}
300 %% Description: Handling cast messages
301 %%--------------------------------------------------------------------
302 handle_cast(_Msg, State) ->
303
:-(
{noreply, State}.
304
305 %%--------------------------------------------------------------------
306 %% Function: handle_info(Info, State) -> {noreply, State} |
307 %% {noreply, State, Timeout} |
308 %% {stop, Reason, State}
309 %% Description: Handling all non call/cast messages
310 %%--------------------------------------------------------------------
311 handle_info({route, Acc, From, To, El}, State) ->
312
:-(
spawn(fun() -> process_packet(Acc, From, To, El, #{}) end),
313
:-(
{noreply, State};
314 handle_info({register_iq_handler, Host, XMLNS, IQHandler}, State) ->
315 244 ets:insert(?NSTABLE, {Host, XMLNS}),
316 244 ets:insert(?IQTABLE, {{XMLNS, Host}, IQHandler}),
317 244 {noreply, State};
318 handle_info({unregister_iq_handler, Host, XMLNS}, State) ->
319 195 case ets:lookup(?IQTABLE, {XMLNS, Host}) of
320 [{_, IQHandler}] ->
321 195 gen_iq_component:stop_iq_handler(IQHandler),
322 195 ets:delete_object(?NSTABLE, {Host, XMLNS}),
323 195 ets:delete(?IQTABLE, {XMLNS, Host});
324 _ ->
325
:-(
ok
326 end,
327 195 {noreply, State};
328 handle_info({timeout, _TRef, ID}, State) ->
329 5 process_iq_timeout(ID),
330 5 {noreply, State};
331 handle_info(_Info, State) ->
332
:-(
{noreply, State}.
333
334 %%--------------------------------------------------------------------
335 %% Function: terminate(Reason, State) -> void()
336 %% Description: This function is called by a gen_server when it is about to
337 %% terminate. It should be the opposite of Module:init/1 and do any necessary
338 %% cleaning up. When it returns, the gen_server terminates with Reason.
339 %% The return value is ignored.
340 %%--------------------------------------------------------------------
341 terminate(_Reason, _State) ->
342
:-(
gen_hook:delete_handlers(hooks()).
343
344 %%--------------------------------------------------------------------
345 %% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
346 %% Description: Convert process state when code is changed
347 %%--------------------------------------------------------------------
348 code_change(_OldVsn, State, _Extra) ->
349
:-(
{ok, State}.
350
351 %%--------------------------------------------------------------------
352 %%% Internal functions
353 %%--------------------------------------------------------------------
354
355 hooks() ->
356 104 [{disco_local_features, HostType, fun ?MODULE:disco_local_features/3, #{}, 99}
357 104 || HostType <- ?ALL_HOST_TYPES].
358
359 -spec do_route(Acc :: mongoose_acc:t(),
360 From :: jid:jid(),
361 To :: jid:jid(),
362 El :: exml:element()) -> mongoose_acc:t().
363 do_route(Acc, From, To, El) ->
364 45429 ?LOG_DEBUG(#{what => local_routing, acc => Acc}),
365 45429 case directed_to(To) of
366 user ->
367 45195 ejabberd_sm:route(From, To, Acc, El);
368 server ->
369 232 case El#xmlel.name of
370 <<"iq">> ->
371 232 process_iq(Acc, From, To, El);
372 _ ->
373
:-(
Acc
374 end;
375 local_resource ->
376 2 case mongoose_acc:stanza_type(Acc) of
377
:-(
<<"error">> -> Acc;
378
:-(
<<"result">> -> Acc;
379 2 _ -> bounce_resource_packet(Acc, From, To, El)
380 end
381 end.
382
383 -spec directed_to(jid:jid()) -> user | server | local_resource.
384 directed_to(To) ->
385 45429 directed_to(To#jid.luser, To#jid.lresource).
386
387 directed_to(<<>>, <<>>) ->
388 232 server;
389 directed_to(<<>>, _) ->
390 2 local_resource;
391 directed_to(_, _) ->
392 45195 user.
393
394 -spec get_iq_callback(ID :: id()) -> error | {ok, fun()}.
395 get_iq_callback(ID) ->
396 76 case ets:lookup(?IQRESPONSE, ID) of
397 [{ID, Callback, TRef}] ->
398 76 erlang:cancel_timer(TRef),
399 76 ets:delete(?IQRESPONSE, ID),
400 76 {ok, Callback};
401 _ ->
402
:-(
error
403 end.
404
405 -spec process_iq_timeout(id()) -> ok.
406 process_iq_timeout(ID) ->
407 5 spawn(fun() -> process_iq_timeout2(ID) end), ok.
408
409 -spec process_iq_timeout2(id()) -> ok.
410 process_iq_timeout2(ID) ->
411 5 case get_iq_callback(ID) of
412 {ok, Function} ->
413 5 Function(undefined, undefined, undefined, timeout), ok;
414 _ ->
415
:-(
ok
416 end.
417
418 do_register_host(Host) ->
419 37 mongoose_router:register_route(Host, mongoose_packet_handler:new(?MODULE)).
420
421 do_unregister_host(Host) ->
422 13 mongoose_router:unregister_route(Host).
423
424 make_iq_id() ->
425 %% Attach NodeId, so we know to which node to forward the response
426 76 BinNodeId = mongoose_start_node_id:node_id(),
427 76 Rand = mongoose_bin:gen_from_crypto(),
428 76 <<BinNodeId/binary, "_", Rand/binary>>.
429
430 %% Parses ID, made by make_iq_id function
431 -spec parse_iq_id(ID :: binary()) ->
432 local_node | {remote_node, node()}
433 | {error, {unknown_node_id, term()} | bad_iq_format}.
434 parse_iq_id(ID) ->
435 71 BinNodeId = mongoose_start_node_id:node_id(),
436 71 case binary:split(ID, <<"_">>) of
437 [BinNodeId, _Rest] ->
438 70 local_node;
439 [OtherBinNodeId, _Rest] ->
440 1 case mongoose_start_node_id:node_id_to_name(OtherBinNodeId) of
441 {ok, NodeName} ->
442 1 {remote_node, NodeName};
443 {error, Reason} ->
444
:-(
{error, {unknown_node_id, Reason}}
445 end;
446 _ ->
447
:-(
{error, bad_iq_format}
448 end.
Line Hits Source