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