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