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