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 |
88 |
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. |