24
1
mirror of https://github.com/processone/ejabberd.git synced 2024-06-10 21:47:01 +02:00
xmpp.chapril.org-ejabberd/src/ejabberd_s2s.erl
Evgeniy Khramtsov 7bcbea2108 Deprecate jlib.erl in favor of aux.erl
Since the main goal of jlib.erl is lost, all auxiliary functions
are now moved to aux.erl, and the whole jlib.erl is now deprecated.
2017-03-30 14:17:13 +03:00

756 lines
24 KiB
Erlang

%%%----------------------------------------------------------------------
%%% File : ejabberd_s2s.erl
%%% Author : Alexey Shchepin <alexey@process-one.net>
%%% Purpose : S2S connections manager
%%% Created : 7 Dec 2002 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2017 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
%%% published by the Free Software Foundation; either version 2 of the
%%% License, or (at your option) any later version.
%%%
%%% This program is distributed in the hope that it will be useful,
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%% You should have received a copy of the GNU General Public License along
%%% with this program; if not, write to the Free Software Foundation, Inc.,
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
%%%
%%%----------------------------------------------------------------------
-module(ejabberd_s2s).
-protocol({xep, 220, '1.1'}).
-behaviour(ejabberd_config).
-author('alexey@process-one.net').
-behaviour(gen_server).
%% API
-export([start_link/0, route/1, have_connection/1,
get_connections_pids/1, try_register/1,
remove_connection/2, start_connection/2, start_connection/3,
dirty_get_connections/0, allow_host/2,
incoming_s2s_number/0, outgoing_s2s_number/0,
stop_all_connections/0,
clean_temporarily_blocked_table/0,
list_temporarily_blocked_hosts/0,
external_host_overloaded/1, is_temporarly_blocked/1,
get_commands_spec/0, zlib_enabled/1, get_idle_timeout/1,
tls_required/1, tls_verify/1, tls_enabled/1, tls_options/2,
host_up/1, host_down/1, queue_type/1]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2,
handle_info/2, terminate/2, code_change/3]).
-export([get_info_s2s_connections/1,
transform_options/1, opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
-include("xmpp.hrl").
-include("ejabberd_commands.hrl").
-include_lib("public_key/include/public_key.hrl").
-define(PKIXEXPLICIT, 'OTP-PUB-KEY').
-define(PKIXIMPLICIT, 'OTP-PUB-KEY').
-include("XmppAddr.hrl").
-define(DEFAULT_MAX_S2S_CONNECTIONS_NUMBER, 1).
-define(DEFAULT_MAX_S2S_CONNECTIONS_NUMBER_PER_NODE, 1).
-define(S2S_OVERLOAD_BLOCK_PERIOD, 60).
%% once a server is temporarly blocked, it stay blocked for 60 seconds
-record(s2s, {fromto = {<<"">>, <<"">>} :: {binary(), binary()} | '_',
pid = self() :: pid() | '_' | '$1'}).
-record(state, {}).
-record(temporarily_blocked, {host = <<"">> :: binary(),
timestamp :: integer()}).
-type temporarily_blocked() :: #temporarily_blocked{}.
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [],
[]).
-spec route(stanza()) -> ok.
route(Packet) ->
try do_route(Packet)
catch E:R ->
?ERROR_MSG("failed to route packet:~n~s~nReason = ~p",
[xmpp:pp(Packet), {E, {R, erlang:get_stacktrace()}}])
end.
clean_temporarily_blocked_table() ->
mnesia:clear_table(temporarily_blocked).
-spec list_temporarily_blocked_hosts() -> [temporarily_blocked()].
list_temporarily_blocked_hosts() ->
ets:tab2list(temporarily_blocked).
-spec external_host_overloaded(binary()) -> {aborted, any()} | {atomic, ok}.
external_host_overloaded(Host) ->
?INFO_MSG("Disabling connections from ~s for ~p "
"seconds",
[Host, ?S2S_OVERLOAD_BLOCK_PERIOD]),
mnesia:transaction(fun () ->
Time = p1_time_compat:monotonic_time(),
mnesia:write(#temporarily_blocked{host = Host,
timestamp = Time})
end).
-spec is_temporarly_blocked(binary()) -> boolean().
is_temporarly_blocked(Host) ->
case mnesia:dirty_read(temporarily_blocked, Host) of
[] -> false;
[#temporarily_blocked{timestamp = T} = Entry] ->
Diff = p1_time_compat:monotonic_time() - T,
case p1_time_compat:convert_time_unit(Diff, native, micro_seconds) of
N when N > (?S2S_OVERLOAD_BLOCK_PERIOD) * 1000 * 1000 ->
mnesia:dirty_delete_object(Entry), false;
_ -> true
end
end.
-spec remove_connection({binary(), binary()},
pid()) -> {atomic, ok} | ok | {aborted, any()}.
remove_connection(FromTo, Pid) ->
case catch mnesia:dirty_match_object(s2s,
#s2s{fromto = FromTo, pid = Pid})
of
[#s2s{pid = Pid}] ->
F = fun () ->
mnesia:delete_object(#s2s{fromto = FromTo, pid = Pid})
end,
mnesia:transaction(F);
_ -> ok
end.
-spec have_connection({binary(), binary()}) -> boolean().
have_connection(FromTo) ->
case catch mnesia:dirty_read(s2s, FromTo) of
[_] ->
true;
_ ->
false
end.
-spec get_connections_pids({binary(), binary()}) -> [pid()].
get_connections_pids(FromTo) ->
case catch mnesia:dirty_read(s2s, FromTo) of
L when is_list(L) ->
[Connection#s2s.pid || Connection <- L];
_ ->
[]
end.
-spec try_register({binary(), binary()}) -> boolean().
try_register(FromTo) ->
MaxS2SConnectionsNumber = max_s2s_connections_number(FromTo),
MaxS2SConnectionsNumberPerNode =
max_s2s_connections_number_per_node(FromTo),
F = fun () ->
L = mnesia:read({s2s, FromTo}),
NeededConnections = needed_connections_number(L,
MaxS2SConnectionsNumber,
MaxS2SConnectionsNumberPerNode),
if NeededConnections > 0 ->
mnesia:write(#s2s{fromto = FromTo, pid = self()}),
true;
true -> false
end
end,
case mnesia:transaction(F) of
{atomic, Res} -> Res;
_ -> false
end.
-spec dirty_get_connections() -> [{binary(), binary()}].
dirty_get_connections() ->
mnesia:dirty_all_keys(s2s).
-spec tls_options(binary(), [proplists:property()]) -> [proplists:property()].
tls_options(LServer, DefaultOpts) ->
TLSOpts1 = case ejabberd_config:get_option(
{s2s_certfile, LServer},
fun iolist_to_binary/1,
ejabberd_config:get_option(
{domain_certfile, LServer},
fun iolist_to_binary/1)) of
undefined -> DefaultOpts;
CertFile -> lists:keystore(certfile, 1, DefaultOpts,
{certfile, CertFile})
end,
TLSOpts2 = case ejabberd_config:get_option(
{s2s_ciphers, LServer},
fun iolist_to_binary/1) of
undefined -> TLSOpts1;
Ciphers -> lists:keystore(ciphers, 1, TLSOpts1,
{ciphers, Ciphers})
end,
TLSOpts3 = case ejabberd_config:get_option(
{s2s_protocol_options, LServer},
fun (Options) -> str:join(Options, <<$|>>) end) of
undefined -> TLSOpts2;
ProtoOpts -> lists:keystore(protocol_options, 1, TLSOpts2,
{protocol_options, ProtoOpts})
end,
TLSOpts4 = case ejabberd_config:get_option(
{s2s_dhfile, LServer},
fun iolist_to_binary/1) of
undefined -> TLSOpts3;
DHFile -> lists:keystore(dhfile, 1, TLSOpts3,
{dhfile, DHFile})
end,
TLSOpts5 = case ejabberd_config:get_option(
{s2s_cafile, LServer},
fun iolist_to_binary/1) of
undefined -> TLSOpts4;
CAFile -> lists:keystore(cafile, 1, TLSOpts4,
{cafile, CAFile})
end,
case ejabberd_config:get_option(
{s2s_tls_compression, LServer},
fun(B) when is_boolean(B) -> B end) of
undefined -> TLSOpts5;
false -> [compression_none | TLSOpts5];
true -> lists:delete(compression_none, TLSOpts5)
end.
-spec tls_required(binary()) -> boolean().
tls_required(LServer) ->
TLS = use_starttls(LServer),
TLS == required orelse TLS == required_trusted.
-spec tls_verify(binary()) -> boolean().
tls_verify(LServer) ->
TLS = use_starttls(LServer),
TLS == required_trusted.
-spec tls_enabled(binary()) -> boolean().
tls_enabled(LServer) ->
TLS = use_starttls(LServer),
TLS /= false.
-spec zlib_enabled(binary()) -> boolean().
zlib_enabled(LServer) ->
ejabberd_config:get_option(
{s2s_zlib, LServer},
fun(B) when is_boolean(B) -> B end,
false).
-spec use_starttls(binary()) -> boolean() | optional | required | required_trusted.
use_starttls(LServer) ->
ejabberd_config:get_option(
{s2s_use_starttls, LServer},
fun(true) -> true;
(false) -> false;
(optional) -> optional;
(required) -> required;
(required_trusted) -> required_trusted
end, false).
-spec get_idle_timeout(binary()) -> non_neg_integer() | infinity.
get_idle_timeout(LServer) ->
ejabberd_config:get_option(
{s2s_timeout, LServer},
fun(I) when is_integer(I), I >= 0 -> timer:seconds(I);
(infinity) -> infinity
end, timer:minutes(10)).
-spec queue_type(binary()) -> ram | file.
queue_type(LServer) ->
case ejabberd_config:get_option(
{s2s_queue_type, LServer}, opt_type(s2s_queue_type)) of
undefined -> ejabberd_config:default_queue_type(LServer);
Type -> Type
end.
%%====================================================================
%% gen_server callbacks
%%====================================================================
init([]) ->
update_tables(),
ejabberd_mnesia:create(?MODULE, s2s,
[{ram_copies, [node()]},
{type, bag},
{attributes, record_info(fields, s2s)}]),
mnesia:add_table_copy(s2s, node(), ram_copies),
mnesia:subscribe(system),
ejabberd_commands:register_commands(get_commands_spec()),
ejabberd_mnesia:create(?MODULE, temporarily_blocked,
[{ram_copies, [node()]},
{attributes, record_info(fields, temporarily_blocked)}]),
ejabberd_hooks:add(host_up, ?MODULE, host_up, 50),
ejabberd_hooks:add(host_down, ?MODULE, host_down, 60),
lists:foreach(fun host_up/1, ?MYHOSTS),
{ok, #state{}}.
handle_call(_Request, _From, State) ->
{reply, ok, State}.
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info({mnesia_system_event, {mnesia_down, Node}}, State) ->
clean_table_from_bad_node(Node),
{noreply, State};
handle_info({route, Packet}, State) ->
route(Packet),
{noreply, State};
handle_info(_Info, State) -> {noreply, State}.
terminate(_Reason, _State) ->
ejabberd_commands:unregister_commands(get_commands_spec()),
lists:foreach(fun host_down/1, ?MYHOSTS),
ejabberd_hooks:delete(host_up, ?MODULE, host_up, 50),
ejabberd_hooks:delete(host_down, ?MODULE, host_down, 60),
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
host_up(Host) ->
ejabberd_s2s_in:host_up(Host),
ejabberd_s2s_out:host_up(Host).
host_down(Host) ->
lists:foreach(
fun(#s2s{fromto = {From, _}, pid = Pid}) when node(Pid) == node() ->
case ejabberd_router:host_of_route(From) of
Host ->
ejabberd_s2s_out:send(Pid, xmpp:serr_system_shutdown()),
ejabberd_s2s_out:stop(Pid);
_ ->
ok
end;
(_) ->
ok
end, ets:tab2list(s2s)),
ejabberd_s2s_in:host_down(Host),
ejabberd_s2s_out:host_down(Host).
-spec clean_table_from_bad_node(node()) -> any().
clean_table_from_bad_node(Node) ->
F = fun() ->
Es = mnesia:select(
s2s,
[{#s2s{pid = '$1', _ = '_'},
[{'==', {node, '$1'}, Node}],
['$_']}]),
lists:foreach(fun(E) ->
mnesia:delete_object(E)
end, Es)
end,
mnesia:async_dirty(F).
-spec do_route(stanza()) -> ok.
do_route(Packet) ->
?DEBUG("local route:~n~s", [xmpp:pp(Packet)]),
From = xmpp:get_from(Packet),
To = xmpp:get_to(Packet),
case start_connection(From, To) of
{ok, Pid} when is_pid(Pid) ->
?DEBUG("sending to process ~p~n", [Pid]),
#jid{lserver = MyServer} = From,
ejabberd_hooks:run(s2s_send_packet, MyServer, [Packet]),
ejabberd_s2s_out:route(Pid, Packet);
{error, Reason} ->
Lang = xmpp:get_lang(Packet),
Err = case Reason of
policy_violation ->
xmpp:err_policy_violation(
<<"Server connections to local "
"subdomains are forbidden">>, Lang);
forbidden ->
xmpp:err_forbidden(<<"Denied by ACL">>, Lang);
internal_server_error ->
xmpp:err_internal_server_error()
end,
ejabberd_router:route_error(Packet, Err)
end.
-spec start_connection(jid(), jid())
-> {ok, pid()} | {error, policy_violation | forbidden | internal_server_error}.
start_connection(From, To) ->
start_connection(From, To, []).
-spec start_connection(jid(), jid(), [proplists:property()])
-> {ok, pid()} | {error, policy_violation | forbidden | internal_server_error}.
start_connection(From, To, Opts) ->
#jid{lserver = MyServer} = From,
#jid{lserver = Server} = To,
FromTo = {MyServer, Server},
MaxS2SConnectionsNumber =
max_s2s_connections_number(FromTo),
MaxS2SConnectionsNumberPerNode =
max_s2s_connections_number_per_node(FromTo),
?DEBUG("Finding connection for ~p~n", [FromTo]),
case mnesia:dirty_read(s2s, FromTo) of
[] ->
%% We try to establish all the connections if the host is not a
%% service and if the s2s host is not blacklisted or
%% is in whitelist:
LServer = ejabberd_router:host_of_route(MyServer),
case is_service(From, To) of
true ->
{error, policy_violation};
false ->
case allow_host(LServer, Server) of
true ->
NeededConnections = needed_connections_number(
[],
MaxS2SConnectionsNumber,
MaxS2SConnectionsNumberPerNode),
open_several_connections(NeededConnections, MyServer,
Server, From, FromTo,
MaxS2SConnectionsNumber,
MaxS2SConnectionsNumberPerNode, Opts);
false ->
{error, forbidden}
end
end;
L when is_list(L) ->
NeededConnections = needed_connections_number(L,
MaxS2SConnectionsNumber,
MaxS2SConnectionsNumberPerNode),
if NeededConnections > 0 ->
%% We establish the missing connections for this pair.
open_several_connections(NeededConnections, MyServer,
Server, From, FromTo,
MaxS2SConnectionsNumber,
MaxS2SConnectionsNumberPerNode, Opts);
true ->
%% We choose a connexion from the pool of opened ones.
{ok, choose_connection(From, L)}
end
end.
-spec choose_connection(jid(), [#s2s{}]) -> pid().
choose_connection(From, Connections) ->
choose_pid(From, [C#s2s.pid || C <- Connections]).
-spec choose_pid(jid(), [pid()]) -> pid().
choose_pid(From, Pids) ->
Pids1 = case [P || P <- Pids, node(P) == node()] of
[] -> Pids;
Ps -> Ps
end,
Pid =
lists:nth(erlang:phash(jid:remove_resource(From),
length(Pids1)),
Pids1),
?DEBUG("Using ejabberd_s2s_out ~p~n", [Pid]),
Pid.
open_several_connections(N, MyServer, Server, From,
FromTo, MaxS2SConnectionsNumber,
MaxS2SConnectionsNumberPerNode, Opts) ->
case lists:flatmap(
fun(_) ->
new_connection(MyServer, Server,
From, FromTo, MaxS2SConnectionsNumber,
MaxS2SConnectionsNumberPerNode, Opts)
end, lists:seq(1, N)) of
[] ->
{error, internal_server_error};
PIDs ->
{ok, choose_pid(From, PIDs)}
end.
new_connection(MyServer, Server, From, FromTo,
MaxS2SConnectionsNumber, MaxS2SConnectionsNumberPerNode, Opts) ->
{ok, Pid} = ejabberd_s2s_out:start(MyServer, Server, Opts),
F = fun() ->
L = mnesia:read({s2s, FromTo}),
NeededConnections = needed_connections_number(L,
MaxS2SConnectionsNumber,
MaxS2SConnectionsNumberPerNode),
if NeededConnections > 0 ->
mnesia:write(#s2s{fromto = FromTo, pid = Pid}),
Pid;
true -> choose_connection(From, L)
end
end,
TRes = mnesia:transaction(F),
case TRes of
{atomic, Pid} ->
ejabberd_s2s_out:connect(Pid),
[Pid];
{aborted, Reason} ->
?ERROR_MSG("failed to register connection ~s -> ~s: ~p",
[MyServer, Server, Reason]),
ejabberd_s2s_out:stop(Pid),
[]
end.
-spec max_s2s_connections_number({binary(), binary()}) -> integer().
max_s2s_connections_number({From, To}) ->
case acl:match_rule(From, max_s2s_connections, jid:make(To)) of
Max when is_integer(Max) -> Max;
_ -> ?DEFAULT_MAX_S2S_CONNECTIONS_NUMBER
end.
-spec max_s2s_connections_number_per_node({binary(), binary()}) -> integer().
max_s2s_connections_number_per_node({From, To}) ->
case acl:match_rule(From, max_s2s_connections_per_node, jid:make(To)) of
Max when is_integer(Max) -> Max;
_ -> ?DEFAULT_MAX_S2S_CONNECTIONS_NUMBER_PER_NODE
end.
-spec needed_connections_number([#s2s{}], integer(), integer()) -> integer().
needed_connections_number(Ls, MaxS2SConnectionsNumber,
MaxS2SConnectionsNumberPerNode) ->
LocalLs = [L || L <- Ls, node(L#s2s.pid) == node()],
lists:min([MaxS2SConnectionsNumber - length(Ls),
MaxS2SConnectionsNumberPerNode - length(LocalLs)]).
%%--------------------------------------------------------------------
%% Function: is_service(From, To) -> true | false
%% Description: Return true if the destination must be considered as a
%% service.
%% --------------------------------------------------------------------
-spec is_service(jid(), jid()) -> boolean().
is_service(From, To) ->
LFromDomain = From#jid.lserver,
case ejabberd_config:get_option(
{route_subdomains, LFromDomain},
fun(s2s) -> s2s; (local) -> local end, local) of
s2s -> % bypass RFC 3920 10.3
false;
local ->
Hosts = (?MYHOSTS),
P = fun (ParentDomain) ->
lists:member(ParentDomain, Hosts)
end,
lists:any(P, parent_domains(To#jid.lserver))
end.
parent_domains(Domain) ->
lists:foldl(fun (Label, []) -> [Label];
(Label, [Head | Tail]) ->
[<<Label/binary, ".", Head/binary>>, Head | Tail]
end,
[], lists:reverse(str:tokens(Domain, <<".">>))).
%%%----------------------------------------------------------------------
%%% ejabberd commands
get_commands_spec() ->
[#ejabberd_commands{
name = incoming_s2s_number,
tags = [stats, s2s],
desc = "Number of incoming s2s connections on the node",
policy = admin,
module = ?MODULE, function = incoming_s2s_number,
args = [], result = {s2s_incoming, integer}},
#ejabberd_commands{
name = outgoing_s2s_number,
tags = [stats, s2s],
desc = "Number of outgoing s2s connections on the node",
policy = admin,
module = ?MODULE, function = outgoing_s2s_number,
args = [], result = {s2s_outgoing, integer}},
#ejabberd_commands{name = stop_all_connections,
tags = [s2s],
desc = "Stop all outgoing and incoming connections",
policy = admin,
module = ?MODULE, function = stop_all_connections,
args = [], result = {res, rescode}}].
%% TODO Move those stats commands to ejabberd stats command ?
incoming_s2s_number() ->
supervisor_count(ejabberd_s2s_in_sup).
outgoing_s2s_number() ->
supervisor_count(ejabberd_s2s_out_sup).
supervisor_count(Supervisor) ->
case catch supervisor:which_children(Supervisor) of
{'EXIT', _} -> 0;
Result ->
length(Result)
end.
-spec stop_all_connections() -> ok.
stop_all_connections() ->
lists:foreach(
fun({_Id, Pid, _Type, _Module}) ->
supervisor:terminate_child(ejabberd_s2s_in_sup, Pid)
end, supervisor:which_children(ejabberd_s2s_in_sup)),
lists:foreach(
fun({_Id, Pid, _Type, _Module}) ->
supervisor:terminate_child(ejabberd_s2s_out_sup, Pid)
end, supervisor:which_children(ejabberd_s2s_out_sup)),
mnesia:clear_table(s2s),
ok.
%%%----------------------------------------------------------------------
%%% Update Mnesia tables
update_tables() ->
case catch mnesia:table_info(s2s, type) of
bag -> ok;
{'EXIT', _} -> ok;
_ -> mnesia:delete_table(s2s)
end,
case catch mnesia:table_info(s2s, attributes) of
[fromto, node, key] ->
mnesia:transform_table(s2s, ignore, [fromto, pid]),
mnesia:clear_table(s2s);
[fromto, pid, key] ->
mnesia:transform_table(s2s, ignore, [fromto, pid]),
mnesia:clear_table(s2s);
[fromto, pid] -> ok;
{'EXIT', _} -> ok
end,
case lists:member(local_s2s, mnesia:system_info(tables)) of
true -> mnesia:delete_table(local_s2s);
false -> ok
end.
%% Check if host is in blacklist or white list
allow_host(MyServer, S2SHost) ->
allow_host1(MyServer, S2SHost) andalso
not is_temporarly_blocked(S2SHost).
allow_host1(MyHost, S2SHost) ->
Rule = ejabberd_config:get_option(
{s2s_access, MyHost},
fun acl:access_rules_validator/1,
all),
JID = jid:make(S2SHost),
case acl:match_rule(MyHost, Rule, JID) of
deny -> false;
allow ->
case ejabberd_hooks:run_fold(s2s_allow_host, MyHost,
allow, [MyHost, S2SHost]) of
deny -> false;
allow -> true;
_ -> true
end
end.
transform_options(Opts) ->
lists:foldl(fun transform_options/2, [], Opts).
transform_options({{s2s_host, Host}, Action}, Opts) ->
?WARNING_MSG("Option 's2s_host' is deprecated. "
"The option is still supported but it is better to "
"fix your config: use access rules instead.", []),
ACLName = aux:binary_to_atom(
iolist_to_binary(["s2s_access_", Host])),
[{acl, ACLName, {server, Host}},
{access, s2s, [{Action, ACLName}]},
{s2s_access, s2s} |
Opts];
transform_options({s2s_default_policy, Action}, Opts) ->
?WARNING_MSG("Option 's2s_default_policy' is deprecated. "
"The option is still supported but it is better to "
"fix your config: "
"use 's2s_access' with an access rule.", []),
[{access, s2s, [{Action, all}]},
{s2s_access, s2s} |
Opts];
transform_options(Opt, Opts) ->
[Opt|Opts].
%% Get information about S2S connections of the specified type.
%% @spec (Type) -> [Info]
%% where Type = in | out
%% Info = [{InfoName::atom(), InfoValue::any()}]
get_info_s2s_connections(Type) ->
ChildType = case Type of
in -> ejabberd_s2s_in_sup;
out -> ejabberd_s2s_out_sup
end,
Connections = supervisor:which_children(ChildType),
get_s2s_info(Connections, Type).
get_s2s_info(Connections, Type) ->
complete_s2s_info(Connections, Type, []).
complete_s2s_info([], _, Result) -> Result;
complete_s2s_info([Connection | T], Type, Result) ->
{_, PID, _, _} = Connection,
State = get_s2s_state(PID),
complete_s2s_info(T, Type, [State | Result]).
-spec get_s2s_state(pid()) -> [{status, open | closed | error} | {s2s_pid, pid()}].
get_s2s_state(S2sPid) ->
Infos = case gen_fsm:sync_send_all_state_event(S2sPid,
get_state_infos)
of
{state_infos, Is} -> [{status, open} | Is];
{noproc, _} -> [{status, closed}]; %% Connection closed
{badrpc, _} -> [{status, error}]
end,
[{s2s_pid, S2sPid} | Infos].
opt_type(route_subdomains) ->
fun (s2s) -> s2s;
(local) -> local
end;
opt_type(s2s_access) ->
fun acl:access_rules_validator/1;
opt_type(domain_certfile) -> fun iolist_to_binary/1;
opt_type(s2s_certfile) -> fun iolist_to_binary/1;
opt_type(s2s_ciphers) -> fun iolist_to_binary/1;
opt_type(s2s_dhfile) -> fun iolist_to_binary/1;
opt_type(s2s_cafile) -> fun iolist_to_binary/1;
opt_type(s2s_protocol_options) ->
fun (Options) -> str:join(Options, <<"|">>) end;
opt_type(s2s_tls_compression) ->
fun (true) -> true;
(false) -> false
end;
opt_type(s2s_use_starttls) ->
fun (true) -> true;
(false) -> false;
(optional) -> optional;
(required) -> required;
(required_trusted) -> required_trusted
end;
opt_type(s2s_timeout) ->
fun(I) when is_integer(I), I>=0 -> I;
(infinity) -> infinity
end;
opt_type(s2s_queue_type) ->
fun(ram) -> ram; (file) -> file end;
opt_type(_) ->
[route_subdomains, s2s_access, s2s_certfile,
s2s_ciphers, s2s_dhfile, s2s_cafile, s2s_protocol_options,
s2s_tls_compression, s2s_use_starttls, s2s_timeout, s2s_queue_type].