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 |
:-( |
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 |
:-( |
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 |
:-( |
xml_stream. |
147 |
|
|
148 |
|
-spec start_listener(options()) -> ok. |
149 |
|
start_listener(Opts) -> |
150 |
135 |
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 |
:-( |
Pid ! {route, From, To, Acc}, |
160 |
:-( |
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 |
:-( |
?LOG_INFO(#{what => comp_started, |
176 |
|
text => <<"External service connected">>, |
177 |
:-( |
socket => Socket}), |
178 |
:-( |
#{access := Access, shaper_rule := Shaper, password := Password, |
179 |
|
check_from := CheckFrom, hidden_components := HiddenComponents, |
180 |
|
conflict_behaviour := ConflictBehaviour} = Opts, |
181 |
:-( |
SockMod:change_shaper(Socket, Shaper), |
182 |
:-( |
SocketMonitor = SockMod:monitor(Socket), |
183 |
:-( |
{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 |
:-( |
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 |
:-( |
To = xml:get_attr_s(<<"to">>, Attrs), |
211 |
:-( |
Header = io_lib:format(?STREAM_HEADER, [StateData#state.streamid, To]), |
212 |
:-( |
IsSubdomain = case xml:get_attr_s(<<"is_subdomain">>, Attrs) of |
213 |
:-( |
<<"true">> -> true; |
214 |
:-( |
_ -> false |
215 |
|
end, |
216 |
:-( |
send_text(StateData, list_to_binary(Header)), |
217 |
:-( |
StateData1 = StateData#state{host = To, |
218 |
|
is_subdomain = IsSubdomain}, |
219 |
:-( |
{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 |
:-( |
#xmlel{name = Name, children = Els} = El, |
239 |
:-( |
case {Name, xml:get_cdata(Els)} of |
240 |
|
{<<"handshake">>, Digest} -> |
241 |
:-( |
case sha:sha1_hex(StateData#state.streamid ++ |
242 |
|
StateData#state.password) of |
243 |
|
Digest -> |
244 |
:-( |
try_register_routes(StateData); |
245 |
|
_ -> |
246 |
:-( |
send_text(StateData, ?INVALID_HANDSHAKE_ERR), |
247 |
:-( |
{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 |
:-( |
NewEl = jlib:remove_attr(<<"xmlns">>, El), |
266 |
:-( |
#xmlel{name = Name, attrs = Attrs} = NewEl, |
267 |
:-( |
From = xml:get_attr_s(<<"from">>, Attrs), |
268 |
:-( |
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 |
:-( |
FromJID1 = jid:from_binary(From), |
277 |
:-( |
case FromJID1 of |
278 |
|
#jid{lserver = Server} -> |
279 |
:-( |
case Server =:= StateData#state.host of |
280 |
:-( |
true -> FromJID1; |
281 |
:-( |
false -> error |
282 |
|
end; |
283 |
:-( |
_ -> error |
284 |
|
end |
285 |
|
end, |
286 |
:-( |
To = xml:get_attr_s(<<"to">>, Attrs), |
287 |
:-( |
ToJID = case To of |
288 |
:-( |
<<>> -> error; |
289 |
:-( |
_ -> jid:from_binary(To) |
290 |
|
end, |
291 |
:-( |
if ((Name == <<"iq">>) or |
292 |
|
(Name == <<"message">>) or |
293 |
|
(Name == <<"presence">>)) and |
294 |
|
(ToJID /= error) and (FromJID /= error) -> |
295 |
:-( |
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 |
:-( |
{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 |
:-( |
do_disconnect_on_conflict(StateData); |
313 |
|
stream_established(closed, StateData) -> |
314 |
|
% TODO ?? |
315 |
:-( |
{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 |
:-( |
Packet = mongoose_acc:element(Acc), |
377 |
:-( |
?LOG_DEBUG(#{what => comp_route, |
378 |
|
text => <<"Route packet to an external component">>, |
379 |
:-( |
component => component_host(StateData), acc => Acc}), |
380 |
:-( |
case acl:match_rule(global, StateData#state.access, From) of |
381 |
|
allow -> |
382 |
:-( |
mongoose_hooks:packet_to_component(Acc, From, To), |
383 |
:-( |
Attrs2 = jlib:replace_from_to_attrs(jid:to_binary(From), |
384 |
|
jid:to_binary(To), |
385 |
|
Packet#xmlel.attrs), |
386 |
:-( |
Text = exml:to_binary(Packet#xmlel{ attrs = Attrs2 }), |
387 |
:-( |
send_text(StateData, Text); |
388 |
|
deny -> |
389 |
:-( |
ejabberd_router:route_error_reply(To, From, Acc, mongoose_xmpp_errors:not_allowed()) |
390 |
|
end, |
391 |
:-( |
{next_state, StateName, StateData}; |
392 |
|
handle_info({'DOWN', Monitor, _Type, _Object, _Info}, _StateName, StateData) |
393 |
|
when Monitor == StateData#state.socket_monitor -> |
394 |
:-( |
{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 |
:-( |
?LOG_INFO(#{what => comp_stopped, |
407 |
:-( |
component => component_host(StateData), reason => Reason}), |
408 |
:-( |
case StateName of |
409 |
|
stream_established -> |
410 |
:-( |
unregister_routes(StateData); |
411 |
|
_ -> |
412 |
:-( |
ok |
413 |
|
end, |
414 |
:-( |
(StateData#state.sockmod):close(StateData#state.socket), |
415 |
:-( |
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 |
:-( |
?LOG_DEBUG(#{what => comp_send_text, |
432 |
|
component => component_host(StateData), |
433 |
:-( |
send_text => Text}), |
434 |
:-( |
(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 |
:-( |
binary_to_list(mongoose_bin:gen_from_crypto()). |
445 |
|
|
446 |
|
-spec component_host(state()) -> binary() | string(). |
447 |
:-( |
component_host(#state{ host = undefined }) -> "undefined"; |
448 |
:-( |
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 |
:-( |
case mongoose_config:lookup_opt(max_fsm_queue) of |
455 |
|
{ok, N} -> |
456 |
:-( |
[{max_queue, N}]; |
457 |
|
{error, not_found} -> |
458 |
:-( |
[] |
459 |
|
end. |
460 |
|
|
461 |
|
try_register_routes(StateData) -> |
462 |
:-( |
try_register_routes(StateData, 3). |
463 |
|
|
464 |
|
try_register_routes(StateData, Retries) -> |
465 |
:-( |
case register_routes(StateData) of |
466 |
|
ok -> |
467 |
:-( |
send_text(StateData, <<"<handshake/>">>), |
468 |
:-( |
{next_state, stream_established, StateData}; |
469 |
|
{error, Reason} -> |
470 |
:-( |
RoutesInfo = lookup_routes(StateData), |
471 |
:-( |
ConflictBehaviour = StateData#state.conflict_behaviour, |
472 |
:-( |
?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 |
:-( |
handle_registration_conflict(ConflictBehaviour, RoutesInfo, StateData, Retries) |
478 |
|
end. |
479 |
|
|
480 |
|
routes_info_to_pids(RoutesInfo) -> |
481 |
:-( |
{_Hosts, ExtComponentsPerHost} = lists:unzip(RoutesInfo), |
482 |
|
%% Flatten the list of lists |
483 |
:-( |
ExtComponents = lists:append(ExtComponentsPerHost), |
484 |
|
%% Ignore handlers from other modules |
485 |
:-( |
[maps:get(pid, mongoose_packet_handler:extra(H)) |
486 |
:-( |
|| #external_component{handler = H} <- ExtComponents, |
487 |
:-( |
mongoose_packet_handler:module(H) =:= ?MODULE]. |
488 |
|
|
489 |
|
handle_registration_conflict(kick_old, RoutesInfo, StateData, Retries) when Retries > 0 -> |
490 |
:-( |
Pids = routes_info_to_pids(RoutesInfo), |
491 |
:-( |
Results = lists:map(fun stop_process/1, Pids), |
492 |
:-( |
AllOk = lists:all(fun(Result) -> Result =:= ok end, Results), |
493 |
:-( |
case AllOk of |
494 |
|
true -> |
495 |
|
%% Do recursive call |
496 |
:-( |
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 |
:-( |
do_disconnect_on_conflict(StateData). |
506 |
|
|
507 |
|
do_disconnect_on_conflict(StateData) -> |
508 |
:-( |
send_text(StateData, exml:to_binary(mongoose_xmpp_errors:stream_conflict())), |
509 |
:-( |
{stop, normal, StateData}. |
510 |
|
|
511 |
|
lookup_routes(StateData) -> |
512 |
:-( |
Routes = get_routes(StateData), |
513 |
:-( |
[{Route, ejabberd_router:lookup_component(Route)} || Route <- Routes]. |
514 |
|
|
515 |
|
-spec register_routes(state()) -> any(). |
516 |
|
register_routes(StateData = #state{hidden_components = AreHidden}) -> |
517 |
:-( |
Routes = get_routes(StateData), |
518 |
:-( |
Handler = mongoose_packet_handler:new(?MODULE, #{pid => self()}), |
519 |
:-( |
ejabberd_router:register_components(Routes, node(), Handler, AreHidden). |
520 |
|
|
521 |
|
-spec unregister_routes(state()) -> any(). |
522 |
|
unregister_routes(StateData) -> |
523 |
:-( |
Routes = get_routes(StateData), |
524 |
:-( |
ejabberd_router:unregister_components(Routes). |
525 |
|
|
526 |
|
get_routes(#state{host=Subdomain, is_subdomain=true}) -> |
527 |
:-( |
Hosts = mongoose_config:get_opt(hosts), |
528 |
:-( |
component_routes(Subdomain, Hosts); |
529 |
|
get_routes(#state{host=Host}) -> |
530 |
:-( |
[Host]. |
531 |
|
|
532 |
|
-spec component_routes(binary(), [binary()]) -> [binary()]. |
533 |
|
component_routes(Subdomain, Hosts) -> |
534 |
:-( |
[<<Subdomain/binary, ".", Host/binary>> || Host <- Hosts]. |
535 |
|
|
536 |
|
stop_process(Pid) -> |
537 |
:-( |
p1_fsm:send_event(Pid, replaced), |
538 |
:-( |
MonRef = erlang:monitor(process, Pid), |
539 |
:-( |
receive |
540 |
|
{'DOWN', MonRef, process, Pid, _Reason} -> |
541 |
:-( |
ok |
542 |
|
after 5000 -> |
543 |
:-( |
erlang:demonitor(MonRef, [flush]), |
544 |
:-( |
{error, timeout} |
545 |
|
end. |