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