1 |
|
%%%---------------------------------------------------------------------- |
2 |
|
%%% File : ejabberd_s2s.erl |
3 |
|
%%% Author : Alexey Shchepin <alexey@process-one.net> |
4 |
|
%%% Purpose : S2S connections manager |
5 |
|
%%% Created : 7 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). |
27 |
|
-author('alexey@process-one.net'). |
28 |
|
|
29 |
|
-xep([{xep, 185}, {version, "1.0"}]). |
30 |
|
|
31 |
|
-behaviour(gen_server). |
32 |
|
-behaviour(xmpp_router). |
33 |
|
|
34 |
|
%% API |
35 |
|
-export([start_link/0, |
36 |
|
filter/4, |
37 |
|
route/4, |
38 |
|
have_connection/1, |
39 |
|
key/2, |
40 |
|
get_connections_pids/1, |
41 |
|
try_register/1, |
42 |
|
remove_connection/2, |
43 |
|
find_connection/2, |
44 |
|
dirty_get_connections/0, |
45 |
|
allow_host/2, |
46 |
|
incoming_s2s_number/0, |
47 |
|
outgoing_s2s_number/0, |
48 |
|
domain_utf8_to_ascii/1, |
49 |
|
timeout/0 |
50 |
|
]). |
51 |
|
|
52 |
|
%% Hooks callbacks |
53 |
|
-export([node_cleanup/2]). |
54 |
|
|
55 |
|
%% gen_server callbacks |
56 |
|
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, |
57 |
|
terminate/2, code_change/3]). |
58 |
|
|
59 |
|
%% ejabberd API |
60 |
|
-export([get_info_s2s_connections/1]). |
61 |
|
|
62 |
|
-ignore_xref([dirty_get_connections/0, get_info_s2s_connections/1, have_connection/1, |
63 |
|
incoming_s2s_number/0, node_cleanup/2, outgoing_s2s_number/0, start_link/0]). |
64 |
|
|
65 |
|
-include("mongoose.hrl"). |
66 |
|
-include("jlib.hrl"). |
67 |
|
-include("ejabberd_commands.hrl"). |
68 |
|
|
69 |
|
-define(DEFAULT_MAX_S2S_CONNECTIONS_NUMBER, 1). |
70 |
|
-define(DEFAULT_MAX_S2S_CONNECTIONS_NUMBER_PER_NODE, 1). |
71 |
|
|
72 |
|
-type fromto() :: {'global' | jid:server(), jid:server()}. |
73 |
|
-record(s2s, { |
74 |
|
fromto, |
75 |
|
pid |
76 |
|
}). |
77 |
|
-type s2s() :: #s2s{ |
78 |
|
fromto :: fromto(), |
79 |
|
pid :: pid() |
80 |
|
}. |
81 |
|
-record(s2s_shared, { |
82 |
|
scope = global :: global, % this might become per host type |
83 |
|
secret :: binary() |
84 |
|
}). |
85 |
|
-record(state, {}). |
86 |
|
|
87 |
|
%%==================================================================== |
88 |
|
%% API |
89 |
|
%%==================================================================== |
90 |
|
%%-------------------------------------------------------------------- |
91 |
|
%% Description: Starts the server |
92 |
|
%%-------------------------------------------------------------------- |
93 |
|
-spec start_link() -> 'ignore' | {'error', _} | {'ok', pid()}. |
94 |
|
start_link() -> |
95 |
80 |
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). |
96 |
|
|
97 |
|
filter(From, To, Acc, Packet) -> |
98 |
96 |
{From, To, Acc, Packet}. |
99 |
|
|
100 |
|
route(From, To, Acc, Packet) -> |
101 |
96 |
do_route(From, To, Acc, Packet). |
102 |
|
|
103 |
|
-spec remove_connection(_, pid()) -> 'ok' | {'aborted', _} | {'atomic', _}. |
104 |
|
remove_connection(FromTo, Pid) -> |
105 |
41 |
case catch mnesia:dirty_match_object(s2s, #s2s{fromto = FromTo, |
106 |
|
pid = Pid}) of |
107 |
|
[#s2s{pid = Pid}] -> |
108 |
33 |
F = fun() -> |
109 |
33 |
mnesia:delete_object(#s2s{fromto = FromTo, |
110 |
|
pid = Pid}) |
111 |
|
end, |
112 |
33 |
mnesia:transaction(F); |
113 |
|
_ -> |
114 |
8 |
ok |
115 |
|
end. |
116 |
|
|
117 |
|
have_connection(FromTo) -> |
118 |
:-( |
case catch mnesia:dirty_read(s2s, FromTo) of |
119 |
|
[_] -> |
120 |
:-( |
true; |
121 |
|
_ -> |
122 |
:-( |
false |
123 |
|
end. |
124 |
|
|
125 |
|
-spec get_connections_pids(_) -> ['undefined' | pid()]. |
126 |
|
get_connections_pids(FromTo) -> |
127 |
28 |
case catch mnesia:dirty_read(s2s, FromTo) of |
128 |
|
L when is_list(L) -> |
129 |
28 |
[Connection#s2s.pid || Connection <- L]; |
130 |
|
_ -> |
131 |
:-( |
[] |
132 |
|
end. |
133 |
|
|
134 |
|
-spec try_register(fromto()) -> boolean(). |
135 |
|
try_register(FromTo) -> |
136 |
27 |
MaxS2SConnectionsNumber = max_s2s_connections_number(FromTo), |
137 |
27 |
MaxS2SConnectionsNumberPerNode = |
138 |
|
max_s2s_connections_number_per_node(FromTo), |
139 |
27 |
F = fun() -> |
140 |
28 |
L = mnesia:read({s2s, FromTo}), |
141 |
27 |
NeededConnections = needed_connections_number( |
142 |
|
L, MaxS2SConnectionsNumber, |
143 |
|
MaxS2SConnectionsNumberPerNode), |
144 |
27 |
case NeededConnections > 0 of |
145 |
|
true -> |
146 |
13 |
mnesia:write(#s2s{fromto = FromTo, |
147 |
|
pid = self()}), |
148 |
13 |
true; |
149 |
|
false -> |
150 |
14 |
false |
151 |
|
end |
152 |
|
end, |
153 |
27 |
case mnesia:transaction(F) of |
154 |
|
{atomic, Res} -> |
155 |
27 |
Res; |
156 |
|
_ -> |
157 |
:-( |
false |
158 |
|
end. |
159 |
|
|
160 |
|
dirty_get_connections() -> |
161 |
:-( |
mnesia:dirty_all_keys(s2s). |
162 |
|
|
163 |
|
%%==================================================================== |
164 |
|
%% Hooks callbacks |
165 |
|
%%==================================================================== |
166 |
|
|
167 |
|
node_cleanup(Acc, Node) -> |
168 |
:-( |
F = fun() -> |
169 |
:-( |
Es = mnesia:select( |
170 |
|
s2s, |
171 |
|
[{#s2s{pid = '$1', _ = '_'}, |
172 |
|
[{'==', {node, '$1'}, Node}], |
173 |
|
['$_']}]), |
174 |
:-( |
lists:foreach(fun(E) -> |
175 |
:-( |
mnesia:delete_object(E) |
176 |
|
end, Es) |
177 |
|
end, |
178 |
:-( |
Res = mnesia:async_dirty(F), |
179 |
:-( |
maps:put(?MODULE, Res, Acc). |
180 |
|
|
181 |
|
-spec key({jid:lserver(), jid:lserver()}, binary()) -> |
182 |
|
binary(). |
183 |
|
key({From, To}, StreamID) -> |
184 |
55 |
Secret = get_shared_secret(), |
185 |
55 |
SecretHashed = base16:encode(crypto:hash(sha256, Secret)), |
186 |
55 |
HMac = crypto:mac(hmac, sha256, SecretHashed, [From, " ", To, " ", StreamID]), |
187 |
55 |
base16:encode(HMac). |
188 |
|
|
189 |
|
%%==================================================================== |
190 |
|
%% gen_server callbacks |
191 |
|
%%==================================================================== |
192 |
|
|
193 |
|
%%-------------------------------------------------------------------- |
194 |
|
%% Function: init(Args) -> {ok, State} | |
195 |
|
%% {ok, State, Timeout} | |
196 |
|
%% ignore | |
197 |
|
%% {stop, Reason} |
198 |
|
%% Description: Initiates the server |
199 |
|
%%-------------------------------------------------------------------- |
200 |
|
init([]) -> |
201 |
80 |
mnesia:create_table(s2s, [{ram_copies, [node()]}, {type, bag}, |
202 |
|
{attributes, record_info(fields, s2s)}]), |
203 |
80 |
mnesia:add_table_copy(s2s, node(), ram_copies), |
204 |
80 |
mnesia:create_table(s2s_shared, [{ram_copies, [node()]}, |
205 |
|
{attributes, record_info(fields, s2s_shared)}]), |
206 |
80 |
mnesia:add_table_copy(s2s_shared, node(), ram_copies), |
207 |
80 |
{atomic, ok} = set_shared_secret(), |
208 |
80 |
ejabberd_commands:register_commands(commands()), |
209 |
80 |
ejabberd_hooks:add(node_cleanup, global, ?MODULE, node_cleanup, 50), |
210 |
80 |
{ok, #state{}}. |
211 |
|
|
212 |
|
%%-------------------------------------------------------------------- |
213 |
|
%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} | |
214 |
|
%% {reply, Reply, State, Timeout} | |
215 |
|
%% {noreply, State} | |
216 |
|
%% {noreply, State, Timeout} | |
217 |
|
%% {stop, Reason, Reply, State} | |
218 |
|
%% {stop, Reason, State} |
219 |
|
%% Description: Handling call messages |
220 |
|
%%-------------------------------------------------------------------- |
221 |
|
handle_call(Request, From, State) -> |
222 |
:-( |
?UNEXPECTED_CALL(Request, From), |
223 |
:-( |
{reply, {error, unexpected_call}, State}. |
224 |
|
|
225 |
|
%%-------------------------------------------------------------------- |
226 |
|
%% Function: handle_cast(Msg, State) -> {noreply, State} | |
227 |
|
%% {noreply, State, Timeout} | |
228 |
|
%% {stop, Reason, State} |
229 |
|
%% Description: Handling cast messages |
230 |
|
%%-------------------------------------------------------------------- |
231 |
|
handle_cast(Msg, State) -> |
232 |
:-( |
?UNEXPECTED_CAST(Msg), |
233 |
:-( |
{noreply, State}. |
234 |
|
|
235 |
|
%%-------------------------------------------------------------------- |
236 |
|
%% Function: handle_info(Info, State) -> {noreply, State} | |
237 |
|
%% {noreply, State, Timeout} | |
238 |
|
%% {stop, Reason, State} |
239 |
|
%% Description: Handling all non call/cast messages |
240 |
|
%%-------------------------------------------------------------------- |
241 |
|
|
242 |
|
handle_info(Msg, State) -> |
243 |
:-( |
?UNEXPECTED_INFO(Msg), |
244 |
:-( |
{noreply, State}. |
245 |
|
|
246 |
|
%%-------------------------------------------------------------------- |
247 |
|
%% Function: terminate(Reason, State) -> void() |
248 |
|
%% Description: This function is called by a gen_server when it is about to |
249 |
|
%% terminate. It should be the opposite of Module:init/1 and do any necessary |
250 |
|
%% cleaning up. When it returns, the gen_server terminates with Reason. |
251 |
|
%% The return value is ignored. |
252 |
|
%%-------------------------------------------------------------------- |
253 |
|
terminate(_Reason, _State) -> |
254 |
:-( |
ejabberd_hooks:delete(node_cleanup, global, ?MODULE, node_cleanup, 50), |
255 |
:-( |
ejabberd_commands:unregister_commands(commands()), |
256 |
:-( |
ok. |
257 |
|
|
258 |
|
%%-------------------------------------------------------------------- |
259 |
|
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState} |
260 |
|
%% Description: Convert process state when code is changed |
261 |
|
%%-------------------------------------------------------------------- |
262 |
|
code_change(_OldVsn, State, _Extra) -> |
263 |
:-( |
{ok, State}. |
264 |
|
|
265 |
|
%%-------------------------------------------------------------------- |
266 |
|
%%% Internal functions |
267 |
|
%%-------------------------------------------------------------------- |
268 |
|
|
269 |
|
-spec do_route(From :: jid:jid(), |
270 |
|
To :: jid:jid(), |
271 |
|
Acc :: mongoose_acc:t(), |
272 |
|
Packet :: exml:element()) -> |
273 |
|
{done, mongoose_acc:t()}. % this is the 'last resort' router, it always returns 'done'. |
274 |
|
do_route(From, To, Acc, Packet) -> |
275 |
96 |
?LOG_DEBUG(#{what => s2s_route, acc => Acc}), |
276 |
96 |
case find_connection(From, To) of |
277 |
|
{atomic, Pid} when is_pid(Pid) -> |
278 |
91 |
?LOG_DEBUG(#{what => s2s_found_connection, |
279 |
|
text => <<"Send packet to s2s connection">>, |
280 |
91 |
s2s_pid => Pid, acc => Acc}), |
281 |
91 |
#xmlel{attrs = Attrs} = Packet, |
282 |
91 |
NewAttrs = jlib:replace_from_to_attrs(jid:to_binary(From), |
283 |
|
jid:to_binary(To), |
284 |
|
Attrs), |
285 |
91 |
NewPacket = Packet#xmlel{attrs = NewAttrs}, |
286 |
91 |
Acc1 = mongoose_hooks:s2s_send_packet(Acc, From, To, Packet), |
287 |
91 |
send_element(Pid, Acc1, NewPacket), |
288 |
91 |
{done, Acc1}; |
289 |
|
{aborted, _Reason} -> |
290 |
3 |
case mongoose_acc:stanza_type(Acc) of |
291 |
|
<<"error">> -> |
292 |
:-( |
{done, Acc}; |
293 |
|
<<"result">> -> |
294 |
:-( |
{done, Acc}; |
295 |
|
_ -> |
296 |
3 |
?LOG_DEBUG(#{what => s2s_connection_not_found, acc => Acc}), |
297 |
3 |
{Acc1, Err} = jlib:make_error_reply( |
298 |
|
Acc, Packet, mongoose_xmpp_errors:service_unavailable()), |
299 |
3 |
Acc2 = ejabberd_router:route(To, From, Acc1, Err), |
300 |
3 |
{done, Acc2} |
301 |
|
end |
302 |
|
end. |
303 |
|
|
304 |
|
-spec find_connection(From :: jid:jid(), |
305 |
|
To :: jid:jid()) -> {'aborted', _} | {'atomic', _}. |
306 |
|
find_connection(From, To) -> |
307 |
97 |
#jid{lserver = MyServer} = From, |
308 |
97 |
#jid{lserver = Server} = To, |
309 |
97 |
FromTo = {MyServer, Server}, |
310 |
97 |
MaxS2SConnectionsNumber = max_s2s_connections_number(FromTo), |
311 |
95 |
MaxS2SConnectionsNumberPerNode = |
312 |
|
max_s2s_connections_number_per_node(FromTo), |
313 |
95 |
?LOG_DEBUG(#{what => s2s_find_connection, from_server => MyServer, to_server => Server}), |
314 |
95 |
case catch mnesia:dirty_read(s2s, FromTo) of |
315 |
|
{'EXIT', Reason} -> |
316 |
:-( |
{aborted, Reason}; |
317 |
|
[] -> |
318 |
|
%% We try to establish all the connections if the host is not a |
319 |
|
%% service and if the s2s host is not blacklisted or |
320 |
|
%% is in whitelist: |
321 |
33 |
maybe_open_several_connections(From, To, MyServer, Server, FromTo, |
322 |
|
MaxS2SConnectionsNumber, |
323 |
|
MaxS2SConnectionsNumberPerNode); |
324 |
|
L when is_list(L) -> |
325 |
62 |
maybe_open_missing_connections(From, MyServer, Server, FromTo, |
326 |
|
MaxS2SConnectionsNumber, |
327 |
|
MaxS2SConnectionsNumberPerNode, L) |
328 |
|
end. |
329 |
|
|
330 |
|
maybe_open_missing_connections(From, MyServer, Server, FromTo, |
331 |
|
MaxS2SConnectionsNumber, |
332 |
|
MaxS2SConnectionsNumberPerNode, L) -> |
333 |
62 |
NeededConnections = needed_connections_number( |
334 |
|
L, MaxS2SConnectionsNumber, |
335 |
|
MaxS2SConnectionsNumberPerNode), |
336 |
62 |
case NeededConnections > 0 of |
337 |
|
true -> |
338 |
|
%% We establish the missing connections for this pair. |
339 |
:-( |
open_several_connections( |
340 |
|
NeededConnections, MyServer, |
341 |
|
Server, From, FromTo, |
342 |
|
MaxS2SConnectionsNumber, MaxS2SConnectionsNumberPerNode); |
343 |
|
false -> |
344 |
|
%% We choose a connexion from the pool of opened ones. |
345 |
62 |
{atomic, choose_connection(From, L)} |
346 |
|
end. |
347 |
|
|
348 |
|
maybe_open_several_connections(From, To, MyServer, Server, FromTo, |
349 |
|
MaxS2SConnectionsNumber, |
350 |
|
MaxS2SConnectionsNumberPerNode) -> |
351 |
|
%% We try to establish all the connections if the host is not a |
352 |
|
%% service and if the s2s host is not blacklisted or |
353 |
|
%% is in whitelist: |
354 |
33 |
case not is_service(From, To) andalso allow_host(MyServer, Server) of |
355 |
|
true -> |
356 |
30 |
NeededConnections = needed_connections_number( |
357 |
|
[], MaxS2SConnectionsNumber, |
358 |
|
MaxS2SConnectionsNumberPerNode), |
359 |
30 |
open_several_connections( |
360 |
|
NeededConnections, MyServer, |
361 |
|
Server, From, FromTo, |
362 |
|
MaxS2SConnectionsNumber, MaxS2SConnectionsNumberPerNode); |
363 |
|
false -> |
364 |
3 |
{aborted, error} |
365 |
|
end. |
366 |
|
|
367 |
|
-spec choose_connection(From :: jid:jid(), |
368 |
|
Connections :: [s2s()]) -> any(). |
369 |
|
choose_connection(From, Connections) -> |
370 |
70 |
choose_pid(From, [C#s2s.pid || C <- Connections]). |
371 |
|
|
372 |
|
-spec choose_pid(From :: jid:jid(), Pids :: [pid()]) -> pid(). |
373 |
|
choose_pid(From, Pids) -> |
374 |
100 |
Pids1 = case [P || P <- Pids, node(P) == node()] of |
375 |
:-( |
[] -> Pids; |
376 |
100 |
Ps -> Ps |
377 |
|
end, |
378 |
|
% Use sticky connections based on the JID of the sender |
379 |
|
% (without the resource to ensure that a muc room always uses the same connection) |
380 |
100 |
Pid = lists:nth(erlang:phash(jid:to_bare(From), length(Pids1)), Pids1), |
381 |
100 |
?LOG_DEBUG(#{what => s2s_choose_pid, from => From, s2s_pid => Pid}), |
382 |
100 |
Pid. |
383 |
|
|
384 |
|
-spec open_several_connections(N :: pos_integer(), MyServer :: jid:server(), |
385 |
|
Server :: jid:server(), From :: jid:jid(), FromTo :: fromto(), |
386 |
|
MaxS2S :: pos_integer(), MaxS2SPerNode :: pos_integer()) |
387 |
|
-> {'aborted', _} | {'atomic', _}. |
388 |
|
open_several_connections(N, MyServer, Server, From, FromTo, |
389 |
|
MaxS2SConnectionsNumber, |
390 |
|
MaxS2SConnectionsNumberPerNode) -> |
391 |
30 |
ConnectionsResult = |
392 |
30 |
[new_connection(MyServer, Server, From, FromTo, |
393 |
|
MaxS2SConnectionsNumber, MaxS2SConnectionsNumberPerNode) |
394 |
30 |
|| _N <- lists:seq(1, N)], |
395 |
30 |
case [PID || {atomic, PID} <- ConnectionsResult] of |
396 |
|
[] -> |
397 |
:-( |
hd(ConnectionsResult); |
398 |
|
PIDs -> |
399 |
30 |
{atomic, choose_pid(From, PIDs)} |
400 |
|
end. |
401 |
|
|
402 |
|
-spec new_connection(MyServer :: jid:server(), Server :: jid:server(), |
403 |
|
From :: jid:jid(), FromTo :: fromto(), MaxS2S :: pos_integer(), |
404 |
|
MaxS2SPerNode :: pos_integer()) -> {'aborted', _} | {'atomic', _}. |
405 |
|
new_connection(MyServer, Server, From, FromTo = {FromServer, ToServer}, |
406 |
|
MaxS2SConnectionsNumber, MaxS2SConnectionsNumberPerNode) -> |
407 |
30 |
{ok, Pid} = ejabberd_s2s_out:start( |
408 |
|
MyServer, Server, new), |
409 |
30 |
F = fun() -> |
410 |
35 |
L = mnesia:read({s2s, FromTo}), |
411 |
31 |
NeededConnections = needed_connections_number( |
412 |
|
L, MaxS2SConnectionsNumber, |
413 |
|
MaxS2SConnectionsNumberPerNode), |
414 |
31 |
case NeededConnections > 0 of |
415 |
|
true -> |
416 |
23 |
mnesia:write(#s2s{fromto = FromTo, |
417 |
|
pid = Pid}), |
418 |
22 |
?LOG_INFO(#{what => s2s_new_connection, |
419 |
|
text => <<"New s2s connection started">>, |
420 |
|
from_server => FromServer, |
421 |
|
to_server => ToServer, |
422 |
22 |
s2s_pid => Pid}), |
423 |
22 |
Pid; |
424 |
|
false -> |
425 |
8 |
choose_connection(From, L) |
426 |
|
end |
427 |
|
end, |
428 |
30 |
TRes = mnesia:transaction(F), |
429 |
30 |
case TRes of |
430 |
|
{atomic, Pid} -> |
431 |
22 |
ejabberd_s2s_out:start_connection(Pid); |
432 |
|
_ -> |
433 |
8 |
ejabberd_s2s_out:stop_connection(Pid) |
434 |
|
end, |
435 |
30 |
TRes. |
436 |
|
|
437 |
|
-spec max_s2s_connections_number(fromto()) -> pos_integer(). |
438 |
|
max_s2s_connections_number({From, To}) -> |
439 |
124 |
{ok, HostType} = mongoose_domain_api:get_host_type(From), |
440 |
122 |
case acl:match_rule(HostType, max_s2s_connections, jid:make(<<"">>, To, <<"">>)) of |
441 |
:-( |
Max when is_integer(Max) -> Max; |
442 |
122 |
_ -> ?DEFAULT_MAX_S2S_CONNECTIONS_NUMBER |
443 |
|
end. |
444 |
|
|
445 |
|
-spec max_s2s_connections_number_per_node(fromto()) -> pos_integer(). |
446 |
|
max_s2s_connections_number_per_node({From, To}) -> |
447 |
122 |
{ok, HostType} = mongoose_domain_api:get_host_type(From), |
448 |
122 |
case acl:match_rule(HostType, max_s2s_connections_per_node, jid:make(<<"">>, To, <<"">>)) of |
449 |
:-( |
Max when is_integer(Max) -> Max; |
450 |
122 |
_ -> ?DEFAULT_MAX_S2S_CONNECTIONS_NUMBER_PER_NODE |
451 |
|
end. |
452 |
|
|
453 |
|
-spec needed_connections_number([any()], pos_integer(), pos_integer()) -> integer(). |
454 |
|
needed_connections_number(Ls, MaxS2SConnectionsNumber, |
455 |
|
MaxS2SConnectionsNumberPerNode) -> |
456 |
150 |
LocalLs = [L || L <- Ls, node(L#s2s.pid) == node()], |
457 |
150 |
lists:min([MaxS2SConnectionsNumber - length(Ls), |
458 |
|
MaxS2SConnectionsNumberPerNode - length(LocalLs)]). |
459 |
|
|
460 |
|
%%-------------------------------------------------------------------- |
461 |
|
%% Function: is_service(From, To) -> true | false |
462 |
|
%% Description: Return true if the destination must be considered as a |
463 |
|
%% service. |
464 |
|
%% -------------------------------------------------------------------- |
465 |
|
-spec is_service(jid:jid(), jid:jid()) -> boolean(). |
466 |
|
is_service(From, To) -> |
467 |
33 |
LFromDomain = From#jid.lserver, |
468 |
33 |
case mongoose_config:lookup_opt({route_subdomains, LFromDomain}) of |
469 |
|
{ok, s2s} -> % bypass RFC 3920 10.3 |
470 |
:-( |
false; |
471 |
|
{error, not_found} -> |
472 |
33 |
Hosts = ?MYHOSTS, |
473 |
33 |
P = fun(ParentDomain) -> lists:member(ParentDomain, Hosts) end, |
474 |
33 |
lists:any(P, parent_domains(To#jid.lserver)) |
475 |
|
end. |
476 |
|
|
477 |
|
-spec parent_domains(binary()) -> [binary(), ...]. |
478 |
|
parent_domains(Domain) -> |
479 |
91 |
parent_domains(Domain, [Domain]). |
480 |
|
|
481 |
|
-spec parent_domains(binary(), [binary(), ...]) -> [binary(), ...]. |
482 |
|
parent_domains(<<>>, Acc) -> |
483 |
91 |
lists:reverse(Acc); |
484 |
|
parent_domains(<<$., Rest/binary>>, Acc) -> |
485 |
26 |
parent_domains(Rest, [Rest | Acc]); |
486 |
|
parent_domains(<<_, Rest/binary>>, Acc) -> |
487 |
766 |
parent_domains(Rest, Acc). |
488 |
|
|
489 |
|
-spec send_element(pid(), mongoose_acc:t(), exml:element()) -> |
490 |
|
{'send_element', mongoose_acc:t(), exml:element()}. |
491 |
|
send_element(Pid, Acc, El) -> |
492 |
91 |
Pid ! {send_element, Acc, El}. |
493 |
|
|
494 |
|
timeout() -> |
495 |
285 |
600000. |
496 |
|
%%-------------------------------------------------------------------- |
497 |
|
%% Function: domain_utf8_to_ascii(Domain) -> binary() | false |
498 |
|
%% Description: Converts a UTF-8 domain to ASCII (IDNA) |
499 |
|
%% -------------------------------------------------------------------- |
500 |
|
-spec domain_utf8_to_ascii(binary() | string()) -> binary() | false. |
501 |
|
domain_utf8_to_ascii(Domain) -> |
502 |
10 |
case catch idna:utf8_to_ascii(Domain) of |
503 |
|
{'EXIT', _} -> |
504 |
2 |
false; |
505 |
|
AsciiDomain -> |
506 |
8 |
list_to_binary(AsciiDomain) |
507 |
|
end. |
508 |
|
|
509 |
|
%%%---------------------------------------------------------------------- |
510 |
|
%%% ejabberd commands |
511 |
|
|
512 |
|
-spec commands() -> [ejabberd_commands:cmd(), ...]. |
513 |
|
commands() -> |
514 |
80 |
[ |
515 |
|
#ejabberd_commands{name = incoming_s2s_number, |
516 |
|
tags = [stats, s2s], |
517 |
|
desc = "Number of incoming s2s connections on the node", |
518 |
|
module = ?MODULE, function = incoming_s2s_number, |
519 |
|
args = [], |
520 |
|
result = {s2s_incoming, integer}}, |
521 |
|
#ejabberd_commands{name = outgoing_s2s_number, |
522 |
|
tags = [stats, s2s], |
523 |
|
desc = "Number of outgoing s2s connections on the node", |
524 |
|
module = ?MODULE, function = outgoing_s2s_number, |
525 |
|
args = [], |
526 |
|
result = {s2s_outgoing, integer}} |
527 |
|
]. |
528 |
|
|
529 |
|
-spec incoming_s2s_number() -> non_neg_integer(). |
530 |
|
incoming_s2s_number() -> |
531 |
:-( |
length(supervisor:which_children(ejabberd_s2s_in_sup)). |
532 |
|
|
533 |
|
-spec outgoing_s2s_number() -> non_neg_integer(). |
534 |
|
outgoing_s2s_number() -> |
535 |
:-( |
length(supervisor:which_children(ejabberd_s2s_out_sup)). |
536 |
|
|
537 |
|
|
538 |
|
%% Check if host is in blacklist or white list |
539 |
|
allow_host(MyServer, S2SHost) -> |
540 |
58 |
Hosts = ?MYHOSTS, |
541 |
58 |
case lists:dropwhile( |
542 |
|
fun(ParentDomain) -> |
543 |
64 |
not lists:member(ParentDomain, Hosts) |
544 |
|
end, parent_domains(MyServer)) of |
545 |
|
[MyHost|_] -> |
546 |
55 |
allow_host1(MyHost, S2SHost); |
547 |
|
[] -> |
548 |
3 |
allow_host1(MyServer, S2SHost) |
549 |
|
end. |
550 |
|
|
551 |
|
allow_host1(MyHost, S2SHost) -> |
552 |
58 |
case mongoose_config:lookup_opt([{s2s_host_policy, MyHost}, S2SHost]) of |
553 |
|
{ok, deny} -> |
554 |
:-( |
false; |
555 |
|
{ok, allow} -> |
556 |
:-( |
true; |
557 |
|
{error, not_found} -> |
558 |
58 |
case mongoose_config:lookup_opt({s2s_default_policy, MyHost}) of |
559 |
|
{ok, deny} -> |
560 |
:-( |
false; |
561 |
|
_ -> |
562 |
58 |
mongoose_hooks:s2s_allow_host(MyHost, S2SHost) /= deny |
563 |
|
end |
564 |
|
end. |
565 |
|
|
566 |
|
%% @doc Get information about S2S connections of the specified type. |
567 |
|
-spec get_info_s2s_connections('in' | 'out') -> [[{atom(), any()}, ...]]. |
568 |
|
get_info_s2s_connections(Type) -> |
569 |
2 |
ChildType = case Type of |
570 |
1 |
in -> ejabberd_s2s_in_sup; |
571 |
1 |
out -> ejabberd_s2s_out_sup |
572 |
|
end, |
573 |
2 |
Connections = supervisor:which_children(ChildType), |
574 |
2 |
get_s2s_info(Connections, Type). |
575 |
|
|
576 |
|
-type connstate() :: 'restarting' | 'undefined' | pid(). |
577 |
|
-type conn() :: { any(), connstate(), 'supervisor' | 'worker', 'dynamic' | [_] }. |
578 |
|
-spec get_s2s_info(Connections :: [conn()], |
579 |
|
Type :: 'in' | 'out' |
580 |
|
) -> [[{any(), any()}, ...]]. % list of lists |
581 |
|
get_s2s_info(Connections, Type)-> |
582 |
2 |
complete_s2s_info(Connections, Type, []). |
583 |
|
|
584 |
|
-spec complete_s2s_info(Connections :: [conn()], |
585 |
|
Type :: 'in' | 'out', |
586 |
|
Result :: [[{any(), any()}, ...]] % list of lists |
587 |
|
) -> [[{any(), any()}, ...]]. % list of lists |
588 |
|
complete_s2s_info([], _, Result)-> |
589 |
2 |
Result; |
590 |
|
complete_s2s_info([Connection|T], Type, Result)-> |
591 |
3 |
{_, PID, _, _}=Connection, |
592 |
3 |
State = get_s2s_state(PID), |
593 |
3 |
complete_s2s_info(T, Type, [State|Result]). |
594 |
|
|
595 |
|
-spec get_s2s_state(connstate()) -> [{atom(), any()}, ...]. |
596 |
|
get_s2s_state(S2sPid)-> |
597 |
3 |
Infos = case gen_fsm_compat:sync_send_all_state_event(S2sPid, get_state_infos) of |
598 |
3 |
{state_infos, Is} -> [{status, open} | Is]; |
599 |
:-( |
{noproc, _} -> [{status, closed}]; %% Connection closed |
600 |
:-( |
{badrpc, _} -> [{status, error}] |
601 |
|
end, |
602 |
3 |
[{s2s_pid, S2sPid} | Infos]. |
603 |
|
|
604 |
|
-spec get_shared_secret() -> binary(). |
605 |
|
get_shared_secret() -> |
606 |
|
%% Currently there is only one key, in the future there might be one key per host type |
607 |
55 |
[#s2s_shared{secret = Secret}] = ets:lookup(s2s_shared, global), |
608 |
55 |
Secret. |
609 |
|
|
610 |
|
-spec set_shared_secret() -> mnesia:t_result(ok). |
611 |
|
set_shared_secret() -> |
612 |
80 |
Secret = case mongoose_config:lookup_opt(s2s_shared) of |
613 |
|
{ok, SecretFromConfig} -> |
614 |
:-( |
SecretFromConfig; |
615 |
|
{error, not_found} -> |
616 |
80 |
base16:encode(crypto:strong_rand_bytes(10)) |
617 |
|
end, |
618 |
80 |
mnesia:transaction(fun() -> mnesia:write(#s2s_shared{secret = Secret}) end). |