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