1 |
|
%%%---------------------------------------------------------------------- |
2 |
|
%%% File : ejabberd_s2s_in.erl |
3 |
|
%%% Author : Alexey Shchepin <alexey@process-one.net> |
4 |
|
%%% Purpose : Serve incoming s2s connection |
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_s2s_in). |
27 |
|
-author('alexey@process-one.net'). |
28 |
|
-behaviour(gen_fsm_compat). |
29 |
|
|
30 |
|
%% External exports |
31 |
|
-export([start/2, |
32 |
|
start_link/2, |
33 |
|
match_domain/2, |
34 |
|
socket_type/0]). |
35 |
|
|
36 |
|
%% gen_fsm callbacks |
37 |
|
-export([init/1, |
38 |
|
wait_for_stream/2, |
39 |
|
wait_for_feature_request/2, |
40 |
|
stream_established/2, |
41 |
|
handle_event/3, |
42 |
|
handle_sync_event/4, |
43 |
|
code_change/4, |
44 |
|
handle_info/3, |
45 |
|
terminate/3]). |
46 |
|
|
47 |
|
-ignore_xref([match_domain/2, socket_type/0, start/2, start_link/2, |
48 |
|
stream_established/2, wait_for_feature_request/2, wait_for_stream/2]). |
49 |
|
|
50 |
|
-include("mongoose.hrl"). |
51 |
|
-include("jlib.hrl"). |
52 |
|
|
53 |
|
-record(state, {socket, |
54 |
|
sockmod :: ejabberd:sockmod(), |
55 |
|
streamid :: binary(), |
56 |
|
shaper, |
57 |
|
tls = false :: boolean(), |
58 |
|
tls_enabled = false :: boolean(), |
59 |
|
tls_required = false :: boolean(), |
60 |
|
tls_cert_verify = false :: boolean(), |
61 |
|
tls_options = [] :: [{_, _}], |
62 |
|
server :: jid:server() | undefined, |
63 |
|
host_type :: mongooseim:host_type() | undefined, |
64 |
|
authenticated = false :: boolean(), |
65 |
|
auth_domain :: binary() | undefined, |
66 |
|
connections = dict:new(), |
67 |
|
timer :: reference() |
68 |
|
}). |
69 |
|
-type state() :: #state{}. |
70 |
|
|
71 |
|
-type statename() :: 'stream_established' | 'wait_for_feature_request'. |
72 |
|
%% FSM handler return value |
73 |
|
-type fsm_return() :: {'stop', Reason :: 'normal', state()} |
74 |
|
| {'next_state', statename(), state()} |
75 |
|
| {'next_state', statename(), state(), Timeout :: integer()}. |
76 |
|
%-define(DBGFSM, true). |
77 |
|
|
78 |
|
-ifdef(DBGFSM). |
79 |
|
-define(FSMOPTS, [{debug, [trace]}]). |
80 |
|
-else. |
81 |
|
-define(FSMOPTS, []). |
82 |
|
-endif. |
83 |
|
|
84 |
|
%% Module start with or without supervisor: |
85 |
|
-ifdef(NO_TRANSIENT_SUPERVISORS). |
86 |
|
-define(SUPERVISOR_START, gen_fsm_compat:start(ejabberd_s2s_in, [SockData, Opts], |
87 |
|
?FSMOPTS)). |
88 |
|
-else. |
89 |
|
-define(SUPERVISOR_START, supervisor:start_child(ejabberd_s2s_in_sup, |
90 |
|
[SockData, Opts])). |
91 |
|
-endif. |
92 |
|
|
93 |
|
-define(STREAM_HEADER(Version), |
94 |
|
(<<"<?xml version='1.0'?>" |
95 |
|
"<stream:stream " |
96 |
|
"xmlns:stream='http://etherx.jabber.org/streams' " |
97 |
|
"xmlns='jabber:server' " |
98 |
|
"xmlns:db='jabber:server:dialback' " |
99 |
|
"id='", (StateData#state.streamid)/binary, "'", Version/binary, ">">>) |
100 |
|
). |
101 |
|
|
102 |
|
%%%---------------------------------------------------------------------- |
103 |
|
%%% API |
104 |
|
%%%---------------------------------------------------------------------- |
105 |
|
-spec start(_, _) -> {'error', _} |
106 |
|
| {'ok', 'undefined' | pid()} |
107 |
|
| {'ok', 'undefined' | pid(), _}. |
108 |
|
start(SockData, Opts) -> |
109 |
54 |
?SUPERVISOR_START. |
110 |
|
|
111 |
|
|
112 |
|
-spec start_link(_, _) -> 'ignore' | {'error', _} | {'ok', pid()}. |
113 |
|
start_link(SockData, Opts) -> |
114 |
54 |
gen_fsm_compat:start_link(ejabberd_s2s_in, [SockData, Opts], ?FSMOPTS). |
115 |
|
|
116 |
|
|
117 |
|
socket_type() -> |
118 |
170 |
xml_stream. |
119 |
|
|
120 |
|
%%%---------------------------------------------------------------------- |
121 |
|
%%% Callback functions from gen_fsm |
122 |
|
%%%---------------------------------------------------------------------- |
123 |
|
|
124 |
|
%%---------------------------------------------------------------------- |
125 |
|
%% Func: init/1 |
126 |
|
%% Returns: {ok, StateName, StateData} | |
127 |
|
%% {ok, StateName, StateData, Timeout} | |
128 |
|
%% ignore | |
129 |
|
%% {stop, StopReason} |
130 |
|
%%---------------------------------------------------------------------- |
131 |
|
-spec init(_) -> {'ok', 'wait_for_stream', state()}. |
132 |
|
init([{SockMod, Socket}, Opts]) -> |
133 |
54 |
?LOG_DEBUG(#{what => s2n_in_started, |
134 |
|
text => <<"New incoming S2S connection">>, |
135 |
54 |
sockmod => SockMod, socket => Socket}), |
136 |
54 |
Shaper = case lists:keysearch(shaper, 1, Opts) of |
137 |
54 |
{value, {_, S}} -> S; |
138 |
:-( |
_ -> none |
139 |
|
end, |
140 |
54 |
TLSOpts = lists:filter(fun({protocol_options, _}) -> true; |
141 |
54 |
({dhfile, _}) -> true; |
142 |
12 |
({cafile, _}) -> true; |
143 |
:-( |
({ciphers, _}) -> true; |
144 |
486 |
(_) -> false |
145 |
|
end, Opts), |
146 |
54 |
Timer = erlang:start_timer(ejabberd_s2s:timeout(), self(), []), |
147 |
54 |
{ok, wait_for_stream, |
148 |
|
#state{socket = Socket, |
149 |
|
sockmod = SockMod, |
150 |
|
streamid = new_id(), |
151 |
|
shaper = Shaper, |
152 |
|
tls_enabled = false, |
153 |
|
tls_options = TLSOpts, |
154 |
|
timer = Timer}}. |
155 |
|
|
156 |
|
%%---------------------------------------------------------------------- |
157 |
|
%% Func: StateName/2 |
158 |
|
%% Returns: {next_state, NextStateName, NextStateData} | |
159 |
|
%% {next_state, NextStateName, NextStateData, Timeout} | |
160 |
|
%% {stop, Reason, NewStateData} |
161 |
|
%%---------------------------------------------------------------------- |
162 |
|
|
163 |
|
-spec wait_for_stream(ejabberd:xml_stream_item(), state()) -> fsm_return(). |
164 |
|
wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) -> |
165 |
79 |
case maps:from_list(Attrs) of |
166 |
|
AttrMap = #{<<"xmlns">> := <<"jabber:server">>, <<"to">> := Server} -> |
167 |
77 |
case StateData#state.server of |
168 |
|
undefined -> |
169 |
52 |
case mongoose_domain_api:get_host_type(Server) of |
170 |
|
{error, not_found} -> |
171 |
1 |
stream_start_error(StateData, mongoose_xmpp_errors:host_unknown()); |
172 |
|
{ok, HostType} -> |
173 |
51 |
UseTLS = mongoose_config:get_opt([{s2s, HostType}, use_starttls]), |
174 |
51 |
{StartTLS, TLSRequired, TLSCertVerify} = get_tls_params(UseTLS), |
175 |
51 |
start_stream(AttrMap, StateData#state{server = Server, |
176 |
|
host_type = HostType, |
177 |
|
tls = StartTLS, |
178 |
|
tls_required = TLSRequired, |
179 |
|
tls_cert_verify = TLSCertVerify}) |
180 |
|
end; |
181 |
|
Server -> |
182 |
24 |
start_stream(AttrMap, StateData); |
183 |
|
_Other -> |
184 |
1 |
Msg = <<"The 'to' attribute differs from the originally provided one">>, |
185 |
1 |
stream_start_error(StateData, mongoose_xmpp_errors:host_unknown(?MYLANG, Msg)) |
186 |
|
end; |
187 |
|
#{<<"xmlns">> := <<"jabber:server">>} -> |
188 |
1 |
Msg = <<"The 'to' attribute is missing">>, |
189 |
1 |
stream_start_error(StateData, mongoose_xmpp_errors:improper_addressing(?MYLANG, Msg)); |
190 |
|
_ -> |
191 |
1 |
stream_start_error(StateData, mongoose_xmpp_errors:invalid_namespace()) |
192 |
|
end; |
193 |
|
wait_for_stream({xmlstreamerror, _}, StateData) -> |
194 |
:-( |
stream_start_error(StateData, mongoose_xmpp_errors:xml_not_well_formed()); |
195 |
|
wait_for_stream(timeout, StateData) -> |
196 |
:-( |
{stop, normal, StateData}; |
197 |
|
wait_for_stream(closed, StateData) -> |
198 |
:-( |
{stop, normal, StateData}. |
199 |
|
|
200 |
|
start_stream(#{<<"version">> := <<"1.0">>, <<"from">> := RemoteServer}, |
201 |
|
StateData = #state{tls = true, authenticated = false, server = Server, |
202 |
|
host_type = HostType}) -> |
203 |
43 |
SASL = case StateData#state.tls_enabled of |
204 |
|
true -> |
205 |
21 |
verify_cert_and_get_sasl(StateData#state.sockmod, |
206 |
|
StateData#state.socket, |
207 |
|
StateData#state.tls_cert_verify); |
208 |
|
_Else -> |
209 |
22 |
[] |
210 |
|
end, |
211 |
43 |
StartTLS = get_tls_xmlel(StateData), |
212 |
43 |
case SASL of |
213 |
|
{error_cert_verif, CertError} -> |
214 |
1 |
?LOG_INFO(#{what => s2s_connection_closing, |
215 |
|
text => <<"Closing s2s connection">>, |
216 |
|
server => StateData#state.server, |
217 |
|
remote_server => RemoteServer, |
218 |
|
reason => cert_error, |
219 |
1 |
cert_error => CertError}), |
220 |
1 |
Res = stream_start_error(StateData, |
221 |
|
mongoose_xmpp_errors:policy_violation(?MYLANG, CertError)), |
222 |
1 |
{atomic, Pid} = ejabberd_s2s:find_connection(jid:make(<<>>, Server, <<>>), |
223 |
|
jid:make(<<>>, RemoteServer, <<>>)), |
224 |
1 |
ejabberd_s2s_out:stop_connection(Pid), |
225 |
1 |
Res; |
226 |
|
_ -> |
227 |
42 |
send_text(StateData, ?STREAM_HEADER(<<" version='1.0'">>)), |
228 |
42 |
send_element(StateData, |
229 |
|
#xmlel{name = <<"stream:features">>, |
230 |
|
children = SASL ++ StartTLS ++ stream_features(HostType, Server)}), |
231 |
42 |
{next_state, wait_for_feature_request, StateData} |
232 |
|
end; |
233 |
|
start_stream(#{<<"version">> := <<"1.0">>}, |
234 |
|
StateData = #state{authenticated = true, host_type = HostType, server = Server}) -> |
235 |
3 |
send_text(StateData, ?STREAM_HEADER(<<" version='1.0'">>)), |
236 |
3 |
send_element(StateData, #xmlel{name = <<"stream:features">>, |
237 |
|
children = stream_features(HostType, Server)}), |
238 |
3 |
{next_state, stream_established, StateData}; |
239 |
|
start_stream(#{<<"xmlns:db">> := <<"jabber:server:dialback">>}, StateData) -> |
240 |
27 |
send_text(StateData, ?STREAM_HEADER(<<>>)), |
241 |
27 |
{next_state, stream_established, StateData}; |
242 |
|
start_stream(_, StateData) -> |
243 |
2 |
stream_start_error(StateData, mongoose_xmpp_errors:invalid_xml()). |
244 |
|
|
245 |
|
stream_start_error(StateData, Error) -> |
246 |
7 |
send_text(StateData, ?STREAM_HEADER(<<>>)), |
247 |
7 |
send_element(StateData, Error), |
248 |
7 |
send_text(StateData, ?STREAM_TRAILER), |
249 |
7 |
{stop, normal, StateData}. |
250 |
|
|
251 |
|
-spec wait_for_feature_request(ejabberd:xml_stream_item(), state() |
252 |
|
) -> fsm_return(). |
253 |
|
wait_for_feature_request({xmlstreamelement, El}, StateData) -> |
254 |
42 |
#xmlel{name = Name, attrs = Attrs, children = Els} = El, |
255 |
42 |
TLS = StateData#state.tls, |
256 |
42 |
TLSEnabled = StateData#state.tls_enabled, |
257 |
42 |
SockMod = (StateData#state.sockmod):get_sockmod(StateData#state.socket), |
258 |
42 |
case {xml:get_attr_s(<<"xmlns">>, Attrs), Name} of |
259 |
|
{?NS_TLS, <<"starttls">>} when TLS == true, |
260 |
|
TLSEnabled == false, |
261 |
|
SockMod == gen_tcp -> |
262 |
22 |
?LOG_DEBUG(#{what => s2s_starttls}), |
263 |
22 |
TLSOpts = tls_options(StateData), |
264 |
22 |
TLSSocket = (StateData#state.sockmod):starttls( |
265 |
|
StateData#state.socket, TLSOpts, |
266 |
|
exml:to_binary( |
267 |
|
#xmlel{name = <<"proceed">>, |
268 |
|
attrs = [{<<"xmlns">>, ?NS_TLS}]})), |
269 |
22 |
{next_state, wait_for_stream, |
270 |
|
StateData#state{socket = TLSSocket, |
271 |
|
streamid = new_id(), |
272 |
|
tls_enabled = true, |
273 |
|
tls_options = TLSOpts |
274 |
|
}}; |
275 |
|
{?NS_SASL, <<"auth">>} when TLSEnabled -> |
276 |
5 |
Mech = xml:get_attr_s(<<"mechanism">>, Attrs), |
277 |
5 |
case Mech of |
278 |
|
<<"EXTERNAL">> -> |
279 |
4 |
Auth = jlib:decode_base64(xml:get_cdata(Els)), |
280 |
4 |
AuthDomain = jid:nameprep(Auth), |
281 |
4 |
CertData = (StateData#state.sockmod):get_peer_certificate( |
282 |
|
StateData#state.socket), |
283 |
4 |
AuthRes = check_auth_domain(AuthDomain, CertData), |
284 |
4 |
handle_auth_res(AuthRes, AuthDomain, StateData); |
285 |
|
_ -> |
286 |
1 |
send_element(StateData, |
287 |
|
#xmlel{name = <<"failure">>, |
288 |
|
attrs = [{<<"xmlns">>, ?NS_SASL}], |
289 |
|
children = [#xmlel{name = <<"invalid-mechanism">>}]}), |
290 |
1 |
{stop, normal, StateData} |
291 |
|
end; |
292 |
|
_ -> |
293 |
15 |
stream_established({xmlstreamelement, El}, StateData) |
294 |
|
end; |
295 |
|
wait_for_feature_request({xmlstreamend, _Name}, StateData) -> |
296 |
:-( |
send_text(StateData, ?STREAM_TRAILER), |
297 |
:-( |
{stop, normal, StateData}; |
298 |
|
wait_for_feature_request({xmlstreamerror, _}, StateData) -> |
299 |
:-( |
send_text(StateData, <<(mongoose_xmpp_errors:xml_not_well_formed_bin())/binary, (?STREAM_TRAILER)/binary>>), |
300 |
:-( |
{stop, normal, StateData}; |
301 |
|
wait_for_feature_request(closed, StateData) -> |
302 |
:-( |
{stop, normal, StateData}. |
303 |
|
|
304 |
|
tls_options(#state{host_type = HostType, tls_options = TLSOptions}) -> |
305 |
22 |
case ejabberd_s2s:lookup_certfile(HostType) of |
306 |
|
{ok, CertFile} -> |
307 |
22 |
[{certfile, CertFile} | TLSOptions]; |
308 |
|
{error, not_found} -> |
309 |
:-( |
TLSOptions |
310 |
|
end. |
311 |
|
|
312 |
|
-spec stream_established(ejabberd:xml_stream_item(), state()) -> fsm_return(). |
313 |
|
stream_established({xmlstreamelement, El}, StateData) -> |
314 |
127 |
cancel_timer(StateData#state.timer), |
315 |
127 |
Timer = erlang:start_timer(ejabberd_s2s:timeout(), self(), []), |
316 |
127 |
case is_key_packet(El) of |
317 |
|
{key, To, From, Id, Key} -> |
318 |
28 |
?LOG_DEBUG(#{what => s2s_in_get_key, |
319 |
28 |
to => To, from => From, message_id => Id, key => Key}), |
320 |
28 |
LTo = jid:nameprep(To), |
321 |
28 |
LFrom = jid:nameprep(From), |
322 |
|
%% Checks if the from domain is allowed and if the to |
323 |
|
%% domain is handled by this server: |
324 |
28 |
case {ejabberd_s2s:allow_host(LTo, LFrom), |
325 |
28 |
mongoose_router:is_registered_route(LTo) |
326 |
:-( |
orelse ejabberd_router:is_component_dirty(LTo)} of |
327 |
|
{true, true} -> |
328 |
28 |
ejabberd_s2s_out:terminate_if_waiting_delay(LTo, LFrom), |
329 |
28 |
ejabberd_s2s_out:start(LTo, LFrom, |
330 |
|
{verify, self(), |
331 |
|
Key, StateData#state.streamid}), |
332 |
28 |
Conns = dict:store({LFrom, LTo}, wait_for_verification, |
333 |
|
StateData#state.connections), |
334 |
28 |
change_shaper(StateData, LTo, jid:make(<<>>, LFrom, <<>>)), |
335 |
28 |
{next_state, |
336 |
|
stream_established, |
337 |
|
StateData#state{connections = Conns, |
338 |
|
timer = Timer}}; |
339 |
|
{_, false} -> |
340 |
:-( |
send_text(StateData, exml:to_binary(mongoose_xmpp_errors:host_unknown())), |
341 |
:-( |
{stop, normal, StateData}; |
342 |
|
{false, _} -> |
343 |
:-( |
send_text(StateData, exml:to_binary(mongoose_xmpp_errors:invalid_from())), |
344 |
:-( |
{stop, normal, StateData} |
345 |
|
end; |
346 |
|
{verify, To, From, Id, Key} -> |
347 |
27 |
?LOG_DEBUG(#{what => s2s_in_verify_key, |
348 |
27 |
to => To, from => From, message_id => Id, key => Key}), |
349 |
27 |
LTo = jid:nameprep(To), |
350 |
27 |
LFrom = jid:nameprep(From), |
351 |
27 |
Type = case ejabberd_s2s:key(StateData#state.host_type, {LTo, LFrom}, Id) of |
352 |
27 |
Key -> <<"valid">>; |
353 |
:-( |
_ -> <<"invalid">> |
354 |
|
end, |
355 |
27 |
send_element(StateData, |
356 |
|
#xmlel{name = <<"db:verify">>, |
357 |
|
attrs = [{<<"from">>, To}, |
358 |
|
{<<"to">>, From}, |
359 |
|
{<<"id">>, Id}, |
360 |
|
{<<"type">>, Type}]}), |
361 |
27 |
{next_state, stream_established, StateData#state{timer = Timer}}; |
362 |
|
_ -> |
363 |
72 |
NewEl = jlib:remove_attr(<<"xmlns">>, El), |
364 |
72 |
#xmlel{attrs = Attrs} = NewEl, |
365 |
72 |
FromS = xml:get_attr_s(<<"from">>, Attrs), |
366 |
72 |
From = jid:from_binary(FromS), |
367 |
72 |
ToS = xml:get_attr_s(<<"to">>, Attrs), |
368 |
72 |
To = jid:from_binary(ToS), |
369 |
72 |
case {From, To} of |
370 |
:-( |
{error, _} -> ok; |
371 |
:-( |
{_, error} -> ok; |
372 |
72 |
_ -> route_incoming_stanza(From, To, NewEl, StateData) |
373 |
|
end, |
374 |
72 |
{next_state, stream_established, StateData#state{timer = Timer}} |
375 |
|
end; |
376 |
|
stream_established({valid, From, To}, StateData) -> |
377 |
27 |
send_element(StateData, |
378 |
|
#xmlel{name = <<"db:result">>, |
379 |
|
attrs = [{<<"from">>, To}, |
380 |
|
{<<"to">>, From}, |
381 |
|
{<<"type">>, <<"valid">>}]}), |
382 |
27 |
LFrom = jid:nameprep(From), |
383 |
27 |
LTo = jid:nameprep(To), |
384 |
27 |
NSD = StateData#state{ |
385 |
|
connections = dict:store({LFrom, LTo}, established, |
386 |
|
StateData#state.connections)}, |
387 |
27 |
{next_state, stream_established, NSD}; |
388 |
|
stream_established({invalid, From, To}, StateData) -> |
389 |
:-( |
send_element(StateData, |
390 |
|
#xmlel{name = <<"db:result">>, |
391 |
|
attrs = [{<<"from">>, To}, |
392 |
|
{<<"to">>, From}, |
393 |
|
{<<"type">>, <<"invalid">>}]}), |
394 |
:-( |
LFrom = jid:nameprep(From), |
395 |
:-( |
LTo = jid:nameprep(To), |
396 |
:-( |
NSD = StateData#state{ |
397 |
|
connections = dict:erase({LFrom, LTo}, |
398 |
|
StateData#state.connections)}, |
399 |
:-( |
{next_state, stream_established, NSD}; |
400 |
|
stream_established({xmlstreamend, _Name}, StateData) -> |
401 |
2 |
send_text(StateData, ?STREAM_TRAILER), |
402 |
2 |
{stop, normal, StateData}; |
403 |
|
stream_established({xmlstreamerror, _}, StateData) -> |
404 |
:-( |
send_text(StateData, |
405 |
|
<<(mongoose_xmpp_errors:xml_not_well_formed_bin())/binary, (?STREAM_TRAILER)/binary>>), |
406 |
:-( |
{stop, normal, StateData}; |
407 |
|
stream_established(timeout, StateData) -> |
408 |
:-( |
{stop, normal, StateData}; |
409 |
|
stream_established(closed, StateData) -> |
410 |
27 |
{stop, normal, StateData}. |
411 |
|
|
412 |
|
-spec route_incoming_stanza(From :: jid:jid(), |
413 |
|
To :: jid:jid(), |
414 |
|
El :: exml:element(), |
415 |
|
StateData :: state()) -> |
416 |
|
mongoose_acc:t() | error. |
417 |
|
route_incoming_stanza(From, To, El, StateData) -> |
418 |
72 |
LFromS = From#jid.lserver, |
419 |
72 |
LToS = To#jid.lserver, |
420 |
72 |
#xmlel{name = Name} = El, |
421 |
72 |
Acc = mongoose_acc:new(#{ location => ?LOCATION, |
422 |
|
lserver => LToS, |
423 |
|
element => El, |
424 |
|
from_jid => From, |
425 |
|
to_jid => To }), |
426 |
72 |
case is_s2s_authenticated(LFromS, LToS, StateData) of |
427 |
|
true -> |
428 |
1 |
route_stanza(Name, Acc); |
429 |
|
false -> |
430 |
71 |
case is_s2s_connected(LFromS, LToS, StateData) of |
431 |
|
true -> |
432 |
70 |
route_stanza(Name, Acc); |
433 |
|
false -> |
434 |
1 |
error |
435 |
|
end |
436 |
|
end. |
437 |
|
|
438 |
|
is_s2s_authenticated(_, _, #state{authenticated = false}) -> |
439 |
70 |
false; |
440 |
|
is_s2s_authenticated(LFrom, LTo, #state{auth_domain = LFrom}) -> |
441 |
1 |
mongoose_router:is_registered_route(LTo) |
442 |
:-( |
orelse ejabberd_router:is_component_dirty(LTo); |
443 |
|
is_s2s_authenticated(_, _, _) -> |
444 |
1 |
false. |
445 |
|
|
446 |
|
is_s2s_connected(LFrom, LTo, StateData) -> |
447 |
71 |
case dict:find({LFrom, LTo}, StateData#state.connections) of |
448 |
|
{ok, established} -> |
449 |
70 |
true; |
450 |
|
_ -> |
451 |
1 |
false |
452 |
|
end. |
453 |
|
|
454 |
|
-spec route_stanza(binary(), mongoose_acc:t()) -> mongoose_acc:t(). |
455 |
|
route_stanza(<<"iq">>, Acc) -> |
456 |
50 |
route_stanza(Acc); |
457 |
|
route_stanza(<<"message">>, Acc) -> |
458 |
21 |
route_stanza(Acc); |
459 |
|
route_stanza(<<"presence">>, Acc) -> |
460 |
:-( |
route_stanza(Acc); |
461 |
|
route_stanza(_, _Acc) -> |
462 |
:-( |
error. |
463 |
|
|
464 |
|
-spec route_stanza(mongoose_acc:t()) -> mongoose_acc:t(). |
465 |
|
route_stanza(Acc) -> |
466 |
71 |
From = mongoose_acc:from_jid(Acc), |
467 |
71 |
To = mongoose_acc:to_jid(Acc), |
468 |
71 |
Acc1 = mongoose_hooks:s2s_receive_packet(Acc), |
469 |
71 |
ejabberd_router:route(From, To, Acc1). |
470 |
|
|
471 |
|
%%---------------------------------------------------------------------- |
472 |
|
%% Func: StateName/3 |
473 |
|
%% Returns: {next_state, NextStateName, NextStateData} | |
474 |
|
%% {next_state, NextStateName, NextStateData, Timeout} | |
475 |
|
%% {reply, Reply, NextStateName, NextStateData} | |
476 |
|
%% {reply, Reply, NextStateName, NextStateData, Timeout} | |
477 |
|
%% {stop, Reason, NewStateData} | |
478 |
|
%% {stop, Reason, Reply, NewStateData} |
479 |
|
%%---------------------------------------------------------------------- |
480 |
|
%state_name(Event, From, StateData) -> |
481 |
|
% Reply = ok, |
482 |
|
% {reply, Reply, state_name, StateData}. |
483 |
|
|
484 |
|
%%---------------------------------------------------------------------- |
485 |
|
%% Func: handle_event/3 |
486 |
|
%% Returns: {next_state, NextStateName, NextStateData} | |
487 |
|
%% {next_state, NextStateName, NextStateData, Timeout} | |
488 |
|
%% {stop, Reason, NewStateData} |
489 |
|
%%---------------------------------------------------------------------- |
490 |
|
handle_event(_Event, StateName, StateData) -> |
491 |
68 |
{next_state, StateName, StateData}. |
492 |
|
|
493 |
|
%%---------------------------------------------------------------------- |
494 |
|
%% Func: handle_sync_event/4 |
495 |
|
%% Returns: The associated StateData for this connection |
496 |
|
%% {reply, Reply, NextStateName, NextStateData} |
497 |
|
%% Reply = {state_infos, [{InfoName::atom(), InfoValue::any()] |
498 |
|
%%---------------------------------------------------------------------- |
499 |
|
-spec handle_sync_event(any(), any(), statename(), state() |
500 |
|
) -> {'reply', 'ok' | {'state_infos', [any(), ...]}, atom(), state()}. |
501 |
|
handle_sync_event(get_state_infos, _From, StateName, StateData) -> |
502 |
1 |
SockMod = StateData#state.sockmod, |
503 |
1 |
{Addr, Port} = try SockMod:peername(StateData#state.socket) of |
504 |
1 |
{ok, {A, P}} -> {A, P}; |
505 |
:-( |
{error, _} -> {unknown, unknown} |
506 |
|
catch |
507 |
:-( |
_:_ -> {unknown, unknown} |
508 |
|
end, |
509 |
1 |
Domains = case StateData#state.authenticated of |
510 |
|
true -> |
511 |
:-( |
[StateData#state.auth_domain]; |
512 |
|
false -> |
513 |
1 |
Connections = StateData#state.connections, |
514 |
1 |
[D || {{D, _}, established} <- |
515 |
1 |
dict:to_list(Connections)] |
516 |
|
end, |
517 |
1 |
Infos = [ |
518 |
|
{direction, in}, |
519 |
|
{statename, StateName}, |
520 |
|
{addr, Addr}, |
521 |
|
{port, Port}, |
522 |
|
{streamid, StateData#state.streamid}, |
523 |
|
{tls, StateData#state.tls}, |
524 |
|
{tls_enabled, StateData#state.tls_enabled}, |
525 |
|
{tls_options, StateData#state.tls_options}, |
526 |
|
{authenticated, StateData#state.authenticated}, |
527 |
|
{shaper, StateData#state.shaper}, |
528 |
|
{sockmod, SockMod}, |
529 |
|
{domains, Domains} |
530 |
|
], |
531 |
1 |
Reply = {state_infos, Infos}, |
532 |
1 |
{reply, Reply, StateName, StateData}; |
533 |
|
|
534 |
|
%%---------------------------------------------------------------------- |
535 |
|
%% Func: handle_sync_event/4 |
536 |
|
%% Returns: {next_state, NextStateName, NextStateData} | |
537 |
|
%% {next_state, NextStateName, NextStateData, Timeout} | |
538 |
|
%% {reply, Reply, NextStateName, NextStateData} | |
539 |
|
%% {reply, Reply, NextStateName, NextStateData, Timeout} | |
540 |
|
%% {stop, Reason, NewStateData} | |
541 |
|
%% {stop, Reason, Reply, NewStateData} |
542 |
|
%%---------------------------------------------------------------------- |
543 |
|
handle_sync_event(_Event, _From, StateName, StateData) -> |
544 |
:-( |
Reply = ok, |
545 |
:-( |
{reply, Reply, StateName, StateData}. |
546 |
|
|
547 |
|
|
548 |
|
code_change(_OldVsn, StateName, StateData, _Extra) -> |
549 |
:-( |
{ok, StateName, StateData}. |
550 |
|
|
551 |
|
%%---------------------------------------------------------------------- |
552 |
|
%% Func: handle_info/3 |
553 |
|
%% Returns: {next_state, NextStateName, NextStateData} | |
554 |
|
%% {next_state, NextStateName, NextStateData, Timeout} | |
555 |
|
%% {stop, Reason, NewStateData} |
556 |
|
%%---------------------------------------------------------------------- |
557 |
|
-spec handle_info(_, _, _) -> {next_state, atom(), state()} | {stop, normal, state()}. |
558 |
|
handle_info({send_text, Text}, StateName, StateData) -> |
559 |
:-( |
?LOG_ERROR(#{what => s2s_in_send_text, |
560 |
|
text => <<"Deprecated send_text info in ejabberd_s2s_in">>, |
561 |
:-( |
send_text => Text}), |
562 |
:-( |
send_text(StateData, Text), |
563 |
:-( |
{next_state, StateName, StateData}; |
564 |
|
handle_info({timeout, Timer, _}, _StateName, |
565 |
|
#state{timer = Timer} = StateData) -> |
566 |
:-( |
{stop, normal, StateData}; |
567 |
|
handle_info(_, StateName, StateData) -> |
568 |
:-( |
{next_state, StateName, StateData}. |
569 |
|
|
570 |
|
|
571 |
|
%%---------------------------------------------------------------------- |
572 |
|
%% Func: terminate/3 |
573 |
|
%% Purpose: Shutdown the fsm |
574 |
|
%% Returns: any |
575 |
|
%%---------------------------------------------------------------------- |
576 |
|
-spec terminate(any(), statename(), state()) -> 'ok'. |
577 |
|
terminate(Reason, _StateName, StateData) -> |
578 |
38 |
?LOG_DEBUG(#{what => s2s_in_stopped, reason => Reason}), |
579 |
38 |
(StateData#state.sockmod):close(StateData#state.socket), |
580 |
38 |
ok. |
581 |
|
|
582 |
|
%%%---------------------------------------------------------------------- |
583 |
|
%%% Internal functions |
584 |
|
%%%---------------------------------------------------------------------- |
585 |
|
|
586 |
|
-spec send_text(state(), binary()) -> binary(). |
587 |
|
send_text(StateData, Text) -> |
588 |
200 |
(StateData#state.sockmod):send(StateData#state.socket, Text). |
589 |
|
|
590 |
|
|
591 |
|
-spec send_element(state(), exml:element()) -> binary(). |
592 |
|
send_element(StateData, El) -> |
593 |
111 |
send_text(StateData, exml:to_binary(El)). |
594 |
|
|
595 |
|
-spec stream_features(mongooseim:host_type(), binary()) -> [exml:element()]. |
596 |
|
stream_features(HostType, Domain) -> |
597 |
45 |
mongoose_hooks:s2s_stream_features(HostType, Domain). |
598 |
|
|
599 |
|
-spec change_shaper(state(), jid:lserver(), jid:jid()) -> any(). |
600 |
|
change_shaper(StateData, Host, JID) -> |
601 |
28 |
{ok, HostType} = mongoose_domain_api:get_host_type(Host), |
602 |
28 |
Shaper = acl:match_rule(HostType, StateData#state.shaper, JID), |
603 |
28 |
(StateData#state.sockmod):change_shaper(StateData#state.socket, Shaper). |
604 |
|
|
605 |
|
|
606 |
|
-spec new_id() -> binary(). |
607 |
|
new_id() -> |
608 |
79 |
mongoose_bin:gen_from_crypto(). |
609 |
|
|
610 |
|
|
611 |
|
-spec cancel_timer(reference()) -> 'ok'. |
612 |
|
cancel_timer(Timer) -> |
613 |
127 |
erlang:cancel_timer(Timer), |
614 |
127 |
receive |
615 |
|
{timeout, Timer, _} -> |
616 |
:-( |
ok |
617 |
|
after 0 -> |
618 |
127 |
ok |
619 |
|
end. |
620 |
|
|
621 |
|
|
622 |
|
-spec is_key_packet(exml:element()) -> 'false' | {'key', _, _, _, binary()} |
623 |
|
| {'verify', _, _, _, binary()}. |
624 |
|
is_key_packet(#xmlel{name = Name, attrs = Attrs, |
625 |
|
children = Els}) when Name == <<"db:result">> -> |
626 |
28 |
{key, |
627 |
|
xml:get_attr_s(<<"to">>, Attrs), |
628 |
|
xml:get_attr_s(<<"from">>, Attrs), |
629 |
|
xml:get_attr_s(<<"id">>, Attrs), |
630 |
|
xml:get_cdata(Els)}; |
631 |
|
is_key_packet(#xmlel{name = Name, attrs = Attrs, |
632 |
|
children = Els}) when Name == <<"db:verify">> -> |
633 |
27 |
{verify, |
634 |
|
xml:get_attr_s(<<"to">>, Attrs), |
635 |
|
xml:get_attr_s(<<"from">>, Attrs), |
636 |
|
xml:get_attr_s(<<"id">>, Attrs), |
637 |
|
xml:get_cdata(Els)}; |
638 |
|
is_key_packet(_) -> |
639 |
72 |
false. |
640 |
|
|
641 |
|
|
642 |
|
-spec match_domain(binary(), binary()) -> boolean(). |
643 |
|
match_domain(Domain, Domain) -> |
644 |
3 |
true; |
645 |
|
match_domain(Domain, Pattern) -> |
646 |
3 |
DLabels = binary:split(Domain, <<".">>, [global]), |
647 |
3 |
PLabels = binary:split(Pattern, <<".">>, [global]), |
648 |
3 |
match_labels(DLabels, PLabels). |
649 |
|
|
650 |
|
|
651 |
|
-spec match_labels([binary()], [binary()]) -> boolean(). |
652 |
|
match_labels([], []) -> |
653 |
:-( |
true; |
654 |
|
match_labels([], [_ | _]) -> |
655 |
:-( |
false; |
656 |
|
match_labels([_ | _], []) -> |
657 |
:-( |
false; |
658 |
|
match_labels([DL | DLabels], [PL | PLabels]) -> |
659 |
3 |
PLlist = binary_to_list(PL), |
660 |
3 |
case lists:all(fun(C) -> (($a =< C) andalso (C =< $z)) |
661 |
:-( |
orelse (($0 =< C) andalso (C =< $9)) |
662 |
:-( |
orelse (C == $-) orelse (C == $*) |
663 |
|
end, PLlist) of |
664 |
|
true -> |
665 |
3 |
Regexp = xmerl_regexp:sh_to_awk(PLlist), |
666 |
3 |
case re:run(binary_to_list(DL), Regexp, [{capture, none}]) of |
667 |
|
match -> |
668 |
:-( |
match_labels(DLabels, PLabels); |
669 |
|
nomatch -> |
670 |
3 |
false |
671 |
|
end; |
672 |
|
false -> |
673 |
:-( |
false |
674 |
|
end. |
675 |
|
|
676 |
|
verify_cert_and_get_sasl(SockMod, Socket, TLSCertVerify) -> |
677 |
21 |
case SockMod:get_peer_certificate(Socket) of |
678 |
|
{ok, _} -> |
679 |
6 |
[#xmlel{name = <<"mechanisms">>, |
680 |
|
attrs = [{<<"xmlns">>, ?NS_SASL}], |
681 |
|
children = [#xmlel{name = <<"mechanism">>, |
682 |
|
children = [#xmlcdata{content = <<"EXTERNAL">>}]}]}]; |
683 |
|
{bad_cert, CertVerifyRes} -> |
684 |
15 |
check_sasl_tls_certveify(TLSCertVerify, CertVerifyRes); |
685 |
:-( |
no_peer_cert -> [] |
686 |
|
end. |
687 |
|
|
688 |
|
check_sasl_tls_certveify(true, CertVerifyRes) -> |
689 |
1 |
{error_cert_verif, CertVerifyRes}; |
690 |
|
check_sasl_tls_certveify(false, _) -> |
691 |
14 |
[]. |
692 |
|
|
693 |
|
check_auth_domain(error, _) -> |
694 |
:-( |
false; |
695 |
|
check_auth_domain(AuthDomain, {ok, Cert}) -> |
696 |
4 |
case ejabberd_s2s:domain_utf8_to_ascii(AuthDomain) of |
697 |
|
false -> |
698 |
1 |
false; |
699 |
|
PCAuthDomain -> |
700 |
3 |
lists:any( |
701 |
6 |
fun(D) -> match_domain( PCAuthDomain, D) end, |
702 |
|
cert_utils:get_cert_domains(Cert)) |
703 |
|
end; |
704 |
|
check_auth_domain(_, _) -> |
705 |
:-( |
false. |
706 |
|
|
707 |
|
handle_auth_res(true, AuthDomain, StateData) -> |
708 |
3 |
send_element(StateData, |
709 |
|
#xmlel{name = <<"success">>, |
710 |
|
attrs = [{<<"xmlns">>, ?NS_SASL}]}), |
711 |
3 |
?LOG_DEBUG(#{what => s2s_auth_success, |
712 |
|
text => <<"Accepted s2s authentication">>, |
713 |
3 |
socket => StateData#state.socket, auth_domain => AuthDomain}), |
714 |
3 |
{next_state, wait_for_stream, |
715 |
|
StateData#state{streamid = new_id(), |
716 |
|
authenticated = true, |
717 |
|
auth_domain = AuthDomain |
718 |
|
}}; |
719 |
|
handle_auth_res(_, _, StateData) -> |
720 |
1 |
send_element(StateData, |
721 |
|
#xmlel{name = <<"failure">>, |
722 |
|
attrs = [{<<"xmlns">>, ?NS_SASL}]}), |
723 |
1 |
send_text(StateData, ?STREAM_TRAILER), |
724 |
1 |
{stop, normal, StateData}. |
725 |
|
|
726 |
|
|
727 |
|
get_tls_params(false) -> |
728 |
19 |
{false, false, false}; |
729 |
|
get_tls_params(true) -> |
730 |
:-( |
{true, false, false}; |
731 |
|
get_tls_params(optional) -> |
732 |
11 |
{true, false, false}; |
733 |
|
get_tls_params(required) -> |
734 |
11 |
{true, true, false}; |
735 |
|
get_tls_params(required_trusted) -> |
736 |
10 |
{true, true, true}. |
737 |
|
|
738 |
|
get_tls_xmlel(#state{tls_enabled = true}) -> |
739 |
21 |
[]; |
740 |
|
get_tls_xmlel(#state{tls_enabled = false, tls_required = false}) -> |
741 |
8 |
[#xmlel{name = <<"starttls">>, |
742 |
|
attrs = [{<<"xmlns">>, ?NS_TLS}]}]; |
743 |
|
get_tls_xmlel(#state{tls_enabled = false, tls_required = true}) -> |
744 |
14 |
[#xmlel{name = <<"starttls">>, |
745 |
|
attrs = [{<<"xmlns">>, ?NS_TLS}], |
746 |
|
children = [#xmlel{name = <<"required">>}]}]. |