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 |
|
-behaviour(mongoose_listener). |
30 |
|
|
31 |
|
%% mongoose_listener API |
32 |
|
-export([start_listener/1]). |
33 |
|
|
34 |
|
%% External exports |
35 |
|
-export([start/2, |
36 |
|
start_link/2, |
37 |
|
send_validity_from_s2s_out/3, |
38 |
|
match_domain/2]). |
39 |
|
|
40 |
|
%% gen_fsm callbacks |
41 |
|
-export([init/1, |
42 |
|
wait_for_stream/2, |
43 |
|
wait_for_feature_request/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 |
|
|
51 |
|
-export_type([connection_info/0]). |
52 |
|
|
53 |
|
-ignore_xref([match_domain/2, start/2, start_link/2, stream_established/2, |
54 |
|
wait_for_feature_request/2, wait_for_stream/2]). |
55 |
|
|
56 |
|
-include("mongoose.hrl"). |
57 |
|
-include("jlib.hrl"). |
58 |
|
|
59 |
|
-record(state, {socket :: mongoose_transport:socket_data(), |
60 |
|
streamid :: ejabberd_s2s:stream_id(), |
61 |
|
shaper :: mongoose_shaper:shaper(), |
62 |
|
tls = false :: boolean(), |
63 |
|
tls_enabled = false :: boolean(), |
64 |
|
tls_required = false :: boolean(), |
65 |
|
tls_cert_verify = false :: boolean(), |
66 |
|
tls_options :: mongoose_tls:options(), |
67 |
|
server :: jid:lserver() | undefined, |
68 |
|
host_type :: mongooseim:host_type() | undefined, |
69 |
|
authenticated = false :: boolean(), |
70 |
|
auth_domain :: jid:lserver() | undefined, |
71 |
|
connections = #{} :: map(), |
72 |
|
timer :: reference() |
73 |
|
}). |
74 |
|
-type state() :: #state{}. |
75 |
|
|
76 |
|
-type connection_info() :: |
77 |
|
#{pid => pid(), |
78 |
|
direction => in, |
79 |
|
statename => statename(), |
80 |
|
addr => inet:ip_address(), |
81 |
|
port => inet:port_number(), |
82 |
|
streamid => ejabberd_s2s:stream_id(), |
83 |
|
tls => boolean(), |
84 |
|
tls_enabled => boolean(), |
85 |
|
tls_options => mongoose_tls:options(), |
86 |
|
authenticated => boolean(), |
87 |
|
shaper => mongoose_shaper:shaper(), |
88 |
|
domains => [jid:lserver()]}. |
89 |
|
|
90 |
|
-type statename() :: 'stream_established' | 'wait_for_feature_request'. |
91 |
|
%% FSM handler return value |
92 |
|
-type fsm_return() :: {'stop', Reason :: 'normal', state()} |
93 |
|
| {'next_state', statename(), state()} |
94 |
|
| {'next_state', statename(), state(), Timeout :: integer()}. |
95 |
|
%-define(DBGFSM, true). |
96 |
|
|
97 |
|
-ifdef(DBGFSM). |
98 |
|
-define(FSMOPTS, [{debug, [trace]}]). |
99 |
|
-else. |
100 |
|
-define(FSMOPTS, []). |
101 |
|
-endif. |
102 |
|
|
103 |
|
-define(STREAM_HEADER(Version), |
104 |
|
(<<"<?xml version='1.0'?>" |
105 |
|
"<stream:stream " |
106 |
|
"xmlns:stream='http://etherx.jabber.org/streams' " |
107 |
|
"xmlns='jabber:server' " |
108 |
|
"xmlns:db='jabber:server:dialback' " |
109 |
|
"id='", (StateData#state.streamid)/binary, "'", Version/binary, ">">>) |
110 |
|
). |
111 |
|
|
112 |
|
-type socket() :: term(). |
113 |
|
-type options() :: #{shaper := atom(), tls := mongoose_tls:options(), atom() => any()}. |
114 |
|
|
115 |
|
%%%---------------------------------------------------------------------- |
116 |
|
%%% API |
117 |
|
%%%---------------------------------------------------------------------- |
118 |
|
-spec start(socket(), options()) -> |
119 |
|
{error, _} | {ok, undefined | pid()} | {ok, undefined | pid(), _}. |
120 |
|
start(Socket, Opts) -> |
121 |
:-( |
supervisor:start_child(ejabberd_s2s_in_sup, [Socket, Opts]). |
122 |
|
|
123 |
|
-spec start_link(socket(), options()) -> ignore | {error, _} | {ok, pid()}. |
124 |
|
start_link(Socket, Opts) -> |
125 |
:-( |
gen_fsm_compat:start_link(ejabberd_s2s_in, [Socket, Opts], ?FSMOPTS). |
126 |
|
|
127 |
|
-spec start_listener(options()) -> ok. |
128 |
|
start_listener(Opts) -> |
129 |
4 |
mongoose_tcp_listener:start_listener(Opts). |
130 |
|
|
131 |
|
-spec send_validity_from_s2s_out(pid(), boolean(), ejabberd_s2s:fromto()) -> ok. |
132 |
|
send_validity_from_s2s_out(Pid, IsValid, FromTo) when is_boolean(IsValid) -> |
133 |
:-( |
Event = {validity_from_s2s_out, IsValid, FromTo}, |
134 |
:-( |
p1_fsm:send_event(Pid, Event). |
135 |
|
|
136 |
|
%%%---------------------------------------------------------------------- |
137 |
|
%%% Callback functions from gen_fsm |
138 |
|
%%%---------------------------------------------------------------------- |
139 |
|
|
140 |
|
%%---------------------------------------------------------------------- |
141 |
|
%% Func: init/1 |
142 |
|
%% Returns: {ok, StateName, StateData} | |
143 |
|
%% {ok, StateName, StateData, Timeout} | |
144 |
|
%% ignore | |
145 |
|
%% {stop, StopReason} |
146 |
|
%%---------------------------------------------------------------------- |
147 |
|
-spec init([socket() | options(), ...]) -> {ok, wait_for_stream, state()}. |
148 |
|
init([Socket, #{shaper := Shaper, tls := TLSOpts}]) -> |
149 |
:-( |
?LOG_DEBUG(#{what => s2s_in_started, |
150 |
|
text => <<"New incoming S2S connection">>, |
151 |
:-( |
socket => Socket}), |
152 |
:-( |
Timer = erlang:start_timer(mongoose_s2s_lib:timeout(), self(), []), |
153 |
:-( |
{ok, wait_for_stream, |
154 |
|
#state{socket = Socket, |
155 |
|
streamid = new_id(), |
156 |
|
shaper = Shaper, |
157 |
|
tls_enabled = false, |
158 |
|
tls_options = TLSOpts, |
159 |
|
timer = Timer}}. |
160 |
|
|
161 |
|
%%---------------------------------------------------------------------- |
162 |
|
%% Func: StateName/2 |
163 |
|
%% Returns: {next_state, NextStateName, NextStateData} | |
164 |
|
%% {next_state, NextStateName, NextStateData, Timeout} | |
165 |
|
%% {stop, Reason, NewStateData} |
166 |
|
%%---------------------------------------------------------------------- |
167 |
|
|
168 |
|
-spec wait_for_stream(ejabberd:xml_stream_item(), state()) -> fsm_return(). |
169 |
|
wait_for_stream({xmlstreamstart, _Name, Attrs} = Event, StateData) -> |
170 |
:-( |
case maps:from_list(Attrs) of |
171 |
|
AttrMap = #{<<"xmlns">> := <<"jabber:server">>, <<"to">> := Server} -> |
172 |
:-( |
case StateData#state.server of |
173 |
|
undefined -> |
174 |
:-( |
case mongoose_domain_api:get_host_type(Server) of |
175 |
|
{error, not_found} -> |
176 |
:-( |
Info = #{location => ?LOCATION, last_event => Event}, |
177 |
:-( |
stream_start_error(StateData, Info, mongoose_xmpp_errors:host_unknown()); |
178 |
|
{ok, HostType} -> |
179 |
:-( |
UseTLS = mongoose_config:get_opt([{s2s, HostType}, use_starttls]), |
180 |
:-( |
{StartTLS, TLSRequired, TLSCertVerify} = get_tls_params(UseTLS), |
181 |
:-( |
start_stream(AttrMap, StateData#state{server = Server, |
182 |
|
host_type = HostType, |
183 |
|
tls = StartTLS, |
184 |
|
tls_required = TLSRequired, |
185 |
|
tls_cert_verify = TLSCertVerify}) |
186 |
|
end; |
187 |
|
Server -> |
188 |
:-( |
start_stream(AttrMap, StateData); |
189 |
|
_Other -> |
190 |
:-( |
Msg = <<"The 'to' attribute differs from the originally provided one">>, |
191 |
:-( |
Info = #{location => ?LOCATION, last_event => Event, |
192 |
|
expected_server => StateData#state.server, provided_server => Server}, |
193 |
:-( |
stream_start_error(StateData, Info, mongoose_xmpp_errors:host_unknown(?MYLANG, Msg)) |
194 |
|
end; |
195 |
|
#{<<"xmlns">> := <<"jabber:server">>} -> |
196 |
:-( |
Msg = <<"The 'to' attribute is missing">>, |
197 |
:-( |
Info = #{location => ?LOCATION, last_event => Event}, |
198 |
:-( |
stream_start_error(StateData, Info, mongoose_xmpp_errors:improper_addressing(?MYLANG, Msg)); |
199 |
|
_ -> |
200 |
:-( |
Info = #{location => ?LOCATION, last_event => Event}, |
201 |
:-( |
stream_start_error(StateData, Info, mongoose_xmpp_errors:invalid_namespace()) |
202 |
|
end; |
203 |
|
wait_for_stream({xmlstreamerror, _} = Event, StateData) -> |
204 |
:-( |
Info = #{location => ?LOCATION, last_event => Event, |
205 |
|
reason => s2s_in_wait_for_stream_error}, |
206 |
:-( |
stream_start_error(StateData, Info, mongoose_xmpp_errors:xml_not_well_formed()); |
207 |
|
wait_for_stream(timeout, StateData) -> |
208 |
:-( |
?LOG_WARNING(#{what => s2s_in_wait_for_stream_timeout}), |
209 |
:-( |
{stop, normal, StateData}; |
210 |
|
wait_for_stream(closed, StateData) -> |
211 |
:-( |
?LOG_WARNING(#{what => s2s_in_wait_for_stream_closed}), |
212 |
:-( |
{stop, normal, StateData}. |
213 |
|
|
214 |
|
start_stream(#{<<"version">> := <<"1.0">>, <<"from">> := RemoteServer} = Event, |
215 |
|
StateData = #state{tls = true, authenticated = false, server = Server, |
216 |
|
host_type = HostType}) -> |
217 |
:-( |
SASL = case StateData#state.tls_enabled of |
218 |
|
true -> |
219 |
:-( |
verify_cert_and_get_sasl(StateData#state.socket, |
220 |
|
StateData#state.tls_cert_verify); |
221 |
|
_Else -> |
222 |
:-( |
[] |
223 |
|
end, |
224 |
:-( |
StartTLS = get_tls_xmlel(StateData), |
225 |
:-( |
case SASL of |
226 |
|
{error_cert_verif, CertError} -> |
227 |
:-( |
?LOG_WARNING(#{what => s2s_connection_closing, |
228 |
|
text => <<"Closing s2s connection">>, |
229 |
|
server => StateData#state.server, |
230 |
|
remote_server => RemoteServer, |
231 |
|
reason => cert_error, |
232 |
:-( |
cert_error => CertError}), |
233 |
:-( |
Info = #{location => ?LOCATION, last_event => Event, reason => error_cert_verif}, |
234 |
:-( |
stream_start_error(StateData, Info, |
235 |
|
mongoose_xmpp_errors:policy_violation(?MYLANG, CertError)); |
236 |
|
%% We were stopping ejabberd_s2s_out connection in the older version of the code |
237 |
|
%% from this location. But stopping outgoing connections just because a non-verified |
238 |
|
%% incoming connection fails is an abuse risk (a hacker could connect with an invalid |
239 |
|
%% certificate, it should not cause stopping ejabberd_s2s_out connections). |
240 |
|
_ -> |
241 |
:-( |
send_text(StateData, ?STREAM_HEADER(<<" version='1.0'">>)), |
242 |
:-( |
send_element(StateData, |
243 |
|
#xmlel{name = <<"stream:features">>, |
244 |
|
children = SASL ++ StartTLS ++ stream_features(HostType, Server)}), |
245 |
:-( |
{next_state, wait_for_feature_request, StateData} |
246 |
|
end; |
247 |
|
start_stream(#{<<"version">> := <<"1.0">>}, |
248 |
|
StateData = #state{authenticated = true, host_type = HostType, server = Server}) -> |
249 |
:-( |
send_text(StateData, ?STREAM_HEADER(<<" version='1.0'">>)), |
250 |
:-( |
send_element(StateData, #xmlel{name = <<"stream:features">>, |
251 |
|
children = stream_features(HostType, Server)}), |
252 |
:-( |
{next_state, stream_established, StateData}; |
253 |
|
start_stream(#{<<"xmlns:db">> := <<"jabber:server:dialback">>}, StateData) -> |
254 |
:-( |
send_text(StateData, ?STREAM_HEADER(<<>>)), |
255 |
:-( |
{next_state, stream_established, StateData}; |
256 |
|
start_stream(Event, StateData) -> |
257 |
:-( |
Info = #{location => ?LOCATION, last_event => Event}, |
258 |
:-( |
stream_start_error(StateData, Info, mongoose_xmpp_errors:invalid_xml()). |
259 |
|
|
260 |
|
stream_start_error(StateData, Info, Error) -> |
261 |
:-( |
send_text(StateData, ?STREAM_HEADER(<<>>)), |
262 |
:-( |
send_element(StateData, Error), |
263 |
:-( |
send_text(StateData, ?STREAM_TRAILER), |
264 |
:-( |
?LOG_WARNING(Info#{what => s2s_in_stream_start_error, element => Error}), |
265 |
:-( |
{stop, normal, StateData}. |
266 |
|
|
267 |
|
-spec wait_for_feature_request(ejabberd:xml_stream_item(), state() |
268 |
|
) -> fsm_return(). |
269 |
|
wait_for_feature_request({xmlstreamelement, El}, StateData) -> |
270 |
:-( |
#xmlel{name = Name, attrs = Attrs, children = Els} = El, |
271 |
:-( |
TLS = StateData#state.tls, |
272 |
:-( |
TLSEnabled = StateData#state.tls_enabled, |
273 |
:-( |
case {xml:get_attr_s(<<"xmlns">>, Attrs), Name} of |
274 |
|
{?NS_TLS, <<"starttls">>} when TLS == true, |
275 |
|
TLSEnabled == false -> |
276 |
:-( |
?LOG_DEBUG(#{what => s2s_starttls}), |
277 |
:-( |
TLSOpts = tls_options_with_certfile(StateData), |
278 |
:-( |
TLSSocket = mongoose_transport:wait_for_tls_handshake( |
279 |
|
StateData#state.socket, TLSOpts, |
280 |
|
#xmlel{name = <<"proceed">>, |
281 |
|
attrs = [{<<"xmlns">>, ?NS_TLS}]}), |
282 |
:-( |
{next_state, wait_for_stream, |
283 |
|
StateData#state{socket = TLSSocket, |
284 |
|
streamid = new_id(), |
285 |
|
tls_enabled = true, |
286 |
|
tls_options = TLSOpts |
287 |
|
}}; |
288 |
|
{?NS_SASL, <<"auth">>} when TLSEnabled -> |
289 |
:-( |
Mech = xml:get_attr_s(<<"mechanism">>, Attrs), |
290 |
:-( |
case Mech of |
291 |
|
<<"EXTERNAL">> -> |
292 |
:-( |
Auth = jlib:decode_base64(xml:get_cdata(Els)), |
293 |
:-( |
AuthDomain = jid:nameprep(Auth), |
294 |
:-( |
CertData = mongoose_transport:get_peer_certificate( |
295 |
|
StateData#state.socket), |
296 |
:-( |
AuthRes = check_auth_domain(AuthDomain, CertData), |
297 |
:-( |
handle_auth_res(AuthRes, AuthDomain, StateData); |
298 |
|
_ -> |
299 |
:-( |
send_element(StateData, |
300 |
|
#xmlel{name = <<"failure">>, |
301 |
|
attrs = [{<<"xmlns">>, ?NS_SASL}], |
302 |
|
children = [#xmlel{name = <<"invalid-mechanism">>}]}), |
303 |
:-( |
?LOG_WARNING(#{what => s2s_in_invalid_mechanism}), |
304 |
:-( |
{stop, normal, StateData} |
305 |
|
end; |
306 |
|
_ -> |
307 |
:-( |
stream_established({xmlstreamelement, El}, StateData) |
308 |
|
end; |
309 |
|
wait_for_feature_request({xmlstreamend, _Name}, StateData) -> |
310 |
:-( |
send_text(StateData, ?STREAM_TRAILER), |
311 |
:-( |
?LOG_WARNING(#{what => s2s_in_got_stream_end_before_feature_request}), |
312 |
:-( |
{stop, normal, StateData}; |
313 |
|
wait_for_feature_request({xmlstreamerror, _}, StateData) -> |
314 |
:-( |
send_element(StateData, mongoose_xmpp_errors:xml_not_well_formed()), |
315 |
:-( |
send_text(StateData, ?STREAM_TRAILER), |
316 |
:-( |
?LOG_WARNING(#{what => s2s_in_got_stream_error_before_feature_request}), |
317 |
:-( |
{stop, normal, StateData}; |
318 |
|
wait_for_feature_request(closed, StateData) -> |
319 |
:-( |
?LOG_WARNING(#{what => s2s_in_got_closed_before_feature_request}), |
320 |
:-( |
{stop, normal, StateData}. |
321 |
|
|
322 |
|
tls_options_with_certfile(#state{host_type = HostType, tls_options = TLSOptions}) -> |
323 |
:-( |
case mongoose_s2s_lib:lookup_certfile(HostType) of |
324 |
:-( |
{ok, CertFile} -> TLSOptions#{certfile => CertFile}; |
325 |
:-( |
{error, not_found} -> TLSOptions |
326 |
|
end. |
327 |
|
|
328 |
|
-spec stream_established(ejabberd:xml_stream_item(), state()) -> fsm_return(). |
329 |
|
stream_established({xmlstreamelement, El}, StateData) -> |
330 |
:-( |
cancel_timer(StateData#state.timer), |
331 |
:-( |
Timer = erlang:start_timer(mongoose_s2s_lib:timeout(), self(), []), |
332 |
:-( |
case mongoose_s2s_dialback:parse_key(El) of |
333 |
|
%% Incoming dialback key, we have to verify it using ejabberd_s2s_out before |
334 |
|
%% accepting any incoming stanzas |
335 |
|
%% (we have to receive the `validity_from_s2s_out' event first). |
336 |
|
{step_1, FromTo, StreamID, Key} = Parsed -> |
337 |
:-( |
?LOG_DEBUG(#{what => s2s_in_get_key, |
338 |
:-( |
from_to => FromTo, stream_id => StreamID, key => Key}), |
339 |
|
%% Checks if the from domain is allowed and if the to |
340 |
|
%% domain is handled by this server: |
341 |
:-( |
case {mongoose_s2s_lib:allow_host(FromTo), is_local_host_known(FromTo)} of |
342 |
|
{true, true} -> |
343 |
:-( |
ejabberd_s2s_out:terminate_if_waiting_delay(FromTo), |
344 |
:-( |
StartType = {verify, self(), Key, StateData#state.streamid}, |
345 |
|
%% Could we reuse an existing ejabberd_s2s_out connection |
346 |
|
%% instead of making a new one? |
347 |
:-( |
ejabberd_s2s_out:start(FromTo, StartType), |
348 |
:-( |
Conns = maps:put(FromTo, wait_for_verification, |
349 |
|
StateData#state.connections), |
350 |
:-( |
change_shaper(StateData, FromTo), |
351 |
:-( |
{next_state, |
352 |
|
stream_established, |
353 |
|
StateData#state{connections = Conns, timer = Timer}}; |
354 |
|
{_, false} -> |
355 |
:-( |
send_element(StateData, mongoose_xmpp_errors:host_unknown()), |
356 |
:-( |
?LOG_WARNING(#{what => s2s_in_key_from_uknown_host, element => El, |
357 |
:-( |
parsed => Parsed, from_to => FromTo}), |
358 |
:-( |
{stop, normal, StateData}; |
359 |
|
{false, _} -> |
360 |
:-( |
send_element(StateData, mongoose_xmpp_errors:invalid_from()), |
361 |
:-( |
?LOG_WARNING(#{what => s2s_in_key_with_invalid_from, element => El}), |
362 |
:-( |
{stop, normal, StateData} |
363 |
|
end; |
364 |
|
%% Incoming dialback verification request |
365 |
|
%% We have to check it using secrets and reply if it is valid or not |
366 |
|
{step_2, FromTo, StreamID, Key} -> |
367 |
:-( |
?LOG_DEBUG(#{what => s2s_in_verify_key, |
368 |
:-( |
from_to => FromTo, stream_id => StreamID, key => Key}), |
369 |
:-( |
IsValid = Key =:= ejabberd_s2s:key(StateData#state.host_type, FromTo, StreamID), |
370 |
:-( |
send_element(StateData, mongoose_s2s_dialback:step_3(FromTo, StreamID, IsValid)), |
371 |
:-( |
{next_state, stream_established, StateData#state{timer = Timer}}; |
372 |
|
false -> |
373 |
:-( |
Res = parse_and_route_incoming_stanza(El, StateData), |
374 |
:-( |
handle_routing_result(Res, El, StateData), |
375 |
:-( |
{next_state, stream_established, StateData#state{timer = Timer}} |
376 |
|
end; |
377 |
|
stream_established({validity_from_s2s_out, IsValid, FromTo}, StateData) -> |
378 |
:-( |
handle_validity_from_s2s_out(IsValid, FromTo, StateData); |
379 |
|
stream_established({xmlstreamend, _Name}, StateData) -> |
380 |
:-( |
send_text(StateData, ?STREAM_TRAILER), |
381 |
:-( |
{stop, normal, StateData}; |
382 |
|
stream_established({xmlstreamerror, _}, StateData) -> |
383 |
:-( |
send_element(StateData, mongoose_xmpp_errors:xml_not_well_formed()), |
384 |
:-( |
send_text(StateData, ?STREAM_TRAILER), |
385 |
:-( |
?LOG_WARNING(#{what => s2s_in_stream_error, state_name => stream_established}), |
386 |
:-( |
{stop, normal, StateData}; |
387 |
|
stream_established(timeout, StateData) -> |
388 |
:-( |
{stop, normal, StateData}; |
389 |
|
stream_established(closed, StateData) -> |
390 |
:-( |
{stop, normal, StateData}. |
391 |
|
|
392 |
|
-spec handle_validity_from_s2s_out(boolean(), ejabberd_s2s:fromto(), #state{}) -> |
393 |
|
{next_state, stream_established, #state{}}. |
394 |
|
handle_validity_from_s2s_out(IsValid, FromTo, StateData) -> |
395 |
:-( |
send_element(StateData, mongoose_s2s_dialback:step_4(FromTo, IsValid)), |
396 |
:-( |
{next_state, stream_established, update_connections(IsValid, FromTo, StateData)}. |
397 |
|
|
398 |
|
update_connections(true, FromTo, StateData = #state{connections = Cons}) -> |
399 |
:-( |
StateData#state{connections = maps:put(FromTo, established, Cons)}; |
400 |
|
update_connections(false, FromTo, StateData = #state{connections = Cons}) -> |
401 |
:-( |
StateData#state{connections = maps:remove(FromTo, Cons)}. |
402 |
|
|
403 |
|
handle_routing_result(ok, _El, _StateData) -> |
404 |
:-( |
ok; |
405 |
|
handle_routing_result({error, Reason}, El, _StateData) -> |
406 |
:-( |
?LOG_WARNING(#{what => s2s_in_route_failed, reason => Reason, element => El}). |
407 |
|
|
408 |
|
parse_and_route_incoming_stanza(El, StateData) -> |
409 |
:-( |
NewEl = jlib:remove_attr(<<"xmlns">>, El), |
410 |
:-( |
RemoteJid = jid:from_binary(exml_query:attr(El, <<"from">>, <<>>)), |
411 |
:-( |
LocalJid = jid:from_binary(exml_query:attr(El, <<"to">>, <<>>)), |
412 |
:-( |
case {RemoteJid, LocalJid, is_valid_stanza(NewEl)} of |
413 |
|
{#jid{}, #jid{}, true} -> |
414 |
:-( |
route_incoming_stanza(RemoteJid, LocalJid, NewEl, StateData); |
415 |
|
_ -> |
416 |
:-( |
{error, invalid_stanza} |
417 |
|
end. |
418 |
|
|
419 |
|
-spec route_incoming_stanza(RemoteJid :: jid:jid(), |
420 |
|
LocalJid :: jid:jid(), |
421 |
|
El :: exml:element(), |
422 |
|
StateData :: state()) -> ok | {error, term()}. |
423 |
|
route_incoming_stanza(RemoteJid, LocalJid, El, StateData) -> |
424 |
:-( |
LRemoteServer = RemoteJid#jid.lserver, |
425 |
:-( |
LLocalServer = LocalJid#jid.lserver, |
426 |
:-( |
FromTo = {LLocalServer, LRemoteServer}, |
427 |
:-( |
Acc = mongoose_acc:new(#{ location => ?LOCATION, |
428 |
|
lserver => LLocalServer, |
429 |
|
element => El, |
430 |
|
from_jid => RemoteJid, |
431 |
|
to_jid => LocalJid }), |
432 |
:-( |
case is_s2s_authenticated_or_connected(FromTo, StateData) of |
433 |
|
true -> |
434 |
:-( |
route_stanza(Acc); |
435 |
|
false -> |
436 |
:-( |
{error, not_allowed} |
437 |
|
end. |
438 |
|
|
439 |
|
is_s2s_authenticated_or_connected(FromTo, StateData) -> |
440 |
:-( |
is_s2s_authenticated(FromTo, StateData) orelse |
441 |
:-( |
is_s2s_connected(FromTo, StateData). |
442 |
|
|
443 |
|
-spec is_s2s_authenticated(ejabberd_s2s:fromto(), #state{}) -> boolean(). |
444 |
|
is_s2s_authenticated(_FromTo, #state{authenticated = false}) -> |
445 |
:-( |
false; |
446 |
|
is_s2s_authenticated(FromTo, State) -> |
447 |
:-( |
same_auth_domain(FromTo, State) andalso is_local_host_known(FromTo). |
448 |
|
|
449 |
|
-spec same_auth_domain(ejabberd_s2s:fromto(), #state{}) -> boolean(). |
450 |
|
same_auth_domain({_, LRemoteServer}, #state{auth_domain = AuthDomain}) -> |
451 |
:-( |
LRemoteServer =:= AuthDomain. |
452 |
|
|
453 |
|
-spec is_s2s_connected(ejabberd_s2s:fromto(), #state{}) -> boolean(). |
454 |
|
is_s2s_connected(FromTo, StateData) -> |
455 |
:-( |
established =:= maps:get(FromTo, StateData#state.connections, false). |
456 |
|
|
457 |
|
-spec is_valid_stanza(exml:element()) -> boolean(). |
458 |
|
is_valid_stanza(#xmlel{name = Name}) -> |
459 |
:-( |
is_valid_stanza_name(Name). |
460 |
|
|
461 |
:-( |
is_valid_stanza_name(<<"iq">>) -> true; |
462 |
:-( |
is_valid_stanza_name(<<"message">>) -> true; |
463 |
:-( |
is_valid_stanza_name(<<"presence">>) -> true; |
464 |
:-( |
is_valid_stanza_name(_) -> false. |
465 |
|
|
466 |
|
-spec route_stanza(mongoose_acc:t()) -> ok. |
467 |
|
route_stanza(Acc) -> |
468 |
:-( |
From = mongoose_acc:from_jid(Acc), |
469 |
:-( |
To = mongoose_acc:to_jid(Acc), |
470 |
:-( |
Acc1 = mongoose_hooks:s2s_receive_packet(Acc), |
471 |
:-( |
ejabberd_router:route(From, To, Acc1), |
472 |
:-( |
ok. |
473 |
|
|
474 |
|
handle_event(_Event, StateName, StateData) -> |
475 |
:-( |
{next_state, StateName, StateData}. |
476 |
|
|
477 |
|
-spec handle_sync_event(any(), any(), statename(), state()) -> |
478 |
|
{reply, ok | connection_info(), statename(), state()}. |
479 |
|
handle_sync_event(get_state_info, _From, StateName, StateData) -> |
480 |
:-( |
{reply, handle_get_state_info(StateName, StateData), StateName, StateData}; |
481 |
|
handle_sync_event(_Event, _From, StateName, StateData) -> |
482 |
:-( |
{reply, ok, StateName, StateData}. |
483 |
|
|
484 |
|
code_change(_OldVsn, StateName, StateData, _Extra) -> |
485 |
:-( |
{ok, StateName, StateData}. |
486 |
|
|
487 |
|
%%---------------------------------------------------------------------- |
488 |
|
%% Func: handle_info/3 |
489 |
|
%% Returns: {next_state, NextStateName, NextStateData} | |
490 |
|
%% {next_state, NextStateName, NextStateData, Timeout} | |
491 |
|
%% {stop, Reason, NewStateData} |
492 |
|
%%---------------------------------------------------------------------- |
493 |
|
handle_info({timeout, Timer, _}, _StateName, |
494 |
|
#state{timer = Timer} = StateData) -> |
495 |
:-( |
{stop, normal, StateData}; |
496 |
|
handle_info(_, StateName, StateData) -> |
497 |
:-( |
{next_state, StateName, StateData}. |
498 |
|
|
499 |
|
|
500 |
|
%%---------------------------------------------------------------------- |
501 |
|
%% Func: terminate/3 |
502 |
|
%% Purpose: Shutdown the fsm |
503 |
|
%% Returns: any |
504 |
|
%%---------------------------------------------------------------------- |
505 |
|
-spec terminate(any(), statename(), state()) -> 'ok'. |
506 |
|
terminate(Reason, StateName, StateData) -> |
507 |
:-( |
?LOG_DEBUG(#{what => s2s_in_stopped, reason => Reason, state_name => StateName}), |
508 |
:-( |
mongoose_transport:close(StateData#state.socket), |
509 |
:-( |
ok. |
510 |
|
|
511 |
|
%%%---------------------------------------------------------------------- |
512 |
|
%%% Internal functions |
513 |
|
%%%---------------------------------------------------------------------- |
514 |
|
|
515 |
|
-spec send_text(state(), binary()) -> ok. |
516 |
|
send_text(StateData, Text) -> |
517 |
:-( |
mongoose_transport:send_text(StateData#state.socket, Text). |
518 |
|
|
519 |
|
-spec send_element(state(), exml:element()) -> ok. |
520 |
|
send_element(StateData, El) -> |
521 |
:-( |
mongoose_transport:send_element(StateData#state.socket, El). |
522 |
|
|
523 |
|
-spec stream_features(mongooseim:host_type(), binary()) -> [exml:element()]. |
524 |
|
stream_features(HostType, Domain) -> |
525 |
:-( |
mongoose_hooks:s2s_stream_features(HostType, Domain). |
526 |
|
|
527 |
|
-spec change_shaper(state(), ejabberd_s2s:fromto()) -> ok. |
528 |
|
change_shaper(StateData, {LLocalServer, LRemoteServer}) -> |
529 |
:-( |
{ok, HostType} = mongoose_domain_api:get_host_type(LLocalServer), |
530 |
:-( |
JID = jid:make(<<>>, LRemoteServer, <<>>), |
531 |
:-( |
Shaper = acl:match_rule(HostType, StateData#state.shaper, JID), |
532 |
:-( |
mongoose_transport:change_shaper(StateData#state.socket, Shaper), |
533 |
:-( |
ok. |
534 |
|
|
535 |
|
|
536 |
|
-spec new_id() -> binary(). |
537 |
|
new_id() -> |
538 |
:-( |
mongoose_bin:gen_from_crypto(). |
539 |
|
|
540 |
|
|
541 |
|
-spec cancel_timer(reference()) -> 'ok'. |
542 |
|
cancel_timer(Timer) -> |
543 |
:-( |
erlang:cancel_timer(Timer), |
544 |
:-( |
receive |
545 |
|
{timeout, Timer, _} -> |
546 |
:-( |
ok |
547 |
|
after 0 -> |
548 |
:-( |
ok |
549 |
|
end. |
550 |
|
|
551 |
|
-spec match_domain(binary(), binary()) -> boolean(). |
552 |
|
match_domain(Domain, Domain) -> |
553 |
:-( |
true; |
554 |
|
match_domain(Domain, Pattern) -> |
555 |
:-( |
DLabels = binary:split(Domain, <<".">>, [global]), |
556 |
:-( |
PLabels = binary:split(Pattern, <<".">>, [global]), |
557 |
:-( |
match_labels(DLabels, PLabels). |
558 |
|
|
559 |
|
|
560 |
|
-spec match_labels([binary()], [binary()]) -> boolean(). |
561 |
|
match_labels([], []) -> |
562 |
:-( |
true; |
563 |
|
match_labels([], [_ | _]) -> |
564 |
:-( |
false; |
565 |
|
match_labels([_ | _], []) -> |
566 |
:-( |
false; |
567 |
|
match_labels([DL | DLabels], [PL | PLabels]) -> |
568 |
:-( |
PLlist = binary_to_list(PL), |
569 |
:-( |
case lists:all(fun(C) -> (($a =< C) andalso (C =< $z)) |
570 |
:-( |
orelse (($0 =< C) andalso (C =< $9)) |
571 |
:-( |
orelse (C == $-) orelse (C == $*) |
572 |
|
end, PLlist) of |
573 |
|
true -> |
574 |
:-( |
Regexp = xmerl_regexp:sh_to_awk(PLlist), |
575 |
:-( |
case re:run(binary_to_list(DL), Regexp, [{capture, none}]) of |
576 |
|
match -> |
577 |
:-( |
match_labels(DLabels, PLabels); |
578 |
|
nomatch -> |
579 |
:-( |
false |
580 |
|
end; |
581 |
|
false -> |
582 |
:-( |
false |
583 |
|
end. |
584 |
|
|
585 |
|
verify_cert_and_get_sasl(Socket, TLSCertVerify) -> |
586 |
:-( |
case mongoose_transport:get_peer_certificate(Socket) of |
587 |
|
{ok, _} -> |
588 |
:-( |
[#xmlel{name = <<"mechanisms">>, |
589 |
|
attrs = [{<<"xmlns">>, ?NS_SASL}], |
590 |
|
children = [#xmlel{name = <<"mechanism">>, |
591 |
|
children = [#xmlcdata{content = <<"EXTERNAL">>}]}]}]; |
592 |
|
{bad_cert, CertVerifyRes} -> |
593 |
:-( |
check_sasl_tls_certveify(TLSCertVerify, CertVerifyRes); |
594 |
:-( |
no_peer_cert -> [] |
595 |
|
end. |
596 |
|
|
597 |
|
check_sasl_tls_certveify(true, CertVerifyRes) -> |
598 |
:-( |
{error_cert_verif, CertVerifyRes}; |
599 |
|
check_sasl_tls_certveify(false, _) -> |
600 |
:-( |
[]. |
601 |
|
|
602 |
|
check_auth_domain(error, _) -> |
603 |
:-( |
false; |
604 |
|
check_auth_domain(AuthDomain, {ok, Cert}) -> |
605 |
:-( |
case mongoose_s2s_lib:domain_utf8_to_ascii(AuthDomain) of |
606 |
|
false -> |
607 |
:-( |
false; |
608 |
|
PCAuthDomain -> |
609 |
:-( |
lists:any( |
610 |
:-( |
fun(D) -> match_domain( PCAuthDomain, D) end, |
611 |
|
cert_utils:get_cert_domains(Cert)) |
612 |
|
end; |
613 |
|
check_auth_domain(_, _) -> |
614 |
:-( |
false. |
615 |
|
|
616 |
|
handle_auth_res(true, AuthDomain, StateData) -> |
617 |
:-( |
send_element(StateData, |
618 |
|
#xmlel{name = <<"success">>, |
619 |
|
attrs = [{<<"xmlns">>, ?NS_SASL}]}), |
620 |
:-( |
?LOG_DEBUG(#{what => s2s_auth_success, |
621 |
|
text => <<"Accepted s2s authentication">>, |
622 |
:-( |
socket => StateData#state.socket, auth_domain => AuthDomain}), |
623 |
:-( |
{next_state, wait_for_stream, |
624 |
|
StateData#state{streamid = new_id(), |
625 |
|
authenticated = true, |
626 |
|
auth_domain = AuthDomain |
627 |
|
}}; |
628 |
|
handle_auth_res(_, _, StateData) -> |
629 |
:-( |
send_element(StateData, |
630 |
|
#xmlel{name = <<"failure">>, |
631 |
|
attrs = [{<<"xmlns">>, ?NS_SASL}]}), |
632 |
:-( |
send_text(StateData, ?STREAM_TRAILER), |
633 |
:-( |
?LOG_WARNING(#{what => s2s_in_auth_failed}), |
634 |
:-( |
{stop, normal, StateData}. |
635 |
|
|
636 |
|
|
637 |
|
get_tls_params(false) -> |
638 |
:-( |
{false, false, false}; |
639 |
|
get_tls_params(true) -> |
640 |
:-( |
{true, false, false}; |
641 |
|
get_tls_params(optional) -> |
642 |
:-( |
{true, false, false}; |
643 |
|
get_tls_params(required) -> |
644 |
:-( |
{true, true, false}; |
645 |
|
get_tls_params(required_trusted) -> |
646 |
:-( |
{true, true, true}. |
647 |
|
|
648 |
|
get_tls_xmlel(#state{tls_enabled = true}) -> |
649 |
:-( |
[]; |
650 |
|
get_tls_xmlel(#state{tls_enabled = false, tls_required = false}) -> |
651 |
:-( |
[#xmlel{name = <<"starttls">>, |
652 |
|
attrs = [{<<"xmlns">>, ?NS_TLS}]}]; |
653 |
|
get_tls_xmlel(#state{tls_enabled = false, tls_required = true}) -> |
654 |
:-( |
[#xmlel{name = <<"starttls">>, |
655 |
|
attrs = [{<<"xmlns">>, ?NS_TLS}], |
656 |
|
children = [#xmlel{name = <<"required">>}]}]. |
657 |
|
|
658 |
|
-spec is_local_host_known(ejabberd_s2s:fromto()) -> boolean(). |
659 |
|
is_local_host_known({LLocalServer, _}) -> |
660 |
:-( |
mongoose_router:is_registered_route(LLocalServer) |
661 |
:-( |
orelse mongoose_component:has_component(LLocalServer) |
662 |
:-( |
orelse is_known_domain(LLocalServer). |
663 |
|
|
664 |
|
is_known_domain(Domain) -> |
665 |
:-( |
case mongoose_domain_api:get_host_type(Domain) of |
666 |
|
{ok, _HostType} -> |
667 |
:-( |
true; |
668 |
|
_ -> |
669 |
:-( |
false |
670 |
|
end. |
671 |
|
|
672 |
|
-spec handle_get_state_info(statename(), state()) -> connection_info(). |
673 |
|
handle_get_state_info(StateName, StateData) -> |
674 |
:-( |
{ok, {Addr, Port}} = mongoose_transport:peername(StateData#state.socket), |
675 |
:-( |
Domains = case StateData#state.authenticated of |
676 |
|
true -> |
677 |
:-( |
[StateData#state.auth_domain]; |
678 |
|
false -> |
679 |
:-( |
Connections = StateData#state.connections, |
680 |
:-( |
[LRemoteServer || {{_, LRemoteServer}, established} <- |
681 |
:-( |
maps:to_list(Connections)] |
682 |
|
end, |
683 |
:-( |
#{pid => self(), |
684 |
|
direction => in, |
685 |
|
statename => StateName, |
686 |
|
addr => Addr, |
687 |
|
port => Port, |
688 |
|
streamid => StateData#state.streamid, |
689 |
|
tls => StateData#state.tls, |
690 |
|
tls_enabled => StateData#state.tls_enabled, |
691 |
|
tls_options => StateData#state.tls_options, |
692 |
|
authenticated => StateData#state.authenticated, |
693 |
|
shaper => StateData#state.shaper, |
694 |
|
domains => Domains}. |