./ct_report/coverage/ejabberd_service.COVER.html

1 %%%----------------------------------------------------------------------
2 %%% File : ejabberd_service.erl
3 %%% Author : Alexey Shchepin <alexey@process-one.net>
4 %%% Purpose : External component management (XEP-0114)
5 %%% Created : 6 Dec 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 -module(ejabberd_service).
27 -author('alexey@process-one.net').
28 -xep([{xep, 114}, {version, "1.6"}]).
29
30 -behaviour(p1_fsm).
31 -behaviour(mongoose_packet_handler).
32 -behaviour(mongoose_listener).
33
34 %% mongoose_listener API
35 -export([start_listener/1]).
36
37 %% External exports
38 -export([start/2,
39 start_link/2]).
40
41 %% gen_fsm callbacks
42 -export([init/1,
43 wait_for_stream/2,
44 wait_for_handshake/2,
45 stream_established/2,
46 handle_event/3,
47 handle_sync_event/4,
48 code_change/4,
49 handle_info/3,
50 terminate/3,
51 print_state/1]).
52
53 %% packet handler callback
54 -export([process_packet/5]).
55
56 -ignore_xref([print_state/1, start_listener/1, start_link/2, start/2,
57 stream_established/2, wait_for_handshake/2, wait_for_stream/2]).
58
59 -include("mongoose.hrl").
60 -include("jlib.hrl").
61 -include("external_component.hrl").
62
63 -type conflict_behaviour() :: disconnect | kick_old.
64
65 -record(state, {socket,
66 socket_monitor,
67 streamid,
68 password :: string(),
69 host :: binary() | undefined,
70 is_subdomain :: boolean(),
71 hidden_components = false :: boolean(),
72 conflict_behaviour :: conflict_behaviour(),
73 access,
74 check_from,
75 components = [] :: [mongoose_component:external_component()]
76 }).
77 -type state() :: #state{}.
78
79 -type statename() :: wait_for_stream
80 | wait_for_handshake
81 | stream_established.
82 %% FSM handler return value
83 -type fsm_return() :: {'stop', Reason :: 'normal', state()}
84 | {'next_state', statename(), state()}
85 | {'next_state', statename(), state(), Timeout :: integer()}.
86 %-define(DBGFSM, true).
87
88 -ifdef(DBGFSM).
89 -define(FSMOPTS, [{debug, [trace]}]).
90 -else.
91 -define(FSMOPTS, []).
92 -endif.
93
94 -define(STREAM_HEADER,
95 <<"<?xml version='1.0'?>"
96 "<stream:stream "
97 "xmlns:stream='http://etherx.jabber.org/streams' "
98 "xmlns='jabber:component:accept' "
99 "id='~s' from='~s'>">>
100 ).
101
102 -define(INVALID_HEADER_ERR,
103 <<"<stream:stream "
104 "xmlns:stream='http://etherx.jabber.org/streams'>"
105 "<stream:error>Invalid Stream Header</stream:error>"
106 "</stream:stream>">>
107 ).
108
109 -define(INVALID_HANDSHAKE_ERR,
110 <<"<stream:error>"
111 "<not-authorized xmlns='urn:ietf:params:xml:ns:xmpp-streams'/>"
112 "<text xmlns='urn:ietf:params:xml:ns:xmpp-streams' xml:lang='en'>"
113 "Invalid Handshake</text>"
114 "</stream:error>"
115 "</stream:stream>">>
116 ).
117
118 -type socket() :: term().
119 -type options() :: #{access := atom(),
120 shaper_rule := atom(),
121 password := binary(),
122 check_from := boolean(),
123 hidden_components := boolean(),
124 conflict_behaviour := conflict_behaviour(),
125 atom() => any()}.
126
127 %%%----------------------------------------------------------------------
128 %%% API
129 %%%----------------------------------------------------------------------
130 -spec start(socket(), options()) ->
131 {error, _} | {ok, undefined | pid()} | {ok, undefined | pid(), _}.
132 start(Socket, Opts) ->
133 31 supervisor:start_child(ejabberd_service_sup, [Socket, Opts]).
134
135
136 -spec start_link(socket(), options()) -> ignore | {error, _} | {ok, pid()}.
137 start_link(SockData, Opts) ->
138 31 p1_fsm:start_link(ejabberd_service, [SockData, Opts],
139 fsm_limit_opts(Opts) ++ ?FSMOPTS).
140
141 -spec start_listener(options()) -> ok.
142 start_listener(Opts) ->
143 194 mongoose_tcp_listener:start_listener(Opts).
144
145 %%%----------------------------------------------------------------------
146 %%% mongoose_packet_handler callback
147 %%%----------------------------------------------------------------------
148
149 -spec process_packet(Acc :: mongoose_acc:t(), From :: jid:jid(), To :: jid:jid(),
150 El :: exml:element(), #{pid := pid()}) -> mongoose_acc:t().
151 process_packet(Acc, _From, _To, _El, #{pid := Pid}) ->
152 18 Pid ! {route, Acc},
153 18 Acc.
154
155 %%%----------------------------------------------------------------------
156 %%% Callback functions from gen_fsm
157 %%%----------------------------------------------------------------------
158
159 %%----------------------------------------------------------------------
160 %% Func: init/1
161 %% Returns: {ok, StateName, StateData} |
162 %% {ok, StateName, StateData, Timeout} |
163 %% ignore |
164 %% {stop, StopReason}
165 %%----------------------------------------------------------------------
166 -spec init([socket() | options(), ...]) -> {'ok', 'wait_for_stream', state()}.
167 init([Socket, Opts]) ->
168 31 ?LOG_INFO(#{what => comp_started,
169 text => <<"External service connected">>,
170 31 socket => Socket}),
171 31 #{access := Access, shaper_rule := Shaper, password := Password,
172 check_from := CheckFrom, hidden_components := HiddenComponents,
173 conflict_behaviour := ConflictBehaviour} = Opts,
174 31 mongoose_transport:change_shaper(Socket, Shaper),
175 31 SocketMonitor = mongoose_transport:monitor(Socket),
176 31 {ok, wait_for_stream, #state{socket = Socket,
177 socket_monitor = SocketMonitor,
178 streamid = new_id(),
179 password = Password,
180 access = Access,
181 check_from = CheckFrom,
182 is_subdomain = false,
183 hidden_components = HiddenComponents,
184 conflict_behaviour = ConflictBehaviour
185 }}.
186
187 %%----------------------------------------------------------------------
188 %% Func: StateName/2
189 %% Returns: {next_state, NextStateName, NextStateData} |
190 %% {next_state, NextStateName, NextStateData, Timeout} |
191 %% {stop, Reason, NewStateData}
192 %%----------------------------------------------------------------------
193
194 -spec wait_for_stream(ejabberd:xml_stream_item(), state()) -> fsm_return().
195 wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
196 31 case xml:get_attr_s(<<"xmlns">>, Attrs) of
197 <<"jabber:component:accept">> ->
198 %% Note: XEP-0114 requires to check that destination is a Jabber
199 %% component served by this Jabber server.
200 %% However several transports don't respect that,
201 %% so ejabberd doesn't check 'to' attribute (EJAB-717)
202 31 To = xml:get_attr_s(<<"to">>, Attrs),
203 31 Header = io_lib:format(?STREAM_HEADER, [StateData#state.streamid, To]),
204 31 IsSubdomain = case xml:get_attr_s(<<"is_subdomain">>, Attrs) of
205 1 <<"true">> -> true;
206 30 _ -> false
207 end,
208 31 send_text(StateData, list_to_binary(Header)),
209 31 StateData1 = StateData#state{host = To,
210 is_subdomain = IsSubdomain},
211 31 {next_state, wait_for_handshake, StateData1};
212 _ ->
213
:-(
send_text(StateData, ?INVALID_HEADER_ERR),
214
:-(
{stop, normal, StateData}
215 end;
216 wait_for_stream({xmlstreamerror, _}, StateData) ->
217
:-(
Header = io_lib:format(?STREAM_HEADER,
218 [<<"none">>, ?MYNAME]),
219
:-(
send_text(StateData, iolist_to_binary(Header)),
220
:-(
send_element(StateData, mongoose_xmpp_errors:xml_not_well_formed()),
221
:-(
send_text(StateData, ?STREAM_TRAILER),
222
:-(
{stop, normal, StateData};
223 wait_for_stream(closed, StateData) ->
224
:-(
{stop, normal, StateData};
225 wait_for_stream(replaced, StateData) ->
226
:-(
{stop, normal, StateData}.
227
228
229 -spec wait_for_handshake(ejabberd:xml_stream_item(), state()) -> fsm_return().
230 wait_for_handshake({xmlstreamelement, El}, StateData) ->
231 31 #xmlel{name = Name, children = Els} = El,
232 31 case {Name, xml:get_cdata(Els)} of
233 {<<"handshake">>, Digest} ->
234 31 case mongoose_bin:encode_crypto([StateData#state.streamid,
235 StateData#state.password]) of
236 Digest ->
237 30 try_register_routes(StateData);
238 _ ->
239 1 send_text(StateData, ?INVALID_HANDSHAKE_ERR),
240 1 {stop, normal, StateData}
241 end;
242 _ ->
243
:-(
{next_state, wait_for_handshake, StateData}
244 end;
245 wait_for_handshake({xmlstreamend, _Name}, StateData) ->
246
:-(
{stop, normal, StateData};
247 wait_for_handshake({xmlstreamerror, _}, StateData) ->
248
:-(
send_element(StateData, mongoose_xmpp_errors:xml_not_well_formed()),
249
:-(
send_text(StateData, ?STREAM_TRAILER),
250
:-(
{stop, normal, StateData};
251 wait_for_handshake(replaced, StateData) ->
252 %% We don't expect to receive replaced before handshake
253
:-(
do_disconnect_on_conflict(StateData);
254 wait_for_handshake(closed, StateData) ->
255
:-(
{stop, normal, StateData}.
256
257 -spec stream_established(ejabberd:xml_stream_item(), state()) -> fsm_return().
258 stream_established({xmlstreamelement, El}, StateData) ->
259 17 NewEl = jlib:remove_attr(<<"xmlns">>, El),
260 17 #xmlel{name = Name, attrs = Attrs} = NewEl,
261 17 From = xml:get_attr_s(<<"from">>, Attrs),
262 17 FromJID = case StateData#state.check_from of
263 %% If the admin does not want to check the from field
264 %% when accept packets from any address.
265 %% In this case, the component can send packet of
266 %% behalf of the server users.
267
:-(
false -> jid:from_binary(From);
268 %% The default is the standard behaviour in XEP-0114
269 _ ->
270 17 FromJID1 = jid:from_binary(From),
271 17 case FromJID1 of
272 #jid{lserver = Server} ->
273 17 case Server =:= StateData#state.host of
274 17 true -> FromJID1;
275
:-(
false -> error
276 end;
277
:-(
_ -> error
278 end
279 end,
280 17 To = xml:get_attr_s(<<"to">>, Attrs),
281 17 ToJID = case To of
282
:-(
<<>> -> error;
283 17 _ -> jid:from_binary(To)
284 end,
285 17 if ((Name == <<"iq">>) or
286 (Name == <<"message">>) or
287 (Name == <<"presence">>)) and
288 (ToJID /= error) and (FromJID /= error) ->
289 17 ejabberd_router:route(FromJID, ToJID, NewEl);
290 true ->
291
:-(
?LOG_INFO(#{what => comp_bad_request,
292 text => <<"Not valid Name or error in FromJID or ToJID">>,
293
:-(
stanza_name => Name, from_jid => From, to_jid => To}),
294
:-(
Err = jlib:make_error_reply(NewEl, mongoose_xmpp_errors:bad_request()),
295
:-(
send_element(StateData, Err),
296
:-(
error
297 end,
298 17 {next_state, stream_established, StateData};
299 stream_established({xmlstreamend, _Name}, StateData) ->
300 % TODO ??
301
:-(
{stop, normal, StateData};
302 stream_established({xmlstreamerror, _}, StateData) ->
303
:-(
send_element(StateData, mongoose_xmpp_errors:xml_not_well_formed()),
304
:-(
send_text(StateData, ?STREAM_TRAILER),
305
:-(
{stop, normal, StateData};
306 stream_established(replaced, StateData) ->
307 1 do_disconnect_on_conflict(StateData);
308 stream_established(closed, StateData) ->
309 % TODO ??
310 27 {stop, normal, StateData}.
311
312
313 %%----------------------------------------------------------------------
314 %% Func: StateName/3
315 %% Returns: {next_state, NextStateName, NextStateData} |
316 %% {next_state, NextStateName, NextStateData, Timeout} |
317 %% {reply, Reply, NextStateName, NextStateData} |
318 %% {reply, Reply, NextStateName, NextStateData, Timeout} |
319 %% {stop, Reason, NewStateData} |
320 %% {stop, Reason, Reply, NewStateData}
321 %%----------------------------------------------------------------------
322 %state_name(Event, From, StateData) ->
323 % Reply = ok,
324 % {reply, Reply, state_name, StateData}.
325
326 %%----------------------------------------------------------------------
327 %% Func: handle_event/3
328 %% Returns: {next_state, NextStateName, NextStateData} |
329 %% {next_state, NextStateName, NextStateData, Timeout} |
330 %% {stop, Reason, NewStateData}
331 %%----------------------------------------------------------------------
332 handle_event(_Event, StateName, StateData) ->
333
:-(
{next_state, StateName, StateData}.
334
335 %%----------------------------------------------------------------------
336 %% Func: handle_sync_event/4
337 %% Returns: {next_state, NextStateName, NextStateData} |
338 %% {next_state, NextStateName, NextStateData, Timeout} |
339 %% {reply, Reply, NextStateName, NextStateData} |
340 %% {reply, Reply, NextStateName, NextStateData, Timeout} |
341 %% {stop, Reason, NewStateData} |
342 %% {stop, Reason, Reply, NewStateData}
343 %%----------------------------------------------------------------------
344 handle_sync_event(_Event, _From, StateName, StateData) ->
345
:-(
Reply = ok,
346
:-(
{reply, Reply, StateName, StateData}.
347
348
349 code_change(_OldVsn, StateName, StateData, _Extra) ->
350
:-(
{ok, StateName, StateData}.
351
352 %%----------------------------------------------------------------------
353 %% Func: handle_info/3
354 %% Returns: {next_state, NextStateName, NextStateData} |
355 %% {next_state, NextStateName, NextStateData, Timeout} |
356 %% {stop, Reason, NewStateData}
357 %%----------------------------------------------------------------------
358 handle_info({send_element, El}, StateName, StateData) ->
359 % is it ever called?
360
:-(
?LOG_ERROR(#{what => comp_deprecated_send_element,
361
:-(
component => component_host(StateData), exml_packet => El}),
362
:-(
send_element(StateData, El),
363
:-(
{next_state, StateName, StateData};
364 handle_info({route, Acc}, StateName, StateData) ->
365 18 {From, To, Packet} = mongoose_acc:packet(Acc),
366
367 18 ?LOG_DEBUG(#{what => comp_route,
368 text => <<"Route packet to an external component">>,
369 18 component => component_host(StateData), acc => Acc}),
370 18 case acl:match_rule(global, StateData#state.access, From) of
371 allow ->
372 18 mongoose_hooks:packet_to_component(Acc, From, To),
373 18 Attrs2 = jlib:replace_from_to_attrs(jid:to_binary(From),
374 jid:to_binary(To),
375 Packet#xmlel.attrs),
376 18 send_element(StateData, Packet#xmlel{ attrs = Attrs2 });
377 deny ->
378
:-(
ejabberd_router:route_error_reply(To, From, Acc, mongoose_xmpp_errors:not_allowed())
379 end,
380 18 {next_state, StateName, StateData};
381 handle_info({'DOWN', Monitor, _Type, _Object, _Info}, _StateName, StateData)
382 when Monitor == StateData#state.socket_monitor ->
383
:-(
{stop, normal, StateData};
384 handle_info(Info, StateName, StateData) ->
385
:-(
?UNEXPECTED_INFO(Info),
386
:-(
{next_state, StateName, StateData}.
387
388
389 %%----------------------------------------------------------------------
390 %% Func: terminate/3
391 %% Purpose: Shutdown the fsm
392 %% Returns: any
393 %%----------------------------------------------------------------------
394 terminate(Reason, StateName, StateData) ->
395 31 ?LOG_INFO(#{what => comp_stopped,
396 31 component => component_host(StateData), reason => Reason}),
397 31 case StateName of
398 stream_established ->
399 28 unregister_routes(StateData);
400 _ ->
401 3 ok
402 end,
403 31 mongoose_transport:close(StateData#state.socket),
404 31 ok.
405
406 %%----------------------------------------------------------------------
407 %% Func: print_state/1
408 %% Purpose: Prepare the state to be printed on error log
409 %% Returns: State to print
410 %%----------------------------------------------------------------------
411 print_state(State) ->
412
:-(
State.
413
414 %%%----------------------------------------------------------------------
415 %%% Internal functions
416 %%%----------------------------------------------------------------------
417
418 -spec send_text(state(), binary()) -> ok.
419 send_text(StateData, Text) ->
420 32 ?LOG_DEBUG(#{what => comp_send_text,
421 component => component_host(StateData),
422 32 send_text => Text}),
423 32 mongoose_transport:send_text(StateData#state.socket, Text).
424
425 -spec send_element(state(), exml:element()) -> ok.
426 send_element(StateData, El) ->
427 49 mongoose_transport:send_element(StateData#state.socket, El).
428
429
430 -spec new_id() -> string().
431 new_id() ->
432 31 binary_to_list(mongoose_bin:gen_from_crypto()).
433
434 -spec component_host(state()) -> binary() | string().
435
:-(
component_host(#state{ host = undefined }) -> "undefined";
436 3 component_host(#state{ host = Host }) -> Host.
437
438 -spec fsm_limit_opts(options()) -> [{max_queue, integer()}].
439 fsm_limit_opts(#{max_fsm_queue := N}) ->
440
:-(
[{max_queue, N}];
441 fsm_limit_opts(#{}) ->
442 31 case mongoose_config:lookup_opt(max_fsm_queue) of
443 {ok, N} ->
444 31 [{max_queue, N}];
445 {error, not_found} ->
446
:-(
[]
447 end.
448
449 try_register_routes(StateData) ->
450 30 try_register_routes(StateData, 3).
451
452 try_register_routes(StateData, Retries) ->
453 31 case register_routes(StateData) of
454 {ok, Components} ->
455 28 send_element(StateData, #xmlel{name = <<"handshake">>}),
456 28 {next_state, stream_established, StateData#state{components = Components}};
457 {error, Reason} ->
458 3 RoutesInfo = lookup_routes(StateData),
459 3 ConflictBehaviour = StateData#state.conflict_behaviour,
460 3 ?LOG_ERROR(#{what => comp_registration_conflict,
461 text => <<"Another connection from a component with the same name">>,
462 component => component_host(StateData),
463 reason => Reason, retries => Retries, routes_info => RoutesInfo,
464
:-(
conflict_behaviour => ConflictBehaviour}),
465 3 handle_registration_conflict(ConflictBehaviour, RoutesInfo, StateData, Retries)
466 end.
467
468 routes_info_to_pids(RoutesInfo) ->
469 1 {_Hosts, ExtComponentsPerHost} = lists:unzip(RoutesInfo),
470 %% Flatten the list of lists
471 1 ExtComponents = lists:append(ExtComponentsPerHost),
472 %% Ignore handlers from other modules
473 1 [maps:get(pid, mongoose_packet_handler:extra(H))
474 1 || #external_component{handler = H} <- ExtComponents,
475 1 mongoose_packet_handler:module(H) =:= ?MODULE].
476
477 handle_registration_conflict(kick_old, RoutesInfo, StateData, Retries) when Retries > 0 ->
478 %% see lookup_routes
479 1 Pids = lists:usort(routes_info_to_pids(RoutesInfo)),
480 1 Results = lists:map(fun stop_process/1, Pids),
481 1 AllOk = lists:all(fun(Result) -> Result =:= ok end, Results),
482 1 case AllOk of
483 true ->
484 %% Do recursive call
485 1 try_register_routes(StateData, Retries - 1);
486 false ->
487
:-(
?LOG_ERROR(#{what => comp_registration_kick_failed,
488 text => <<"Failed to stop old component connection. Disconnecting next.">>,
489 component => component_host(StateData),
490
:-(
component_pids => Pids, results => Results}),
491
:-(
do_disconnect_on_conflict(StateData)
492 end;
493 handle_registration_conflict(_Behaviour, _RoutesInfo, StateData, _Retries) ->
494 2 do_disconnect_on_conflict(StateData).
495
496 do_disconnect_on_conflict(StateData) ->
497 3 send_element(StateData, mongoose_xmpp_errors:stream_conflict()),
498 3 {stop, normal, StateData}.
499
500 lookup_routes(StateData) ->
501 3 Routes = get_routes(StateData),
502 %% Lookup for all pids for the route (both local and global)
503 3 [{Route, mongoose_component:lookup_component(Route)} || Route <- Routes].
504
505 -spec register_routes(state()) -> any().
506 register_routes(StateData = #state{hidden_components = AreHidden}) ->
507 31 Routes = get_routes(StateData),
508 31 Handler = mongoose_packet_handler:new(?MODULE, #{pid => self()}),
509 31 mongoose_component:register_components(Routes, node(), Handler, AreHidden).
510
511 -spec unregister_routes(state()) -> any().
512 unregister_routes(#state{components = Components}) ->
513 28 mongoose_component:unregister_components(Components).
514
515 get_routes(#state{host=Subdomain, is_subdomain=true}) ->
516 1 Hosts = mongoose_config:get_opt(hosts),
517 1 component_routes(Subdomain, Hosts);
518 get_routes(#state{host=Host}) ->
519 33 [Host].
520
521 -spec component_routes(binary(), [binary()]) -> [binary()].
522 component_routes(Subdomain, Hosts) ->
523 1 [<<Subdomain/binary, ".", Host/binary>> || Host <- Hosts].
524
525 stop_process(Pid) ->
526 1 p1_fsm:send_event(Pid, replaced),
527 1 MonRef = erlang:monitor(process, Pid),
528 1 receive
529 {'DOWN', MonRef, process, Pid, _Reason} ->
530 1 ok
531 after 5000 ->
532
:-(
erlang:demonitor(MonRef, [flush]),
533
:-(
{error, timeout}
534 end.
Line Hits Source