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 |
:-( |
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 |
:-( |
p1_fsm:start_link(ejabberd_service, [SockData, Opts], |
128 |
|
fsm_limit_opts(Opts) ++ ?FSMOPTS). |
129 |
|
|
130 |
|
|
131 |
|
socket_type() -> |
132 |
128 |
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 |
:-( |
Pid ! {route, From, To, Acc}, |
142 |
:-( |
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 |
:-( |
?LOG_INFO(#{what => comp_started, |
158 |
|
text => <<"External service connected">>, |
159 |
:-( |
socket => Socket}), |
160 |
:-( |
Access = case lists:keysearch(access, 1, Opts) of |
161 |
:-( |
{value, {_, A}} -> A; |
162 |
:-( |
_ -> all |
163 |
|
end, |
164 |
:-( |
{password, Password} = lists:keyfind(password, 1, Opts), |
165 |
:-( |
Shaper = case lists:keysearch(shaper_rule, 1, Opts) of |
166 |
:-( |
{value, {_, S}} -> S; |
167 |
:-( |
_ -> none |
168 |
|
end, |
169 |
:-( |
CheckFrom = case lists:keysearch(service_check_from, 1, Opts) of |
170 |
:-( |
{value, {_, CF}} -> CF; |
171 |
:-( |
_ -> true |
172 |
|
end, |
173 |
:-( |
SockMod:change_shaper(Socket, Shaper), |
174 |
:-( |
SocketMonitor = SockMod:monitor(Socket), |
175 |
:-( |
HiddenComponents = proplists:get_value(hidden_components, Opts, false), |
176 |
:-( |
ConflictBehaviour = proplists:get_value(conflict_behaviour, Opts, disconnect), |
177 |
:-( |
{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 |
:-( |
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 |
:-( |
To = xml:get_attr_s(<<"to">>, Attrs), |
205 |
:-( |
Header = io_lib:format(?STREAM_HEADER, [StateData#state.streamid, To]), |
206 |
:-( |
IsSubdomain = case xml:get_attr_s(<<"is_subdomain">>, Attrs) of |
207 |
:-( |
<<"true">> -> true; |
208 |
:-( |
_ -> false |
209 |
|
end, |
210 |
:-( |
send_text(StateData, list_to_binary(Header)), |
211 |
:-( |
StateData1 = StateData#state{host = To, |
212 |
|
is_subdomain = IsSubdomain}, |
213 |
:-( |
{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 |
:-( |
#xmlel{name = Name, children = Els} = El, |
233 |
:-( |
case {Name, xml:get_cdata(Els)} of |
234 |
|
{<<"handshake">>, Digest} -> |
235 |
:-( |
case sha:sha1_hex(StateData#state.streamid ++ |
236 |
|
StateData#state.password) of |
237 |
|
Digest -> |
238 |
:-( |
try_register_routes(StateData); |
239 |
|
_ -> |
240 |
:-( |
send_text(StateData, ?INVALID_HANDSHAKE_ERR), |
241 |
:-( |
{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 |
:-( |
NewEl = jlib:remove_attr(<<"xmlns">>, El), |
260 |
:-( |
#xmlel{name = Name, attrs = Attrs} = NewEl, |
261 |
:-( |
From = xml:get_attr_s(<<"from">>, Attrs), |
262 |
:-( |
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 |
:-( |
FromJID1 = jid:from_binary(From), |
271 |
:-( |
case FromJID1 of |
272 |
|
#jid{lserver = Server} -> |
273 |
:-( |
case Server =:= StateData#state.host of |
274 |
:-( |
true -> FromJID1; |
275 |
:-( |
false -> error |
276 |
|
end; |
277 |
:-( |
_ -> error |
278 |
|
end |
279 |
|
end, |
280 |
:-( |
To = xml:get_attr_s(<<"to">>, Attrs), |
281 |
:-( |
ToJID = case To of |
282 |
:-( |
<<>> -> error; |
283 |
:-( |
_ -> jid:from_binary(To) |
284 |
|
end, |
285 |
:-( |
if ((Name == <<"iq">>) or |
286 |
|
(Name == <<"message">>) or |
287 |
|
(Name == <<"presence">>)) and |
288 |
|
(ToJID /= error) and (FromJID /= error) -> |
289 |
:-( |
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 |
:-( |
{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 |
:-( |
do_disconnect_on_conflict(StateData); |
307 |
|
stream_established(closed, StateData) -> |
308 |
|
% TODO ?? |
309 |
:-( |
{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 |
:-( |
Packet = mongoose_acc:element(Acc), |
371 |
:-( |
?LOG_DEBUG(#{what => comp_route, |
372 |
|
text => <<"Route packet to an external component">>, |
373 |
:-( |
component => component_host(StateData), acc => Acc}), |
374 |
:-( |
case acl:match_rule(global, StateData#state.access, From) of |
375 |
|
allow -> |
376 |
:-( |
mongoose_hooks:packet_to_component(Acc, From, To), |
377 |
:-( |
Attrs2 = jlib:replace_from_to_attrs(jid:to_binary(From), |
378 |
|
jid:to_binary(To), |
379 |
|
Packet#xmlel.attrs), |
380 |
:-( |
Text = exml:to_binary(Packet#xmlel{ attrs = Attrs2 }), |
381 |
:-( |
send_text(StateData, Text); |
382 |
|
deny -> |
383 |
:-( |
ejabberd_router:route_error_reply(To, From, Acc, mongoose_xmpp_errors:not_allowed()) |
384 |
|
end, |
385 |
:-( |
{next_state, StateName, StateData}; |
386 |
|
handle_info({'DOWN', Monitor, _Type, _Object, _Info}, _StateName, StateData) |
387 |
|
when Monitor == StateData#state.socket_monitor -> |
388 |
:-( |
{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 |
:-( |
?LOG_INFO(#{what => comp_stopped, |
401 |
:-( |
component => component_host(StateData), reason => Reason}), |
402 |
:-( |
case StateName of |
403 |
|
stream_established -> |
404 |
:-( |
unregister_routes(StateData); |
405 |
|
_ -> |
406 |
:-( |
ok |
407 |
|
end, |
408 |
:-( |
(StateData#state.sockmod):close(StateData#state.socket), |
409 |
:-( |
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 |
:-( |
?LOG_DEBUG(#{what => comp_send_text, |
426 |
|
component => component_host(StateData), |
427 |
:-( |
send_text => Text}), |
428 |
:-( |
(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 |
:-( |
binary_to_list(mongoose_bin:gen_from_crypto()). |
439 |
|
|
440 |
|
-spec component_host(state()) -> binary() | string(). |
441 |
:-( |
component_host(#state{ host = undefined }) -> "undefined"; |
442 |
:-( |
component_host(#state{ host = Host }) -> Host. |
443 |
|
|
444 |
|
-spec fsm_limit_opts(maybe_improper_list()) -> [{'max_queue', integer()}]. |
445 |
|
fsm_limit_opts(Opts) -> |
446 |
:-( |
case lists:keysearch(max_fsm_queue, 1, Opts) of |
447 |
|
{value, {_, N}} when is_integer(N) -> |
448 |
:-( |
[{max_queue, N}]; |
449 |
|
_ -> |
450 |
:-( |
case mongoose_config:lookup_opt(max_fsm_queue) of |
451 |
|
{ok, N} -> |
452 |
:-( |
[{max_queue, N}]; |
453 |
|
{error, not_found} -> |
454 |
:-( |
[] |
455 |
|
end |
456 |
|
end. |
457 |
|
|
458 |
|
try_register_routes(StateData) -> |
459 |
:-( |
try_register_routes(StateData, 3). |
460 |
|
|
461 |
|
try_register_routes(StateData, Retries) -> |
462 |
:-( |
case register_routes(StateData) of |
463 |
|
ok -> |
464 |
:-( |
send_text(StateData, <<"<handshake/>">>), |
465 |
:-( |
{next_state, stream_established, StateData}; |
466 |
|
{error, Reason} -> |
467 |
:-( |
RoutesInfo = lookup_routes(StateData), |
468 |
:-( |
ConflictBehaviour = StateData#state.conflict_behaviour, |
469 |
:-( |
?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 |
:-( |
handle_registration_conflict(ConflictBehaviour, RoutesInfo, StateData, Retries) |
475 |
|
end. |
476 |
|
|
477 |
|
routes_info_to_pids(RoutesInfo) -> |
478 |
:-( |
{_Hosts, ExtComponentsPerHost} = lists:unzip(RoutesInfo), |
479 |
|
%% Flatten the list of lists |
480 |
:-( |
ExtComponents = lists:append(ExtComponentsPerHost), |
481 |
|
%% Ignore handlers from other modules |
482 |
:-( |
[maps:get(pid, mongoose_packet_handler:extra(H)) |
483 |
:-( |
|| #external_component{handler = H} <- ExtComponents, |
484 |
:-( |
mongoose_packet_handler:module(H) =:= ?MODULE]. |
485 |
|
|
486 |
|
handle_registration_conflict(kick_old, RoutesInfo, StateData, Retries) when Retries > 0 -> |
487 |
:-( |
Pids = routes_info_to_pids(RoutesInfo), |
488 |
:-( |
Results = lists:map(fun stop_process/1, Pids), |
489 |
:-( |
AllOk = lists:all(fun(Result) -> Result =:= ok end, Results), |
490 |
:-( |
case AllOk of |
491 |
|
true -> |
492 |
|
%% Do recursive call |
493 |
:-( |
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 |
:-( |
do_disconnect_on_conflict(StateData). |
503 |
|
|
504 |
|
do_disconnect_on_conflict(StateData) -> |
505 |
:-( |
send_text(StateData, exml:to_binary(mongoose_xmpp_errors:stream_conflict())), |
506 |
:-( |
{stop, normal, StateData}. |
507 |
|
|
508 |
|
lookup_routes(StateData) -> |
509 |
:-( |
Routes = get_routes(StateData), |
510 |
:-( |
[{Route, ejabberd_router:lookup_component(Route)} || Route <- Routes]. |
511 |
|
|
512 |
|
-spec register_routes(state()) -> any(). |
513 |
|
register_routes(StateData = #state{hidden_components = AreHidden}) -> |
514 |
:-( |
Routes = get_routes(StateData), |
515 |
:-( |
Handler = mongoose_packet_handler:new(?MODULE, #{pid => self()}), |
516 |
:-( |
ejabberd_router:register_components(Routes, node(), Handler, AreHidden). |
517 |
|
|
518 |
|
-spec unregister_routes(state()) -> any(). |
519 |
|
unregister_routes(StateData) -> |
520 |
:-( |
Routes = get_routes(StateData), |
521 |
:-( |
ejabberd_router:unregister_components(Routes). |
522 |
|
|
523 |
|
get_routes(#state{host=Subdomain, is_subdomain=true}) -> |
524 |
:-( |
Hosts = mongoose_config:get_opt(hosts), |
525 |
:-( |
component_routes(Subdomain, Hosts); |
526 |
|
get_routes(#state{host=Host}) -> |
527 |
:-( |
[Host]. |
528 |
|
|
529 |
|
-spec component_routes(binary(), [binary()]) -> [binary()]. |
530 |
|
component_routes(Subdomain, Hosts) -> |
531 |
:-( |
[<<Subdomain/binary, ".", Host/binary>> || Host <- Hosts]. |
532 |
|
|
533 |
|
stop_process(Pid) -> |
534 |
:-( |
p1_fsm:send_event(Pid, replaced), |
535 |
:-( |
MonRef = erlang:monitor(process, Pid), |
536 |
:-( |
receive |
537 |
|
{'DOWN', MonRef, process, Pid, _Reason} -> |
538 |
:-( |
ok |
539 |
|
after 5000 -> |
540 |
:-( |
erlang:demonitor(MonRef, [flush]), |
541 |
:-( |
{error, timeout} |
542 |
|
end. |