-define(EJABBERD_URI, <<"http://www.process-one.net/en/ejabberd/">>).
--define(COPYRIGHT, "Copyright (c) 2002-2016 ProcessOne").
+-define(COPYRIGHT, "Copyright (c) 2002-2017 ProcessOne").
- -define(S2STIMEOUT, timer:minutes(10)).
-
%%-define(DBGFSM, true).
-record(scram,
PasswordType :: password_type()) -> any().
register_mechanism(Mechanism, Module, PasswordType) ->
- case is_disabled(Mechanism) of
- false ->
- ets:insert(sasl_mechanism,
- #sasl_mechanism{mechanism = Mechanism, module = Module,
+ ets:insert(sasl_mechanism,
+ #sasl_mechanism{mechanism = Mechanism, module = Module,
- password_type = PasswordType});
- true ->
- ?DEBUG("SASL mechanism ~p is disabled", [Mechanism]),
- true
- end.
+ password_type = PasswordType}).
check_credentials(_State, Props) ->
User = proplists:get_value(authzid, Props, <<>>),
-spec listmech(Host ::binary()) -> Mechanisms::mechanisms().
listmech(Host) ->
- Mechs = ets:select(sasl_mechanism,
+ ets:select(sasl_mechanism,
- [{#sasl_mechanism{mechanism = '$1',
- password_type = '$2', _ = '_'},
- case catch ejabberd_auth:store_type(Host) of
- external -> [{'==', '$2', plain}];
- scram -> [{'/=', '$2', digest}];
- {'EXIT', {undef, [{Module, store_type, []} | _]}} ->
- ?WARNING_MSG("~p doesn't implement the function store_type/0",
- [Module]),
- [];
- _Else -> []
- end,
+ [{#sasl_mechanism{mechanism = '$1',
+ password_type = '$2', _ = '_'},
+ case catch ejabberd_auth:store_type(Host) of
+ external -> [{'==', '$2', plain}];
+ scram -> [{'/=', '$2', digest}];
+ {'EXIT', {undef, [{Module, store_type, []} | _]}} ->
+ ?WARNING_MSG("~p doesn't implement the function store_type/0",
+ [Module]),
+ [];
+ _Else -> []
+ end,
- ['$1']}]),
- filter_anonymous(Host, Mechs).
+ ['$1']}]).
-spec server_new(binary(), binary(), binary(), term(),
fun(), fun(), fun()) -> sasl_state().
%% API
-export([get_nodes/0, call/4, multicall/3, multicall/4]).
--export([join/1, leave/1]).
+-export([join/1, leave/1, get_known_nodes/0]).
+ -export([node_id/0, get_node_by_id/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
run_fold1(Ls, Hook, NewVal, Args)
end.
- safe_apply(Module, Function, Args) ->
- if is_function(Function) ->
- catch apply(Function, Args);
+ safe_apply(Hook, Module, Function, Args) ->
+ try if is_function(Function) ->
+ apply(Function, Args);
- true ->
+ true ->
- catch apply(Module, Function, Args)
+ apply(Module, Function, Args)
+ end
+ catch E:R when E /= exit, R /= normal ->
+ ?ERROR_MSG("Hook ~p crashed when running ~p:~p/~p:~n"
+ "** Reason = ~p~n"
+ "** Arguments = ~p",
+ [Hook, Module, Function, length(Args),
+ {E, R, get_stacktrace()}, Args]),
+ 'EXIT'
end.
+
+ get_stacktrace() ->
+ [{Mod, Fun, Loc, Args} || {Mod, Fun, Args, Loc} <- erlang:get_stacktrace()].
%% list with such name. We shouldn't stop here.
{ok, State};
true ->
- stop("Failed to write privacy: ~p", [Err])
+ stop("Failed to write privacy: ~p", [Reason])
- end;
+ end;
_ ->
{ok, State}
end.
?DEBUG("s2s manager~n\tfrom ~p~n\tto ~p~n\tpacket "
"~P~n",
[From, To, Packet, 8]),
- case find_connection(From, To) of
- {atomic, Pid} when is_pid(Pid) ->
+ case start_connection(From, To) of
+ {ok, Pid} when is_pid(Pid) ->
- ?DEBUG("sending to process ~p~n", [Pid]),
- #jid{lserver = MyServer} = From,
+ ?DEBUG("sending to process ~p~n", [Pid]),
+ #jid{lserver = MyServer} = From,
- ejabberd_hooks:run(s2s_send_packet, MyServer,
- [From, To, Packet]),
- send_element(Pid, xmpp:set_from_to(Packet, From, To)),
- ok;
- {aborted, _Reason} ->
+ ejabberd_hooks:run(s2s_send_packet, MyServer, [From, To, Packet]),
+ ejabberd_s2s_out:route(Pid, xmpp:set_from_to(Packet, From, To));
+ {error, Reason} ->
- Lang = xmpp:get_lang(Packet),
+ Lang = xmpp:get_lang(Packet),
- Txt = <<"No s2s connection found">>,
- Err = xmpp:err_service_unavailable(Txt, Lang),
- ejabberd_router:route_error(To, From, Packet, Err),
- false
+ 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(To, From, Packet, Err)
end.
- -spec find_connection(jid(), jid()) -> {aborted, any()} | {atomic, pid()}.
+ -spec start_connection(jid(), jid())
+ -> {ok, pid()} | {error, policy_violation | forbidden | internal_server_error}.
+ start_connection(From, To) ->
+ start_connection(From, To, []).
- find_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},
%% 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:
- case not is_service(From, To) andalso
- allow_host(MyServer, Server)
- of
+ LServer = ejabberd_router:host_of_route(MyServer),
+ case is_service(From, To) of
- true ->
+ true ->
- NeededConnections = needed_connections_number([],
+ {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,
+ MaxS2SConnectionsNumber,
+ MaxS2SConnectionsNumberPerNode),
+ open_several_connections(NeededConnections, MyServer,
+ Server, From, FromTo,
+ MaxS2SConnectionsNumber,
- MaxS2SConnectionsNumberPerNode);
- false -> {aborted, error}
+ MaxS2SConnectionsNumberPerNode, Opts);
+ false ->
+ {error, forbidden}
+ end
end;
L when is_list(L) ->
NeededConnections = needed_connections_number(L,
open_several_connections(N, MyServer, Server, From,
FromTo, MaxS2SConnectionsNumber,
- MaxS2SConnectionsNumberPerNode) ->
- ConnectionsResult = [new_connection(MyServer, Server,
+ MaxS2SConnectionsNumberPerNode, Opts) ->
+ case lists:flatmap(
+ fun(_) ->
+ new_connection(MyServer, Server,
- From, FromTo, MaxS2SConnectionsNumber,
+ From, FromTo, MaxS2SConnectionsNumber,
- MaxS2SConnectionsNumberPerNode)
- || _N <- lists:seq(1, N)],
- case [PID || {atomic, PID} <- ConnectionsResult] of
- [] -> hd(ConnectionsResult);
- PIDs -> {atomic, choose_pid(From, PIDs)}
+ 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,
- %%%----------------------------------------------------------------------
- %%% File : ejabberd_s2s_in.erl
- %%% Author : Alexey Shchepin <alexey@process-one.net>
- %%% Purpose : Serve incoming s2s connection
- %%% Created : 6 Dec 2002 by Alexey Shchepin <alexey@process-one.net>
+ %%%-------------------------------------------------------------------
+ %%% Created : 12 Dec 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2016 ProcessOne
+%%% 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
%%% 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_in).
-
+ -behaviour(xmpp_stream_in).
-behaviour(ejabberd_config).
+ -behaviour(ejabberd_socket).
- -author('alexey@process-one.net').
-
- -behaviour(p1_fsm).
-
- %% External exports
+ %% ejabberd_socket callbacks
-export([start/2, start_link/2, socket_type/0]).
-
- -export([init/1, wait_for_stream/2,
- wait_for_feature_request/2, stream_established/2,
- handle_event/3, handle_sync_event/4, code_change/4,
- handle_info/3, print_state/1, terminate/3, opt_type/1]).
+ %% ejabberd_config callbacks
+ -export([opt_type/1]).
+ %% xmpp_stream_in callbacks
+ -export([init/1, handle_call/3, handle_cast/2,
+ handle_info/2, terminate/2, code_change/3]).
+ -export([tls_options/1, tls_required/1, tls_verify/1, tls_enabled/1,
+ compress_methods/1,
+ unauthenticated_stream_features/1, authenticated_stream_features/1,
+ handle_stream_start/2, handle_stream_end/2,
+ handle_stream_established/1, handle_auth_success/4,
+ handle_auth_failure/4, handle_send/3, handle_recv/3, handle_cdata/2,
+ handle_unauthenticated_packet/2, handle_authenticated_packet/2]).
+ %% Hooks
+ -export([handle_unexpected_info/2, handle_unexpected_cast/2,
+ reject_unauthenticated_packet/2, process_closed/2]).
+ %% API
+ -export([stop/1, close/1, send/2, update_state/2, establish/1, add_hooks/0]).
-include("ejabberd.hrl").
- -include("logger.hrl").
-
-include("xmpp.hrl").
+ -include("logger.hrl").
- -define(DICT, dict).
-
- -record(state,
- {socket :: ejabberd_socket:socket_state(),
- sockmod = ejabberd_socket :: ejabberd_socket | ejabberd_frontend_socket,
- streamid = <<"">> :: binary(),
- shaper = none :: shaper:shaper(),
- tls = false :: boolean(),
- tls_enabled = false :: boolean(),
- tls_required = false :: boolean(),
- tls_certverify = false :: boolean(),
- tls_options = [] :: list(),
- server = <<"">> :: binary(),
- authenticated = false :: boolean(),
- auth_domain = <<"">> :: binary(),
- connections = (?DICT):new() :: ?TDICT,
- timer = make_ref() :: reference()}).
-
- -type state_name() :: wait_for_stream | wait_for_feature_request | stream_established.
- -type state() :: #state{}.
- -type fsm_next() :: {next_state, state_name(), state()}.
- -type fsm_stop() :: {stop, normal, state()}.
- -type fsm_transition() :: fsm_stop() | fsm_next().
-
- %%-define(DBGFSM, true).
- -ifdef(DBGFSM).
- -define(FSMOPTS, [{debug, [trace]}]).
- -else.
- -define(FSMOPTS, []).
- -endif.
+ -type state() :: map().
+ -export_type([state/0]).
+ %%%===================================================================
+ %%% API
+ %%%===================================================================
start(SockData, Opts) ->
- supervisor:start_child(ejabberd_s2s_in_sup,
- [SockData, Opts]).
+ case proplists:get_value(supervisor, Opts, true) of
+ true ->
+ supervisor:start_child(ejabberd_s2s_in_sup, [SockData, Opts]);
+ _ ->
+ xmpp_stream_in:start(?MODULE, [SockData, Opts],
+ ejabberd_config:fsm_limit_opts(Opts))
+ end.
start_link(SockData, Opts) ->
- p1_fsm:start_link(ejabberd_s2s_in, [SockData, Opts],
- ?FSMOPTS ++ fsm_limit_opts(Opts)).
-
- socket_type() -> xml_stream.
-
- %%%----------------------------------------------------------------------
- %%% Callback functions from gen_fsm
- %%%----------------------------------------------------------------------
-
- init([{SockMod, Socket}, Opts]) ->
- ?DEBUG("started: ~p", [{SockMod, Socket}]),
- Shaper = case lists:keysearch(shaper, 1, Opts) of
- {value, {_, S}} -> S;
- _ -> none
- end,
- {StartTLS, TLSRequired, TLSCertverify} =
- case ejabberd_config:get_option(
- s2s_use_starttls,
- fun(false) -> false;
- (true) -> true;
- (optional) -> optional;
- (required) -> required;
- (required_trusted) -> required_trusted
- end,
- false) of
- UseTls
- when (UseTls == undefined) or
- (UseTls == false) ->
- {false, false, false};
- UseTls
- when (UseTls == true) or
- (UseTls ==
- optional) ->
- {true, false, false};
- required -> {true, true, false};
- required_trusted ->
- {true, true, true}
- end,
- TLSOpts1 = case ejabberd_config:get_option(
- s2s_certfile,
- fun iolist_to_binary/1) of
- undefined -> [];
- CertFile -> [{certfile, CertFile}]
- end,
- TLSOpts2 = case ejabberd_config:get_option(
- s2s_ciphers, fun iolist_to_binary/1) of
- undefined -> TLSOpts1;
- Ciphers -> [{ciphers, Ciphers} | TLSOpts1]
- end,
- TLSOpts3 = case ejabberd_config:get_option(
- s2s_protocol_options,
- fun (Options) ->
- [_|O] = lists:foldl(
- fun(X, Acc) -> X ++ Acc end, [],
- [["|" | binary_to_list(Opt)] || Opt <- Options, is_binary(Opt)]
- ),
- iolist_to_binary(O)
- end) of
- undefined -> TLSOpts2;
- ProtocolOpts -> [{protocol_options, ProtocolOpts} | TLSOpts2]
- end,
- TLSOpts4 = case ejabberd_config:get_option(
- s2s_dhfile, fun iolist_to_binary/1) of
- undefined -> TLSOpts3;
- DHFile -> [{dhfile, DHFile} | TLSOpts3]
- end,
- TLSOpts = case proplists:get_bool(tls_compression, Opts) of
- false -> [compression_none | TLSOpts4];
- true -> TLSOpts4
- end,
- Timer = erlang:start_timer(?S2STIMEOUT, self(), []),
- {ok, wait_for_stream,
- #state{socket = Socket, sockmod = SockMod,
- streamid = new_id(), shaper = Shaper, tls = StartTLS,
- tls_enabled = false, tls_required = TLSRequired,
- tls_certverify = TLSCertverify, tls_options = TLSOpts,
- timer = Timer}}.
-
- %%----------------------------------------------------------------------
- %% Func: StateName/2
- %% Returns: {next_state, NextStateName, NextStateData} |
- %% {next_state, NextStateName, NextStateData, Timeout} |
- %% {stop, Reason, NewStateData}
- %%----------------------------------------------------------------------
- wait_for_stream({xmlstreamstart, Name, Attrs}, StateData) ->
- try xmpp:decode(#xmlel{name = Name, attrs = Attrs}) of
- #stream_start{xmlns = NS_SERVER, stream_xmlns = NS_STREAM}
- when NS_SERVER /= ?NS_SERVER; NS_STREAM /= ?NS_STREAM ->
- send_header(StateData, {1,0}),
- send_element(StateData, xmpp:serr_invalid_namespace()),
- {stop, normal, StateData};
- #stream_start{to = #jid{lserver = Server},
- from = From, version = {1,0}}
- when StateData#state.tls and not StateData#state.authenticated ->
- send_header(StateData, {1,0}),
- Auth = if StateData#state.tls_enabled ->
- case From of
- #jid{} ->
- {Result, Message} =
- ejabberd_s2s:check_peer_certificate(
- StateData#state.sockmod,
- StateData#state.socket,
- From#jid.lserver),
- {Result, From#jid.lserver, Message};
- undefined ->
- {error, <<"(unknown)">>,
- <<"Got no valid 'from' attribute">>}
- end;
+ xmpp_stream_in:start_link(?MODULE, [SockData, Opts],
+ ejabberd_config:fsm_limit_opts(Opts)).
+
+ close(Ref) ->
+ xmpp_stream_in:close(Ref).
+
+ stop(Ref) ->
+ xmpp_stream_in:stop(Ref).
+
+ socket_type() ->
+ xml_stream.
+
+ -spec send(pid(), xmpp_element()) -> ok;
+ (state(), xmpp_element()) -> state().
+ send(Stream, Pkt) ->
+ xmpp_stream_in:send(Stream, Pkt).
+
+ -spec establish(state()) -> state().
+ establish(State) ->
+ xmpp_stream_in:establish(State).
+
+ -spec update_state(pid(), fun((state()) -> state()) |
+ {module(), atom(), list()}) -> ok.
+ update_state(Ref, Callback) ->
+ xmpp_stream_in:cast(Ref, {update_state, Callback}).
+
+ -spec add_hooks() -> ok.
+ add_hooks() ->
+ lists:foreach(
+ fun(Host) ->
+ ejabberd_hooks:add(s2s_in_closed, Host, ?MODULE,
+ process_closed, 100),
+ ejabberd_hooks:add(s2s_in_unauthenticated_packet, Host, ?MODULE,
+ reject_unauthenticated_packet, 100),
+ ejabberd_hooks:add(s2s_in_handle_info, Host, ?MODULE,
+ handle_unexpected_info, 100),
+ ejabberd_hooks:add(s2s_in_handle_cast, Host, ?MODULE,
+ handle_unexpected_cast, 100)
+ end, ?MYHOSTS).
+
+ %%%===================================================================
+ %%% Hooks
+ %%%===================================================================
+ handle_unexpected_info(State, Info) ->
+ ?WARNING_MSG("got unexpected info: ~p", [Info]),
+ State.
+
+ handle_unexpected_cast(State, Msg) ->
+ ?WARNING_MSG("got unexpected cast: ~p", [Msg]),
+ State.
+
+ reject_unauthenticated_packet(State, _Pkt) ->
+ Err = xmpp:serr_not_authorized(),
+ send(State, Err).
+
+ process_closed(State, _Reason) ->
+ stop(State).
+
+ %%%===================================================================
+ %%% xmpp_stream_in callbacks
+ %%%===================================================================
+ tls_options(#{tls_options := TLSOpts, server_host := LServer}) ->
+ ejabberd_s2s:tls_options(LServer, TLSOpts).
+
+ tls_required(#{server_host := LServer}) ->
+ ejabberd_s2s:tls_required(LServer).
+
+ tls_verify(#{server_host := LServer}) ->
+ ejabberd_s2s:tls_verify(LServer).
+
+ tls_enabled(#{server_host := LServer}) ->
+ ejabberd_s2s:tls_enabled(LServer).
+
+ compress_methods(#{server_host := LServer}) ->
+ case ejabberd_s2s:zlib_enabled(LServer) of
+ true -> [<<"zlib">>];
+ false -> []
+ end.
+
+ unauthenticated_stream_features(#{server_host := LServer}) ->
+ ejabberd_hooks:run_fold(s2s_in_pre_auth_features, LServer, [], [LServer]).
+
+ authenticated_stream_features(#{server_host := LServer}) ->
+ ejabberd_hooks:run_fold(s2s_in_post_auth_features, LServer, [], [LServer]).
+
+ handle_stream_start(_StreamStart, #{lserver := LServer} = State) ->
+ case check_to(jid:make(LServer), State) of
+ false ->
+ send(State, xmpp:serr_host_unknown());
- true ->
+ true ->
- {no_verify, <<"(unknown)">>, <<"TLS not (yet) enabled">>}
- end,
- StartTLS = if StateData#state.tls_enabled -> [];
- not StateData#state.tls_enabled and
- not StateData#state.tls_required ->
- [#starttls{required = false}];
- not StateData#state.tls_enabled and
- StateData#state.tls_required ->
- [#starttls{required = true}]
- end,
- case Auth of
- {error, RemoteServer, CertError}
- when StateData#state.tls_certverify ->
- ?INFO_MSG("Closing s2s connection: ~s <--> ~s (~s)",
- [StateData#state.server, RemoteServer, CertError]),
- send_element(StateData,
- xmpp:serr_policy_violation(CertError, ?MYLANG)),
- {stop, normal, StateData};
- {VerifyResult, RemoteServer, Msg} ->
- {SASL, NewStateData} =
- case VerifyResult of
- ok ->
- {[#sasl_mechanisms{list = [<<"EXTERNAL">>]}],
- StateData#state{auth_domain = RemoteServer}};
- error ->
- ?DEBUG("Won't accept certificate of ~s: ~s",
- [RemoteServer, Msg]),
- {[], StateData};
- no_verify ->
- {[], StateData}
- end,
- send_element(NewStateData,
- #stream_features{
- sub_els = SASL ++ StartTLS ++
- ejabberd_hooks:run_fold(
- s2s_stream_features, Server, [],
- [Server])}),
- {next_state, wait_for_feature_request,
- NewStateData#state{server = Server}}
- end;
- #stream_start{to = #jid{lserver = Server},
- version = {1,0}} when StateData#state.authenticated ->
- send_header(StateData, {1,0}),
- send_element(StateData,
- #stream_features{
- sub_els = ejabberd_hooks:run_fold(
- s2s_stream_features, Server, [],
- [Server])}),
- {next_state, stream_established, StateData};
- #stream_start{db_xmlns = ?NS_SERVER_DIALBACK}
- when (StateData#state.tls_required and StateData#state.tls_enabled)
- or (not StateData#state.tls_required) ->
- send_header(StateData, undefined),
- {next_state, stream_established, StateData};
- #stream_start{} ->
- send_header(StateData, {1,0}),
- send_element(StateData, xmpp:serr_undefined_condition()),
- {stop, normal, StateData};
- _ ->
- send_header(StateData, {1,0}),
- send_element(StateData, xmpp:serr_invalid_xml()),
- {stop, normal, StateData}
- catch _:{xmpp_codec, Why} ->
- Txt = xmpp:format_error(Why),
- send_header(StateData, {1,0}),
- send_element(StateData, xmpp:serr_invalid_xml(Txt, ?MYLANG)),
- {stop, normal, StateData}
- end;
- wait_for_stream({xmlstreamerror, _}, StateData) ->
- send_header(StateData, {1,0}),
- send_element(StateData, xmpp:serr_not_well_formed()),
- {stop, normal, StateData};
- wait_for_stream(timeout, StateData) ->
- send_header(StateData, {1,0}),
- send_element(StateData, xmpp:serr_connection_timeout()),
- {stop, normal, StateData};
- wait_for_stream(closed, StateData) ->
- {stop, normal, StateData}.
-
- wait_for_feature_request({xmlstreamelement, El}, StateData) ->
- decode_element(El, wait_for_feature_request, StateData);
- wait_for_feature_request(#starttls{},
- #state{tls = true, tls_enabled = false} = StateData) ->
- case (StateData#state.sockmod):get_sockmod(StateData#state.socket) of
- gen_tcp ->
- ?DEBUG("starttls", []),
- Socket = StateData#state.socket,
- TLSOpts1 = case
- ejabberd_config:get_option(
- {domain_certfile, StateData#state.server},
- fun iolist_to_binary/1) of
- undefined -> StateData#state.tls_options;
- CertFile ->
- lists:keystore(certfile, 1,
- StateData#state.tls_options,
- {certfile, CertFile})
- end,
- TLSOpts2 = case ejabberd_config:get_option(
- {s2s_cafile, StateData#state.server},
- fun iolist_to_binary/1) of
- undefined -> TLSOpts1;
- CAFile ->
- lists:keystore(cafile, 1, TLSOpts1,
- {cafile, CAFile})
- end,
- TLSOpts = case ejabberd_config:get_option(
- {s2s_tls_compression, StateData#state.server},
- fun(true) -> true;
- (false) -> false
- end, false) of
- true -> lists:delete(compression_none, TLSOpts2);
- false -> [compression_none | TLSOpts2]
- end,
- TLSSocket = (StateData#state.sockmod):starttls(
- Socket, TLSOpts,
- fxml:element_to_binary(
- xmpp:encode(#starttls_proceed{}))),
- {next_state, wait_for_stream,
- StateData#state{socket = TLSSocket, streamid = new_id(),
- tls_enabled = true, tls_options = TLSOpts}};
- _ ->
- send_element(StateData, #starttls_failure{}),
- {stop, normal, StateData}
- end;
- wait_for_feature_request(#sasl_auth{mechanism = Mech},
- #state{tls_enabled = true} = StateData) ->
- case Mech of
- <<"EXTERNAL">> when StateData#state.auth_domain /= <<"">> ->
- AuthDomain = StateData#state.auth_domain,
- AllowRemoteHost = ejabberd_s2s:allow_host(<<"">>, AuthDomain),
- if AllowRemoteHost ->
- (StateData#state.sockmod):reset_stream(StateData#state.socket),
- send_element(StateData, #sasl_success{}),
- ?INFO_MSG("Accepted s2s EXTERNAL authentication for ~s (TLS=~p)",
- [AuthDomain, StateData#state.tls_enabled]),
- change_shaper(StateData, <<"">>, jid:make(AuthDomain)),
- {next_state, wait_for_stream,
- StateData#state{streamid = new_id(),
- authenticated = true}};
+ ServerHost = ejabberd_router:host_of_route(LServer),
+ State#{server_host => ServerHost}
+ end.
+
+ handle_stream_end(Reason, #{server_host := LServer} = State) ->
+ ejabberd_hooks:run_fold(s2s_in_closed, LServer, State, [Reason]).
+
+ handle_stream_established(State) ->
+ set_idle_timeout(State#{established => true}).
+
+ handle_auth_success(RServer, Mech, _AuthModule,
+ #{sockmod := SockMod,
+ socket := Socket, ip := IP,
+ auth_domains := AuthDomains,
+ server_host := ServerHost,
+ lserver := LServer} = State) ->
+ ?INFO_MSG("(~s) Accepted inbound s2s ~s authentication ~s -> ~s (~s)",
+ [SockMod:pp(Socket), Mech, RServer, LServer,
+ ejabberd_config:may_hide_data(jlib:ip_to_list(IP))]),
+ State1 = case ejabberd_s2s:allow_host(ServerHost, RServer) of
- true ->
+ true ->
- Txt = xmpp:mk_text(<<"Denied by ACL">>, ?MYLANG),
- send_element(StateData,
- #sasl_failure{reason = 'not-authorized',
- text = Txt}),
- {stop, normal, StateData}
- end;
- _ ->
- send_element(StateData, #sasl_failure{reason = 'invalid-mechanism'}),
- {stop, normal, StateData}
- end;
- wait_for_feature_request({xmlstreamend, _Name}, StateData) ->
- {stop, normal, StateData};
- wait_for_feature_request({xmlstreamerror, _}, StateData) ->
- send_element(StateData, xmpp:serr_not_well_formed()),
- {stop, normal, StateData};
- wait_for_feature_request(closed, StateData) ->
- {stop, normal, StateData};
- wait_for_feature_request(_Pkt, #state{tls_required = TLSRequired,
- tls_enabled = TLSEnabled} = StateData)
- when TLSRequired and not TLSEnabled ->
- Txt = <<"Use of STARTTLS required">>,
- send_element(StateData, xmpp:serr_policy_violation(Txt, ?MYLANG)),
- {stop, normal, StateData};
- wait_for_feature_request(El, StateData) ->
- stream_established({xmlstreamelement, El}, StateData).
-
- stream_established({xmlstreamelement, El}, StateData) ->
- cancel_timer(StateData#state.timer),
- Timer = erlang:start_timer(?S2STIMEOUT, self(), []),
- decode_element(El, stream_established, StateData#state{timer = Timer});
- stream_established(#db_result{to = To, from = From, key = Key},
- StateData) ->
- ?DEBUG("GET KEY: ~p", [{To, From, Key}]),
- case {ejabberd_s2s:allow_host(To, From),
- lists:member(To, ejabberd_router:dirty_get_all_domains())} of
- {true, true} ->
- ejabberd_s2s_out:terminate_if_waiting_delay(To, From),
- ejabberd_s2s_out:start(To, From,
- {verify, self(), Key,
- StateData#state.streamid}),
- Conns = (?DICT):store({From, To},
- wait_for_verification,
- StateData#state.connections),
- change_shaper(StateData, To, jid:make(From)),
- {next_state, stream_established,
- StateData#state{connections = Conns}};
- {_, false} ->
- send_element(StateData, xmpp:serr_host_unknown()),
- {stop, normal, StateData};
- {false, _} ->
- send_element(StateData, xmpp:serr_invalid_from()),
- {stop, normal, StateData}
- end;
- stream_established(#db_verify{to = To, from = From, id = Id, key = Key},
- StateData) ->
- ?DEBUG("VERIFY KEY: ~p", [{To, From, Id, Key}]),
- Type = case ejabberd_s2s:make_key({To, From}, Id) of
- Key -> valid;
- _ -> invalid
+ AuthDomains1 = sets:add_element(RServer, AuthDomains),
+ change_shaper(State, RServer),
+ State#{auth_domains => AuthDomains1};
+ false ->
+ State
- end,
+ end,
- send_element(StateData,
- #db_verify{from = To, to = From, id = Id, type = Type}),
- {next_state, stream_established, StateData};
- stream_established(Pkt, StateData) when ?is_stanza(Pkt) ->
+ ejabberd_hooks:run_fold(s2s_in_auth_result, ServerHost, State1, [true, RServer]).
+
+ handle_auth_failure(RServer, Mech, Reason,
+ #{sockmod := SockMod,
+ socket := Socket, ip := IP,
+ server_host := ServerHost,
+ lserver := LServer} = State) ->
+ ?INFO_MSG("(~s) Failed inbound s2s ~s authentication ~s -> ~s (~s): ~s",
+ [SockMod:pp(Socket), Mech, RServer, LServer,
+ ejabberd_config:may_hide_data(jlib:ip_to_list(IP)), Reason]),
+ ejabberd_hooks:run_fold(s2s_in_auth_result,
+ ServerHost, State, [false, RServer]).
+
+ handle_unauthenticated_packet(Pkt, #{server_host := LServer} = State) ->
+ ejabberd_hooks:run_fold(s2s_in_unauthenticated_packet,
+ LServer, State, [Pkt]).
+
+ handle_authenticated_packet(Pkt, #{server_host := LServer} = State) when not ?is_stanza(Pkt) ->
+ ejabberd_hooks:run_fold(s2s_in_authenticated_packet, LServer, State, [Pkt]);
+ handle_authenticated_packet(Pkt, State) ->
From = xmpp:get_from(Pkt),
To = xmpp:get_to(Pkt),
- if To /= undefined, From /= undefined ->
- LFrom = From#jid.lserver,
- LTo = To#jid.lserver,
- if StateData#state.authenticated ->
- case LFrom == StateData#state.auth_domain andalso
- lists:member(LTo, ejabberd_router:dirty_get_all_domains()) of
- true ->
- ejabberd_hooks:run(s2s_receive_packet, LTo,
- [From, To, Pkt]),
- ejabberd_router:route(From, To, Pkt);
- false ->
- send_error(StateData, Pkt, xmpp:err_not_authorized())
- end;
- true ->
- case (?DICT):find({LFrom, LTo}, StateData#state.connections) of
- {ok, established} ->
- ejabberd_hooks:run(s2s_receive_packet, LTo,
- [From, To, Pkt]),
- ejabberd_router:route(From, To, Pkt);
- _ ->
- send_error(StateData, Pkt, xmpp:err_not_authorized())
- end
- end;
- true ->
- send_error(StateData, Pkt, xmpp:err_jid_malformed())
+ case check_from_to(From, To, State) of
+ ok ->
+ LServer = ejabberd_router:host_of_route(To#jid.lserver),
+ State1 = ejabberd_hooks:run_fold(s2s_in_authenticated_packet,
+ LServer, State, [Pkt]),
+ {Pkt1, State2} = ejabberd_hooks:run_fold(s2s_receive_packet, LServer,
+ {Pkt, State1}, []),
+ case Pkt1 of
+ drop -> ok;
+ _ -> ejabberd_router:route(From, To, Pkt1)
- end,
+ end,
- ejabberd_hooks:run(s2s_loop_debug, [{xmlstreamelement, Pkt}]),
- {next_state, stream_established, StateData};
- stream_established({valid, From, To}, StateData) ->
- send_element(StateData,
- #db_result{from = To, to = From, type = valid}),
- ?INFO_MSG("Accepted s2s dialback authentication for ~s (TLS=~p)",
- [From, StateData#state.tls_enabled]),
- NSD = StateData#state{connections =
- (?DICT):store({From, To}, established,
- StateData#state.connections)},
- {next_state, stream_established, NSD};
- stream_established({invalid, From, To}, StateData) ->
- send_element(StateData,
- #db_result{from = To, to = From, type = invalid}),
- NSD = StateData#state{connections =
- (?DICT):erase({From, To},
- StateData#state.connections)},
- {next_state, stream_established, NSD};
- stream_established({xmlstreamend, _Name}, StateData) ->
- {stop, normal, StateData};
- stream_established({xmlstreamerror, _}, StateData) ->
- send_element(StateData, xmpp:serr_not_well_formed()),
- {stop, normal, StateData};
- stream_established(timeout, StateData) ->
- send_element(StateData, xmpp:serr_connection_timeout()),
- {stop, normal, StateData};
- stream_established(closed, StateData) ->
- {stop, normal, StateData};
- stream_established(Pkt, StateData) ->
- ejabberd_hooks:run(s2s_loop_debug, [{xmlstreamelement, Pkt}]),
- {next_state, stream_established, StateData}.
-
- %%----------------------------------------------------------------------
- %% Func: StateName/3
- %% Returns: {next_state, NextStateName, NextStateData} |
- %% {next_state, NextStateName, NextStateData, Timeout} |
- %% {reply, Reply, NextStateName, NextStateData} |
- %% {reply, Reply, NextStateName, NextStateData, Timeout} |
- %% {stop, Reason, NewStateData} |
- %% {stop, Reason, Reply, NewStateData}
- %%----------------------------------------------------------------------
- %state_name(Event, From, StateData) ->
- % Reply = ok,
- % {reply, Reply, state_name, StateData}.
-
- handle_event(_Event, StateName, StateData) ->
- {next_state, StateName, StateData}.
-
- handle_sync_event(get_state_infos, _From, StateName,
- StateData) ->
- SockMod = StateData#state.sockmod,
- {Addr, Port} = try
- SockMod:peername(StateData#state.socket)
- of
- {ok, {A, P}} -> {A, P};
- {error, _} -> {unknown, unknown}
- catch
- _:_ -> {unknown, unknown}
+ State2;
+ {error, Err} ->
+ send(State, Err)
+ end.
+
+ handle_cdata(Data, #{server_host := LServer} = State) ->
+ ejabberd_hooks:run_fold(s2s_in_handle_cdata, LServer, State, [Data]).
+
+ handle_recv(El, Pkt, #{server_host := LServer} = State) ->
+ State1 = set_idle_timeout(State),
+ ejabberd_hooks:run_fold(s2s_in_handle_recv, LServer, State1, [El, Pkt]).
+
+ handle_send(Pkt, Result, #{server_host := LServer} = State) ->
+ ejabberd_hooks:run_fold(s2s_in_handle_send, LServer,
+ State, [Pkt, Result]).
+
+ init([State, Opts]) ->
+ Shaper = gen_mod:get_opt(shaper, Opts, fun acl:shaper_rules_validator/1, none),
+ TLSOpts1 = lists:filter(
+ fun({certfile, _}) -> true;
+ ({ciphers, _}) -> true;
+ ({dhfile, _}) -> true;
+ ({cafile, _}) -> true;
+ (_) -> false
+ end, Opts),
+ TLSOpts2 = case lists:keyfind(protocol_options, 1, Opts) of
+ false -> TLSOpts1;
+ {_, OptString} ->
+ ProtoOpts = str:join(OptString, <<$|>>),
+ [{protocol_options, ProtoOpts}|TLSOpts1]
- end,
+ end,
- Domains = get_external_hosts(StateData),
- Infos = [{direction, in}, {statename, StateName},
- {addr, Addr}, {port, Port},
- {streamid, StateData#state.streamid},
- {tls, StateData#state.tls},
- {tls_enabled, StateData#state.tls_enabled},
- {tls_options, StateData#state.tls_options},
- {authenticated, StateData#state.authenticated},
- {shaper, StateData#state.shaper}, {sockmod, SockMod},
- {domains, Domains}],
- Reply = {state_infos, Infos},
- {reply, Reply, StateName, StateData};
- %%----------------------------------------------------------------------
- %% Func: handle_sync_event/4
- %% Returns: {next_state, NextStateName, NextStateData} |
- %% {next_state, NextStateName, NextStateData, Timeout} |
- %% {reply, Reply, NextStateName, NextStateData} |
- %% {reply, Reply, NextStateName, NextStateData, Timeout} |
- %% {stop, Reason, NewStateData} |
- %% {stop, Reason, Reply, NewStateData}
- %%----------------------------------------------------------------------
- handle_sync_event(_Event, _From, StateName,
- StateData) ->
- Reply = ok, {reply, Reply, StateName, StateData}.
-
- code_change(_OldVsn, StateName, StateData, _Extra) ->
- {ok, StateName, StateData}.
-
- handle_info({send_text, Text}, StateName, StateData) ->
- send_text(StateData, Text),
- {next_state, StateName, StateData};
- handle_info({timeout, Timer, _}, StateName,
- #state{timer = Timer} = StateData) ->
- if StateName == wait_for_stream ->
- send_header(StateData, undefined);
- true ->
- ok
+ TLSOpts3 = case proplists:get_bool(tls_compression, Opts) of
+ false -> [compression_none | TLSOpts2];
+ true -> TLSOpts2
- end,
+ end,
- send_element(StateData, xmpp:serr_connection_timeout()),
- {stop, normal, StateData};
- handle_info(_, StateName, StateData) ->
- {next_state, StateName, StateData}.
+ State1 = State#{tls_options => TLSOpts3,
+ auth_domains => sets:new(),
+ xmlns => ?NS_SERVER,
+ lang => ?MYLANG,
+ server => ?MYNAME,
+ lserver => ?MYNAME,
+ server_host => ?MYNAME,
+ established => false,
+ shaper => Shaper},
+ ejabberd_hooks:run_fold(s2s_in_init, {ok, State1}, [Opts]).
+
+ handle_call(Request, From, #{server_host := LServer} = State) ->
+ ejabberd_hooks:run_fold(s2s_in_handle_call, LServer, State, [Request, From]).
+
+ handle_cast({update_state, Fun}, State) ->
+ case Fun of
+ {M, F, A} -> erlang:apply(M, F, [State|A]);
+ _ when is_function(Fun) -> Fun(State)
+ end;
+ handle_cast(Msg, #{server_host := LServer} = State) ->
+ ejabberd_hooks:run_fold(s2s_in_handle_cast, LServer, State, [Msg]).
- terminate(Reason, _StateName, StateData) ->
- ?DEBUG("terminated: ~p", [Reason]),
+ handle_info(Info, #{server_host := LServer} = State) ->
+ ejabberd_hooks:run_fold(s2s_in_handle_info, LServer, State, [Info]).
+
+ terminate(Reason, #{auth_domains := AuthDomains}) ->
case Reason of
- {process_limit, _} ->
+ {process_limit, _} ->
- [ejabberd_s2s:external_host_overloaded(Host)
- || Host <- get_external_hosts(StateData)];
- _ -> ok
- end,
- catch send_trailer(StateData),
- (StateData#state.sockmod):close(StateData#state.socket),
- ok.
-
- get_external_hosts(StateData) ->
- case StateData#state.authenticated of
- true -> [StateData#state.auth_domain];
- false ->
- Connections = StateData#state.connections,
- [D
- || {{D, _}, established} <- dict:to_list(Connections)]
+ sets:fold(
+ fun(Host, _) ->
+ ejabberd_s2s:external_host_overloaded(Host)
+ end, ok, AuthDomains);
+ _ ->
+ ok
end.
- print_state(State) -> State.
+ code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
- %%%----------------------------------------------------------------------
+ %%%===================================================================
%%% Internal functions
- %%%----------------------------------------------------------------------
-
- -spec send_text(state(), iodata()) -> ok.
- send_text(StateData, Text) ->
- (StateData#state.sockmod):send(StateData#state.socket,
- Text).
-
- -spec send_element(state(), xmpp_element()) -> ok.
- send_element(StateData, El) ->
- El1 = xmpp:encode(El, ?NS_SERVER),
- send_text(StateData, fxml:element_to_binary(El1)).
-
- -spec send_error(state(), xmlel() | stanza(), stanza_error()) -> ok.
- send_error(StateData, Stanza, Error) ->
- Type = xmpp:get_type(Stanza),
- if Type == error; Type == result;
- Type == <<"error">>; Type == <<"result">> ->
- ok;
+ %%%===================================================================
+ -spec check_from_to(jid(), jid(), state()) -> ok | {error, stream_error()}.
+ check_from_to(From, To, State) ->
+ case check_from(From, State) of
- true ->
+ true ->
- send_element(StateData, xmpp:make_error(Stanza, Error))
- end.
-
- -spec send_trailer(state()) -> ok.
- send_trailer(StateData) ->
- send_text(StateData, <<"</stream:stream>">>).
-
- -spec send_header(state(), undefined | {integer(), integer()}) -> ok.
- send_header(StateData, Version) ->
- Header = xmpp:encode(
- #stream_start{xmlns = ?NS_SERVER,
- stream_xmlns = ?NS_STREAM,
- db_xmlns = ?NS_SERVER_DIALBACK,
- id = StateData#state.streamid,
- version = Version}),
- send_text(StateData, fxml:element_to_header(Header)).
-
- -spec change_shaper(state(), binary(), jid()) -> ok.
- change_shaper(StateData, Host, JID) ->
- Shaper = acl:match_rule(Host, StateData#state.shaper,
- JID),
- (StateData#state.sockmod):change_shaper(StateData#state.socket,
- Shaper).
-
- -spec new_id() -> binary().
- new_id() -> randoms:get_string().
-
- -spec cancel_timer(reference()) -> ok.
- cancel_timer(Timer) ->
- erlang:cancel_timer(Timer),
- receive {timeout, Timer, _} -> ok after 0 -> ok end.
-
- fsm_limit_opts(Opts) ->
- case lists:keysearch(max_fsm_queue, 1, Opts) of
- {value, {_, N}} when is_integer(N) -> [{max_queue, N}];
- _ ->
- case ejabberd_config:get_option(
- max_fsm_queue,
- fun(I) when is_integer(I), I > 0 -> I end) of
- undefined -> [];
- N -> [{max_queue, N}]
- end
- end.
-
- -spec decode_element(xmlel() | xmpp_element(), state_name(), state()) -> fsm_transition().
- decode_element(#xmlel{} = El, StateName, StateData) ->
- Opts = if StateName == stream_established ->
- [ignore_els];
+ case check_to(To, State) of
- true ->
+ true ->
- []
- end,
- try xmpp:decode(El, ?NS_SERVER, Opts) of
- Pkt -> ?MODULE:StateName(Pkt, StateData)
- catch error:{xmpp_codec, Why} ->
- case xmpp:is_stanza(El) of
- true ->
- Lang = xmpp:get_lang(El),
- Txt = xmpp:format_error(Why),
- send_error(StateData, El, xmpp:err_bad_request(Txt, Lang));
+ ok;
- false ->
+ false ->
- ok
- end,
- {next_state, StateName, StateData}
- end;
- decode_element(Pkt, StateName, StateData) ->
- ?MODULE:StateName(Pkt, StateData).
-
- opt_type(domain_certfile) -> fun iolist_to_binary/1;
- opt_type(max_fsm_queue) ->
- fun (I) when is_integer(I), I > 0 -> I end;
- opt_type(s2s_certfile) -> fun iolist_to_binary/1;
- opt_type(s2s_cafile) -> 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_protocol_options) ->
- fun (Options) ->
- [_ | O] = lists:foldl(fun (X, Acc) -> X ++ Acc end, [],
- [["|" | binary_to_list(Opt)]
- || Opt <- Options, is_binary(Opt)]),
- iolist_to_binary(O)
- end;
- opt_type(s2s_tls_compression) ->
- fun (true) -> true;
- (false) -> false
- end;
- opt_type(s2s_use_starttls) ->
- fun (false) -> false;
- (true) -> true;
- (optional) -> optional;
- (required) -> required;
- (required_trusted) -> required_trusted
+ {error, xmpp:serr_host_unknown()}
- end;
+ end;
+ false ->
+ {error, xmpp:serr_invalid_from()}
+ end.
+
+ -spec check_from(jid(), state()) -> boolean().
+ check_from(#jid{lserver = S1}, #{auth_domains := AuthDomains}) ->
+ sets:is_element(S1, AuthDomains).
+
+ -spec check_to(jid(), state()) -> boolean().
+ check_to(#jid{lserver = LServer}, _State) ->
+ ejabberd_router:is_my_route(LServer).
+
+ -spec set_idle_timeout(state()) -> state().
+ set_idle_timeout(#{server_host := LServer,
+ established := true} = State) ->
+ Timeout = ejabberd_s2s:get_idle_timeout(LServer),
+ xmpp_stream_in:set_timeout(State, Timeout);
+ set_idle_timeout(State) ->
+ State.
+
+ -spec change_shaper(state(), binary()) -> ok.
+ change_shaper(#{shaper := ShaperName, server_host := ServerHost} = State,
+ RServer) ->
+ Shaper = acl:match_rule(ServerHost, ShaperName, jid:make(RServer)),
+ xmpp_stream_in:change_shaper(State, Shaper).
+
opt_type(_) ->
- [domain_certfile, max_fsm_queue, s2s_certfile, s2s_cafile,
- s2s_ciphers, s2s_dhfile, s2s_protocol_options,
- s2s_tls_compression, s2s_use_starttls].
+ [].
- %%%----------------------------------------------------------------------
- %%% File : ejabberd_s2s_out.erl
- %%% Author : Alexey Shchepin <alexey@process-one.net>
- %%% Purpose : Manage outgoing server-to-server connections
- %%% Created : 6 Dec 2002 by Alexey Shchepin <alexey@process-one.net>
+ %%%-------------------------------------------------------------------
+ %%% Created : 16 Dec 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2016 ProcessOne
+%%% 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
%%% 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_out).
-
+ -behaviour(xmpp_stream_out).
-behaviour(ejabberd_config).
- -author('alexey@process-one.net').
-
- -behaviour(p1_fsm).
-
- %% External exports
- -export([start/3,
- start_link/3,
- start_connection/1,
- terminate_if_waiting_delay/2,
- stop_connection/1,
- transform_options/1]).
-
- -export([init/1, open_socket/2, wait_for_stream/2,
- wait_for_validation/2, wait_for_features/2,
- wait_for_auth_result/2, wait_for_starttls_proceed/2,
- relay_to_bridge/2, reopen_socket/2, wait_before_retry/2,
- stream_established/2, handle_event/3,
- handle_sync_event/4, handle_info/3, terminate/3,
- print_state/1, code_change/4, test_get_addr_port/1,
- get_addr_port/1, opt_type/1]).
+ %% ejabberd_config callbacks
+ -export([opt_type/1, transform_options/1]).
+ %% xmpp_stream_out callbacks
+ -export([tls_options/1, tls_required/1, tls_verify/1, tls_enabled/1,
+ connect_timeout/1, address_families/1, default_port/1,
+ dns_retries/1, dns_timeout/1,
+ handle_auth_success/2, handle_auth_failure/3, handle_packet/2,
+ handle_stream_end/2, handle_stream_downgraded/2,
+ handle_recv/3, handle_send/3, handle_cdata/2,
+ handle_stream_established/1, handle_timeout/1]).
+ -export([init/1, handle_call/3, handle_cast/2, handle_info/2,
+ terminate/2, code_change/3]).
+ %% Hooks
+ -export([process_auth_result/2, process_closed/2, handle_unexpected_info/2,
+ handle_unexpected_cast/2, process_downgraded/2]).
+ %% API
+ -export([start/3, start_link/3, connect/1, close/1, stop/1, send/2,
+ route/2, establish/1, update_state/2, add_hooks/0]).
-include("ejabberd.hrl").
- -include("logger.hrl").
-include("xmpp.hrl").
+ -include("logger.hrl").
- -record(state,
- {socket :: ejabberd_socket:socket_state(),
- streamid = <<"">> :: binary(),
- remote_streamid = <<"">> :: binary(),
- use_v10 = true :: boolean(),
- tls = false :: boolean(),
- tls_required = false :: boolean(),
- tls_certverify = false :: boolean(),
- tls_enabled = false :: boolean(),
- tls_options = [connect] :: list(),
- authenticated = false :: boolean(),
- db_enabled = true :: boolean(),
- try_auth = true :: boolean(),
- myname = <<"">> :: binary(),
- server = <<"">> :: binary(),
- queue = queue:new() :: ?TQUEUE,
- delay_to_retry = undefined_delay :: undefined_delay | non_neg_integer(),
- new = false :: boolean(),
- verify = false :: false | {pid(), binary(), binary()},
- bridge :: {atom(), atom()},
- timer = make_ref() :: reference()}).
-
- -type state_name() :: open_socket | wait_for_stream |
- wait_for_validation | wait_for_features |
- wait_for_auth_result | wait_for_starttls_proceed |
- relay_to_bridge | reopen_socket | wait_before_retry |
- stream_established.
- -type state() :: #state{}.
- -type fsm_stop() :: {stop, normal, state()}.
- -type fsm_next() :: {next_state, state_name(), state(), non_neg_integer()} |
- {next_state, state_name(), state()}.
- -type fsm_transition() :: fsm_stop() | fsm_next().
-
- %%-define(DBGFSM, true).
-
- -ifdef(DBGFSM).
-
- -define(FSMOPTS, [{debug, [trace]}]).
-
- -else.
-
- -define(FSMOPTS, []).
-
- -endif.
-
- -define(FSMTIMEOUT, 30000).
-
- %% We do not block on send anymore.
- -define(TCP_SEND_TIMEOUT, 15000).
-
- %% Maximum delay to wait before retrying to connect after a failed attempt.
- %% Specified in miliseconds. Default value is 5 minutes.
- -define(MAX_RETRY_DELAY, 300000).
-
- -define(SOCKET_DEFAULT_RESULT, {error, badarg}).
+ -type state() :: map().
+ -export_type([state/0]).
- %%%----------------------------------------------------------------------
+ %%%===================================================================
%%% API
- %%%----------------------------------------------------------------------
- start(From, Host, Type) ->
- supervisor:start_child(ejabberd_s2s_out_sup,
- [From, Host, Type]).
-
- start_link(From, Host, Type) ->
- p1_fsm:start_link(ejabberd_s2s_out, [From, Host, Type],
- fsm_limit_opts() ++ (?FSMOPTS)).
-
- start_connection(Pid) -> p1_fsm:send_event(Pid, init).
-
- stop_connection(Pid) -> p1_fsm:send_event(Pid, closed).
-
- %%%----------------------------------------------------------------------
- %%% Callback functions from p1_fsm
- %%%----------------------------------------------------------------------
-
- init([From, Server, Type]) ->
- process_flag(trap_exit, true),
- ?DEBUG("started: ~p", [{From, Server, Type}]),
- {TLS, TLSRequired, TLSCertverify} =
- case ejabberd_config:get_option(
- s2s_use_starttls,
- fun(true) -> true;
- (false) -> false;
- (optional) -> optional;
- (required) -> required;
- (required_trusted) -> required_trusted
- end)
- of
- UseTls
- when (UseTls == undefined) or (UseTls == false) ->
- {false, false, false};
- UseTls
- when (UseTls == true) or (UseTls == optional) ->
- {true, false, false};
- required ->
- {true, true, false};
- required_trusted ->
- {true, true, true}
- end,
- UseV10 = TLS,
- TLSOpts1 = case
- ejabberd_config:get_option(
- s2s_certfile, fun iolist_to_binary/1)
- of
- undefined -> [connect];
- CertFile -> [{certfile, CertFile}, connect]
- end,
- TLSOpts2 = case ejabberd_config:get_option(
- s2s_ciphers, fun iolist_to_binary/1) of
- undefined -> TLSOpts1;
- Ciphers -> [{ciphers, Ciphers} | TLSOpts1]
- end,
- TLSOpts3 = case ejabberd_config:get_option(
- s2s_protocol_options,
- fun (Options) ->
- [_|O] = lists:foldl(
- fun(X, Acc) -> X ++ Acc end, [],
- [["|" | binary_to_list(Opt)] || Opt <- Options, is_binary(Opt)]
- ),
- iolist_to_binary(O)
- end) of
- undefined -> TLSOpts2;
- ProtocolOpts -> [{protocol_options, ProtocolOpts} | TLSOpts2]
- end,
- TLSOpts4 = case ejabberd_config:get_option(
- s2s_dhfile, fun iolist_to_binary/1) of
- undefined -> TLSOpts3;
- DHFile -> [{dhfile, DHFile} | TLSOpts3]
- end,
- TLSOpts = case ejabberd_config:get_option(
- {s2s_tls_compression, From},
- fun(true) -> true;
- (false) -> false
- end, false) of
- false -> [compression_none | TLSOpts4];
- true -> TLSOpts4
- end,
- {New, Verify} = case Type of
- new -> {true, false};
- {verify, Pid, Key, SID} ->
- start_connection(self()), {false, {Pid, Key, SID}}
- end,
- Timer = erlang:start_timer(?S2STIMEOUT, self(), []),
- {ok, open_socket,
- #state{use_v10 = UseV10, tls = TLS,
- tls_required = TLSRequired, tls_certverify = TLSCertverify,
- tls_options = TLSOpts, queue = queue:new(), myname = From,
- server = Server, new = New, verify = Verify, timer = Timer}}.
-
- open_socket(init, StateData) ->
- log_s2s_out(StateData#state.new, StateData#state.myname,
- StateData#state.server, StateData#state.tls),
- ?DEBUG("open_socket: ~p",
- [{StateData#state.myname, StateData#state.server,
- StateData#state.new, StateData#state.verify}]),
- AddrList = case
- ejabberd_idna:domain_utf8_to_ascii(StateData#state.server)
- of
- false -> [];
- ASCIIAddr -> get_addr_port(ASCIIAddr)
- end,
- case lists:foldl(fun ({Addr, Port}, Acc) ->
- case Acc of
- {ok, Socket} -> {ok, Socket};
- _ -> open_socket1(Addr, Port)
- end
- end,
- ?SOCKET_DEFAULT_RESULT, AddrList)
- of
- {ok, Socket} ->
- Version = if StateData#state.use_v10 -> {1,0};
- true -> undefined
- end,
- NewStateData = StateData#state{socket = Socket,
- tls_enabled = false,
- streamid = new_id()},
- send_header(NewStateData, Version),
- {next_state, wait_for_stream, NewStateData,
- ?FSMTIMEOUT};
- {error, Reason} ->
- ?INFO_MSG("s2s connection: ~s -> ~s (remote server "
- "not found: ~p)",
- [StateData#state.myname, StateData#state.server, Reason]),
- case ejabberd_hooks:run_fold(find_s2s_bridge, undefined,
- [StateData#state.myname,
- StateData#state.server])
- of
- {Mod, Fun, Type} ->
- ?INFO_MSG("found a bridge to ~s for: ~s -> ~s",
- [Type, StateData#state.myname,
- StateData#state.server]),
- NewStateData = StateData#state{bridge = {Mod, Fun}},
- {next_state, relay_to_bridge, NewStateData};
- _ -> wait_before_reconnect(StateData)
- end
- end;
- open_socket(Event, StateData) ->
- handle_unexpected_event(Event, open_socket, StateData).
-
- open_socket1({_, _, _, _} = Addr, Port) ->
- open_socket2(inet, Addr, Port);
- %% IPv6
- open_socket1({_, _, _, _, _, _, _, _} = Addr, Port) ->
- open_socket2(inet6, Addr, Port);
- %% Hostname
- open_socket1(Host, Port) ->
- lists:foldl(fun (_Family, {ok, _Socket} = R) -> R;
- (Family, _) ->
- Addrs = get_addrs(Host, Family),
- lists:foldl(fun (_Addr, {ok, _Socket} = R) -> R;
- (Addr, _) -> open_socket1(Addr, Port)
- end,
- ?SOCKET_DEFAULT_RESULT, Addrs)
- end,
- ?SOCKET_DEFAULT_RESULT, outgoing_s2s_families()).
-
- open_socket2(Type, Addr, Port) ->
- ?DEBUG("s2s_out: connecting to ~p:~p~n", [Addr, Port]),
- Timeout = outgoing_s2s_timeout(),
- case catch ejabberd_socket:connect(Addr, Port,
- [binary, {packet, 0},
- {send_timeout, ?TCP_SEND_TIMEOUT},
- {send_timeout_close, true},
- {active, false}, Type],
- Timeout)
- of
- {ok, _Socket} = R -> R;
- {error, Reason} = R ->
- ?DEBUG("s2s_out: connect return ~p~n", [Reason]), R;
- {'EXIT', Reason} ->
- ?DEBUG("s2s_out: connect crashed ~p~n", [Reason]),
- {error, Reason}
- end.
-
- %%----------------------------------------------------------------------
-
- wait_for_stream({xmlstreamstart, Name, Attrs}, StateData0) ->
- {CertCheckRes, CertCheckMsg, StateData} =
- if StateData0#state.tls_certverify, StateData0#state.tls_enabled ->
- {Res, Msg} =
- ejabberd_s2s:check_peer_certificate(ejabberd_socket,
- StateData0#state.socket,
- StateData0#state.server),
- ?DEBUG("Certificate verification result for ~s: ~s",
- [StateData0#state.server, Msg]),
- {Res, Msg, StateData0#state{tls_certverify = false}};
+ %%%===================================================================
+ start(From, To, Opts) ->
+ case proplists:get_value(supervisor, Opts, true) of
- true ->
+ true ->
- {no_verify, <<"Not verified">>, StateData0}
- end,
- try xmpp:decode(#xmlel{name = Name, attrs = Attrs}) of
- _ when CertCheckRes == error ->
- send_element(StateData,
- xmpp:serr_policy_violation(CertCheckMsg, ?MYLANG)),
- ?INFO_MSG("Closing s2s connection: ~s -> ~s (~s)",
- [StateData#state.myname, StateData#state.server,
- CertCheckMsg]),
- {stop, normal, StateData};
- #stream_start{xmlns = NS_SERVER, stream_xmlns = NS_STREAM}
- when NS_SERVER /= ?NS_SERVER; NS_STREAM /= ?NS_STREAM ->
- send_element(StateData, xmpp:serr_invalid_namespace()),
- {stop, normal, StateData};
- #stream_start{db_xmlns = ?NS_SERVER_DIALBACK, id = ID,
- version = V} when V /= {1,0} ->
- send_db_request(StateData#state{remote_streamid = ID});
- #stream_start{db_xmlns = ?NS_SERVER_DIALBACK, id = ID}
- when StateData#state.use_v10 ->
- {next_state, wait_for_features,
- StateData#state{remote_streamid = ID}, ?FSMTIMEOUT};
- #stream_start{db_xmlns = ?NS_SERVER_DIALBACK, id = ID}
- when not StateData#state.use_v10 ->
- %% Handle Tigase's workaround for an old ejabberd bug:
- send_db_request(StateData#state{remote_streamid = ID});
- #stream_start{id = ID} when StateData#state.use_v10 ->
- {next_state, wait_for_features,
- StateData#state{db_enabled = false, remote_streamid = ID},
- ?FSMTIMEOUT};
- #stream_start{} ->
- send_element(StateData, xmpp:serr_invalid_namespace()),
- {stop, normal, StateData};
- _ ->
- send_element(StateData, xmpp:serr_invalid_xml()),
- {stop, normal, StateData}
- catch _:{xmpp_codec, Why} ->
- Txt = xmpp:format_error(Why),
- send_element(StateData, xmpp:serr_invalid_xml(Txt, ?MYLANG)),
- {stop, normal, StateData}
- end;
- wait_for_stream(Event, StateData) ->
- handle_unexpected_event(Event, wait_for_stream, StateData).
-
- wait_for_validation({xmlstreamelement, El}, StateData) ->
- decode_element(El, wait_for_validation, StateData);
- wait_for_validation(#db_result{to = To, from = From, type = Type}, StateData) ->
- ?DEBUG("recv result: ~p", [{From, To, Type}]),
- case {Type, StateData#state.tls_enabled, StateData#state.tls_required} of
- {valid, Enabled, Required} when (Enabled == true) or (Required == false) ->
- send_queue(StateData, StateData#state.queue),
- ?INFO_MSG("Connection established: ~s -> ~s with "
- "TLS=~p",
- [StateData#state.myname, StateData#state.server,
- StateData#state.tls_enabled]),
- ejabberd_hooks:run(s2s_connect_hook,
- [StateData#state.myname,
- StateData#state.server]),
- {next_state, stream_established, StateData#state{queue = queue:new()}};
- {valid, Enabled, Required} when (Enabled == false) and (Required == true) ->
- ?INFO_MSG("Closing s2s connection: ~s -> ~s (TLS "
- "is required but unavailable)",
- [StateData#state.myname, StateData#state.server]),
- {stop, normal, StateData};
- _ ->
- ?INFO_MSG("Closing s2s connection: ~s -> ~s (invalid "
- "dialback key result)",
- [StateData#state.myname, StateData#state.server]),
- {stop, normal, StateData}
- end;
- wait_for_validation(#db_verify{to = To, from = From, id = Id, type = Type},
- StateData) ->
- ?DEBUG("recv verify: ~p", [{From, To, Id, Type}]),
- case StateData#state.verify of
- false ->
- NextState = wait_for_validation,
- {next_state, NextState, StateData, get_timeout_interval(NextState)};
- {Pid, _Key, _SID} ->
- case Type of
- valid ->
- p1_fsm:send_event(Pid,
- {valid, StateData#state.server,
- StateData#state.myname});
+ supervisor:start_child(ejabberd_s2s_out_sup,
+ [From, To, Opts]);
- _ ->
+ _ ->
- p1_fsm:send_event(Pid,
- {invalid, StateData#state.server,
- StateData#state.myname})
- end,
- if StateData#state.verify == false ->
- {stop, normal, StateData};
- true ->
- NextState = wait_for_validation,
- {next_state, NextState, StateData, get_timeout_interval(NextState)}
- end
- end;
- wait_for_validation(timeout,
- #state{verify = {VPid, VKey, SID}} = StateData)
- when is_pid(VPid) and is_binary(VKey) and is_binary(SID) ->
- ?DEBUG("wait_for_validation: ~s -> ~s (timeout in verify connection)",
- [StateData#state.myname, StateData#state.server]),
- {stop, normal, StateData};
- wait_for_validation(Event, StateData) ->
- handle_unexpected_event(Event, wait_for_validation, StateData).
-
- wait_for_features({xmlstreamelement, El}, StateData) ->
- decode_element(El, wait_for_features, StateData);
- wait_for_features(#stream_features{sub_els = Els}, StateData) ->
- {SASLEXT, StartTLS, StartTLSRequired} =
- lists:foldl(
- fun(#sasl_mechanisms{list = Mechs}, {_, STLS, STLSReq}) ->
- {lists:member(<<"EXTERNAL">>, Mechs), STLS, STLSReq};
- (#starttls{required = Required}, {SEXT, _, _}) ->
- {SEXT, true, Required};
- (_, Acc) ->
- Acc
- end, {false, false, false}, Els),
- if not SASLEXT and not StartTLS and StateData#state.authenticated ->
- send_queue(StateData, StateData#state.queue),
- ?INFO_MSG("Connection established: ~s -> ~s with "
- "SASL EXTERNAL and TLS=~p",
- [StateData#state.myname, StateData#state.server,
- StateData#state.tls_enabled]),
- ejabberd_hooks:run(s2s_connect_hook,
- [StateData#state.myname,
- StateData#state.server]),
- {next_state, stream_established,
- StateData#state{queue = queue:new()}};
- SASLEXT and StateData#state.try_auth and
- (StateData#state.new /= false) and
- (StateData#state.tls_enabled or
- not StateData#state.tls_required) ->
- send_element(StateData,
- #sasl_auth{mechanism = <<"EXTERNAL">>,
- text = StateData#state.myname}),
- {next_state, wait_for_auth_result,
- StateData#state{try_auth = false}, ?FSMTIMEOUT};
- StartTLS and StateData#state.tls and
- not StateData#state.tls_enabled ->
- send_element(StateData, #starttls{}),
- {next_state, wait_for_starttls_proceed, StateData, ?FSMTIMEOUT};
- StartTLSRequired and not StateData#state.tls ->
- ?DEBUG("restarted: ~p",
- [{StateData#state.myname, StateData#state.server}]),
- ejabberd_socket:close(StateData#state.socket),
- {next_state, reopen_socket,
- StateData#state{socket = undefined, use_v10 = false},
- ?FSMTIMEOUT};
- StateData#state.db_enabled ->
- send_db_request(StateData);
- true ->
- ?DEBUG("restarted: ~p",
- [{StateData#state.myname, StateData#state.server}]),
- ejabberd_socket:close(StateData#state.socket),
- {next_state, reopen_socket,
- StateData#state{socket = undefined, use_v10 = false}, ?FSMTIMEOUT}
- end;
- wait_for_features(Event, StateData) ->
- handle_unexpected_event(Event, wait_for_features, StateData).
-
- wait_for_auth_result({xmlstreamelement, El}, StateData) ->
- decode_element(El, wait_for_auth_result, StateData);
- wait_for_auth_result(#sasl_success{}, StateData) ->
- ?DEBUG("auth: ~p", [{StateData#state.myname, StateData#state.server}]),
- ejabberd_socket:reset_stream(StateData#state.socket),
- send_header(StateData, {1,0}),
- {next_state, wait_for_stream,
- StateData#state{streamid = new_id(), authenticated = true},
- ?FSMTIMEOUT};
- wait_for_auth_result(#sasl_failure{}, StateData) ->
- ?DEBUG("restarted: ~p", [{StateData#state.myname, StateData#state.server}]),
- ejabberd_socket:close(StateData#state.socket),
- {next_state, reopen_socket,
- StateData#state{socket = undefined}, ?FSMTIMEOUT};
- wait_for_auth_result(Event, StateData) ->
- handle_unexpected_event(Event, wait_for_auth_result, StateData).
-
- wait_for_starttls_proceed({xmlstreamelement, El}, StateData) ->
- decode_element(El, wait_for_starttls_proceed, StateData);
- wait_for_starttls_proceed(#starttls_proceed{}, StateData) ->
- ?DEBUG("starttls: ~p", [{StateData#state.myname, StateData#state.server}]),
- Socket = StateData#state.socket,
- TLSOpts = case ejabberd_config:get_option(
- {domain_certfile, StateData#state.myname},
- fun iolist_to_binary/1) of
- undefined -> StateData#state.tls_options;
- CertFile ->
- [{certfile, CertFile}
- | lists:keydelete(certfile, 1,
- StateData#state.tls_options)]
- end,
- TLSSocket = ejabberd_socket:starttls(Socket, TLSOpts),
- NewStateData = StateData#state{socket = TLSSocket,
- streamid = new_id(),
- tls_enabled = true,
- tls_options = TLSOpts},
- send_header(NewStateData, {1,0}),
- {next_state, wait_for_stream, NewStateData, ?FSMTIMEOUT};
- wait_for_starttls_proceed(Event, StateData) ->
- handle_unexpected_event(Event, wait_for_starttls_proceed, StateData).
+ xmpp_stream_out:start(?MODULE, [ejabberd_socket, From, To, Opts],
+ ejabberd_config:fsm_limit_opts([]))
+ end.
- reopen_socket({xmlstreamelement, _El}, StateData) ->
- {next_state, reopen_socket, StateData, ?FSMTIMEOUT};
- reopen_socket({xmlstreamend, _Name}, StateData) ->
- {next_state, reopen_socket, StateData, ?FSMTIMEOUT};
- reopen_socket({xmlstreamerror, _}, StateData) ->
- {next_state, reopen_socket, StateData, ?FSMTIMEOUT};
- reopen_socket(timeout, StateData) ->
- ?INFO_MSG("reopen socket: timeout", []),
- {stop, normal, StateData};
- reopen_socket(closed, StateData) ->
- p1_fsm:send_event(self(), init),
- {next_state, open_socket, StateData, ?FSMTIMEOUT}.
+ start_link(From, To, Opts) ->
+ xmpp_stream_out:start_link(?MODULE, [ejabberd_socket, From, To, Opts],
+ ejabberd_config:fsm_limit_opts([])).
+
+ connect(Ref) ->
+ xmpp_stream_out:connect(Ref).
+
+ close(Ref) ->
+ xmpp_stream_out:close(Ref).
+
+ stop(Ref) ->
+ xmpp_stream_out:stop(Ref).
+
+ -spec send(pid(), xmpp_element()) -> ok;
+ (state(), xmpp_element()) -> state().
+ send(Stream, Pkt) ->
+ xmpp_stream_out:send(Stream, Pkt).
+
+ -spec route(pid(), xmpp_element()) -> ok.
+ route(Ref, Pkt) ->
+ Ref ! {route, Pkt}.
+
+ -spec establish(state()) -> state().
+ establish(State) ->
+ xmpp_stream_out:establish(State).
+
+ -spec update_state(pid(), fun((state()) -> state()) |
+ {module(), atom(), list()}) -> ok.
+ update_state(Ref, Callback) ->
+ xmpp_stream_out:cast(Ref, {update_state, Callback}).
+
+ -spec add_hooks() -> ok.
+ add_hooks() ->
+ lists:foreach(
+ fun(Host) ->
+ ejabberd_hooks:add(s2s_out_auth_result, Host, ?MODULE,
+ process_auth_result, 100),
+ ejabberd_hooks:add(s2s_out_closed, Host, ?MODULE,
+ process_closed, 100),
+ ejabberd_hooks:add(s2s_out_handle_info, Host, ?MODULE,
+ handle_unexpected_info, 100),
+ ejabberd_hooks:add(s2s_out_handle_cast, Host, ?MODULE,
+ handle_unexpected_cast, 100),
+ ejabberd_hooks:add(s2s_out_downgraded, Host, ?MODULE,
+ process_downgraded, 100)
+ end, ?MYHOSTS).
+
+ %%%===================================================================
+ %%% Hooks
+ %%%===================================================================
+ process_auth_result(#{server := LServer, remote_server := RServer} = State,
+ {false, Reason}) ->
+ Delay = get_delay(),
+ ?INFO_MSG("Failed to establish outbound s2s connection ~s -> ~s: "
+ "authentication failed; bouncing for ~p seconds",
+ [LServer, RServer, Delay]),
+ State1 = State#{on_route => bounce, stop_reason => Reason},
+ State2 = close(State1),
+ State3 = bounce_queue(State2),
+ xmpp_stream_out:set_timeout(State3, timer:seconds(Delay));
+ process_auth_result(State, true) ->
+ State.
+
+ process_closed(#{server := LServer, remote_server := RServer,
+ on_route := send} = State,
+ Reason) ->
+ ?INFO_MSG("Closing outbound s2s connection ~s -> ~s: ~s",
+ [LServer, RServer, xmpp_stream_out:format_error(Reason)]),
+ stop(State);
+ process_closed(#{server := LServer, remote_server := RServer} = State,
+ Reason) ->
+ Delay = get_delay(),
+ ?INFO_MSG("Failed to establish outbound s2s connection ~s -> ~s: ~s; "
+ "bouncing for ~p seconds",
+ [LServer, RServer, xmpp_stream_out:format_error(Reason), Delay]),
+ State1 = State#{on_route => bounce},
+ State2 = bounce_queue(State1),
+ xmpp_stream_out:set_timeout(State2, timer:seconds(Delay)).
+
+ handle_unexpected_info(State, Info) ->
+ ?WARNING_MSG("got unexpected info: ~p", [Info]),
+ State.
+
+ handle_unexpected_cast(State, Msg) ->
+ ?WARNING_MSG("got unexpected cast: ~p", [Msg]),
+ State.
+
+ process_downgraded(State, _StreamStart) ->
+ send(State, xmpp:serr_unsupported_version()).
+
+ %%%===================================================================
+ %%% gen_server callbacks
+ %%%===================================================================
+ tls_options(#{server := LServer}) ->
+ ejabberd_s2s:tls_options(LServer, []).
+
+ tls_required(#{server := LServer}) ->
+ ejabberd_s2s:tls_required(LServer).
+
+ tls_verify(#{server := LServer}) ->
+ ejabberd_s2s:tls_verify(LServer).
+
+ tls_enabled(#{server := LServer}) ->
+ ejabberd_s2s:tls_enabled(LServer).
+
+ connect_timeout(#{server := LServer}) ->
+ ejabberd_config:get_option(
+ {outgoing_s2s_timeout, LServer},
+ fun(TimeOut) when is_integer(TimeOut), TimeOut > 0 ->
+ timer:seconds(TimeOut);
+ (infinity) ->
+ infinity
+ end, timer:seconds(10)).
- %% This state is use to avoid reconnecting to often to bad sockets
- wait_before_retry(_Event, StateData) ->
- {next_state, wait_before_retry, StateData, ?FSMTIMEOUT}.
+ default_port(#{server := LServer}) ->
+ ejabberd_config:get_option(
+ {outgoing_s2s_port, LServer},
+ fun(I) when is_integer(I), I > 0, I =< 65536 -> I end,
+ 5269).
- relay_to_bridge(stop, StateData) ->
- wait_before_reconnect(StateData);
- relay_to_bridge(closed, StateData) ->
- ?INFO_MSG("relay to bridge: ~s -> ~s (closed)",
- [StateData#state.myname, StateData#state.server]),
- {stop, normal, StateData};
- relay_to_bridge(_Event, StateData) ->
- {next_state, relay_to_bridge, StateData}.
+ address_families(#{server := LServer}) ->
+ ejabberd_config:get_option(
+ {outgoing_s2s_families, LServer},
+ fun(Families) ->
+ lists:map(
+ fun(ipv4) -> inet;
+ (ipv6) -> inet6
+ end, Families)
+ end, [inet, inet6]).
- stream_established({xmlstreamelement, El}, StateData) ->
- decode_element(El, stream_established, StateData);
- stream_established(#db_verify{to = VTo, from = VFrom, id = VId, type = VType},
- StateData) ->
- ?DEBUG("recv verify: ~p", [{VFrom, VTo, VId, VType}]),
- case StateData#state.verify of
- {VPid, _VKey, _SID} ->
- case VType of
- valid ->
- p1_fsm:send_event(VPid,
- {valid, StateData#state.server,
- StateData#state.myname});
- _ ->
- p1_fsm:send_event(VPid,
- {invalid, StateData#state.server,
- StateData#state.myname})
- end;
- _ -> ok
- end,
- {next_state, stream_established, StateData};
- stream_established(Event, StateData) ->
- handle_unexpected_event(Event, stream_established, StateData).
+ dns_retries(#{server := LServer}) ->
+ ejabberd_config:get_option(
+ {s2s_dns_retries, LServer},
+ fun(I) when is_integer(I), I>=0 -> I end,
+ 2).
- -spec handle_unexpected_event(term(), state_name(), state()) -> fsm_transition().
- handle_unexpected_event(Event, StateName, StateData) ->
- case Event of
- {xmlstreamerror, _} ->
- send_element(StateData, xmpp:serr_not_well_formed()),
- ?INFO_MSG("Closing s2s connection ~s -> ~s in state ~s: "
- "got invalid XML from peer",
- [StateData#state.myname, StateData#state.server,
- StateName]),
- {stop, normal, StateData};
- {xmlstreamend, _} ->
- ?INFO_MSG("Closing s2s connection ~s -> ~s in state ~s: "
- "XML stream closed by peer",
- [StateData#state.myname, StateData#state.server,
- StateName]),
- {stop, normal, StateData};
- timeout ->
- send_element(StateData, xmpp:serr_connection_timeout()),
- ?INFO_MSG("Closing s2s connection ~s -> ~s in state ~s: "
- "timed out during establishing an XML stream",
- [StateData#state.myname, StateData#state.server,
- StateName]),
- {stop, normal, StateData};
- closed ->
- ?INFO_MSG("Closing s2s connection ~s -> ~s in state ~s: "
- "connection socket closed",
- [StateData#state.myname, StateData#state.server,
- StateName]),
- {stop, normal, StateData};
- Pkt when StateName == wait_for_stream;
- StateName == wait_for_features;
- StateName == wait_for_auth_result;
- StateName == wait_for_starttls_proceed ->
- send_element(StateData, xmpp:serr_bad_format()),
- ?INFO_MSG("Closing s2s connection ~s -> ~s in state ~s: "
- "got unexpected event ~p",
- [StateData#state.myname, StateData#state.server,
- StateName, Pkt]),
- {stop, normal, StateData};
- _ ->
- {next_state, StateName, StateData, get_timeout_interval(StateName)}
+ dns_timeout(#{server := LServer}) ->
+ ejabberd_config:get_option(
+ {s2s_dns_timeout, LServer},
+ fun(I) when is_integer(I), I>=0 ->
+ timer:seconds(I);
+ (infinity) ->
+ infinity
+ end, timer:seconds(10)).
+
+ handle_auth_success(Mech, #{sockmod := SockMod,
+ socket := Socket, ip := IP,
+ remote_server := RServer,
+ server := LServer} = State) ->
+ ?INFO_MSG("(~s) Accepted outbound s2s ~s authentication ~s -> ~s (~s)",
+ [SockMod:pp(Socket), Mech, LServer, RServer,
+ ejabberd_config:may_hide_data(jlib:ip_to_list(IP))]),
+ ejabberd_hooks:run_fold(s2s_out_auth_result, LServer, State, [true]).
+
+ handle_auth_failure(Mech, Reason,
+ #{sockmod := SockMod,
+ socket := Socket, ip := IP,
+ remote_server := RServer,
+ server := LServer} = State) ->
+ ?INFO_MSG("(~s) Failed outbound s2s ~s authentication ~s -> ~s (~s): ~s",
+ [SockMod:pp(Socket), Mech, LServer, RServer,
+ ejabberd_config:may_hide_data(jlib:ip_to_list(IP)),
+ xmpp_stream_out:format_error(Reason)]),
+ ejabberd_hooks:run_fold(s2s_out_auth_result, LServer, State, [{false, Reason}]).
+
+ handle_packet(Pkt, #{server := LServer} = State) ->
+ ejabberd_hooks:run_fold(s2s_out_packet, LServer, State, [Pkt]).
+
+ handle_stream_end(Reason, #{server := LServer} = State) ->
+ State1 = State#{stop_reason => Reason},
+ ejabberd_hooks:run_fold(s2s_out_closed, LServer, State1, [Reason]).
+
+ handle_stream_downgraded(StreamStart, #{server := LServer} = State) ->
+ ejabberd_hooks:run_fold(s2s_out_downgraded, LServer, State, [StreamStart]).
+
+ handle_stream_established(State) ->
+ State1 = State#{on_route => send},
+ State2 = resend_queue(State1),
+ set_idle_timeout(State2).
+
+ handle_cdata(Data, #{server := LServer} = State) ->
+ ejabberd_hooks:run_fold(s2s_out_handle_cdata, LServer, State, [Data]).
+
+ handle_recv(El, Pkt, #{server := LServer} = State) ->
+ ejabberd_hooks:run_fold(s2s_out_handle_recv, LServer, State, [El, Pkt]).
+
+ handle_send(El, Pkt, #{server := LServer} = State) ->
+ ejabberd_hooks:run_fold(s2s_out_handle_send, LServer, State, [El, Pkt]).
+
+ handle_timeout(#{on_route := Action} = State) ->
+ case Action of
+ bounce -> stop(State);
+ _ -> send(State, xmpp:serr_connection_timeout())
end.
- %%----------------------------------------------------------------------
- %% Func: StateName/3
- %% Returns: {next_state, NextStateName, NextStateData} |
- %% {next_state, NextStateName, NextStateData, Timeout} |
- %% {reply, Reply, NextStateName, NextStateData} |
- %% {reply, Reply, NextStateName, NextStateData, Timeout} |
- %% {stop, Reason, NewStateData} |
- %% {stop, Reason, Reply, NewStateData}
- %%----------------------------------------------------------------------
- %%state_name(Event, From, StateData) ->
- %% Reply = ok,
- %% {reply, Reply, state_name, StateData}.
-
- handle_event(_Event, StateName, StateData) ->
- {next_state, StateName, StateData,
- get_timeout_interval(StateName)}.
-
- handle_sync_event(get_state_infos, _From, StateName,
- StateData) ->
- {Addr, Port} = try
- ejabberd_socket:peername(StateData#state.socket)
- of
- {ok, {A, P}} -> {A, P};
- {error, _} -> {unknown, unknown}
- catch
- _:_ -> {unknown, unknown}
- end,
- Infos = [{direction, out}, {statename, StateName},
- {addr, Addr}, {port, Port},
- {streamid, StateData#state.streamid},
- {use_v10, StateData#state.use_v10},
- {tls, StateData#state.tls},
- {tls_required, StateData#state.tls_required},
- {tls_enabled, StateData#state.tls_enabled},
- {tls_options, StateData#state.tls_options},
- {authenticated, StateData#state.authenticated},
- {db_enabled, StateData#state.db_enabled},
- {try_auth, StateData#state.try_auth},
- {myname, StateData#state.myname},
- {server, StateData#state.server},
- {delay_to_retry, StateData#state.delay_to_retry},
- {verify, StateData#state.verify}],
- Reply = {state_infos, Infos},
- {reply, Reply, StateName, StateData};
- %%----------------------------------------------------------------------
- %% Func: handle_sync_event/4
- %% Returns: {next_state, NextStateName, NextStateData} |
- %% {next_state, NextStateName, NextStateData, Timeout} |
- %% {reply, Reply, NextStateName, NextStateData} |
- %% {reply, Reply, NextStateName, NextStateData, Timeout} |
- %% {stop, Reason, NewStateData} |
- %% {stop, Reason, Reply, NewStateData}
- %%----------------------------------------------------------------------
- handle_sync_event(_Event, _From, StateName,
- StateData) ->
- Reply = ok,
- {reply, Reply, StateName, StateData,
- get_timeout_interval(StateName)}.
-
- code_change(_OldVsn, StateName, StateData, _Extra) ->
- {ok, StateName, StateData}.
-
- handle_info({send_text, Text}, StateName, StateData) ->
- send_text(StateData, Text),
- cancel_timer(StateData#state.timer),
- Timer = erlang:start_timer(?S2STIMEOUT, self(), []),
- {next_state, StateName, StateData#state{timer = Timer},
- get_timeout_interval(StateName)};
- handle_info({send_element, El}, StateName, StateData) ->
- case StateName of
- stream_established ->
- cancel_timer(StateData#state.timer),
- Timer = erlang:start_timer(?S2STIMEOUT, self(), []),
- send_element(StateData, El),
- {next_state, StateName, StateData#state{timer = Timer}};
- %% In this state we bounce all message: We are waiting before
- %% trying to reconnect
- wait_before_retry ->
- bounce_element(El, xmpp:err_remote_server_not_found()),
- {next_state, StateName, StateData};
- relay_to_bridge ->
- {Mod, Fun} = StateData#state.bridge,
- ?DEBUG("relaying stanza via ~p:~p/1", [Mod, Fun]),
- case catch Mod:Fun(El) of
- {'EXIT', Reason} ->
- ?ERROR_MSG("Error while relaying to bridge: ~p",
- [Reason]),
- bounce_element(El, xmpp:err_internal_server_error()),
- wait_before_reconnect(StateData);
- _ -> {next_state, StateName, StateData}
+ init([#{server := LServer, remote_server := RServer} = State, Opts]) ->
+ State1 = State#{on_route => queue,
+ queue => queue:new(),
+ xmlns => ?NS_SERVER,
+ lang => ?MYLANG,
+ shaper => none},
+ ?INFO_MSG("Outbound s2s connection started: ~s -> ~s",
+ [LServer, RServer]),
+ ejabberd_hooks:run_fold(s2s_out_init, LServer, {ok, State1}, [Opts]).
+
+ handle_call(Request, From, #{server := LServer} = State) ->
+ ejabberd_hooks:run_fold(s2s_out_handle_call, LServer, State, [Request, From]).
+
+ handle_cast({update_state, Fun}, State) ->
+ case Fun of
+ {M, F, A} -> erlang:apply(M, F, [State|A]);
+ _ when is_function(Fun) -> Fun(State)
- end;
+ end;
- _ ->
- Q = queue:in(El, StateData#state.queue),
- {next_state, StateName, StateData#state{queue = Q},
- get_timeout_interval(StateName)}
+ handle_cast(Msg, #{server := LServer} = State) ->
+ ejabberd_hooks:run_fold(s2s_out_handle_cast, LServer, State, [Msg]).
+
+ handle_info({route, Pkt}, #{queue := Q, on_route := Action} = State) ->
+ case Action of
+ queue -> State#{queue => queue:in(Pkt, Q)};
+ bounce -> bounce_packet(Pkt, State);
+ send -> set_idle_timeout(send(State, Pkt))
end;
- handle_info({timeout, Timer, _}, wait_before_retry,
- #state{timer = Timer} = StateData) ->
- ?INFO_MSG("Reconnect delay expired: Will now retry "
- "to connect to ~s when needed.",
- [StateData#state.server]),
- {stop, normal, StateData};
- handle_info({timeout, Timer, _}, _StateName,
- #state{timer = Timer} = StateData) ->
- ?INFO_MSG("Closing connection with ~s: timeout",
- [StateData#state.server]),
- {stop, normal, StateData};
- handle_info(terminate_if_waiting_before_retry,
- wait_before_retry, StateData) ->
- {stop, normal, StateData};
- handle_info(terminate_if_waiting_before_retry,
- StateName, StateData) ->
- {next_state, StateName, StateData,
- get_timeout_interval(StateName)};
- handle_info(_, StateName, StateData) ->
- {next_state, StateName, StateData,
- get_timeout_interval(StateName)}.
-
- terminate(Reason, StateName, StateData) ->
- ?DEBUG("terminated: ~p", [{Reason, StateName}]),
- case StateData#state.new of
- false -> ok;
- true ->
- ejabberd_s2s:remove_connection({StateData#state.myname,
- StateData#state.server},
- self())
+ handle_info(Info, #{server := LServer} = State) ->
+ ejabberd_hooks:run_fold(s2s_out_handle_info, LServer, State, [Info]).
+
+ terminate(Reason, #{server := LServer,
+ remote_server := RServer} = State) ->
+ ejabberd_s2s:remove_connection({LServer, RServer}, self()),
+ State1 = case Reason of
+ normal -> State;
+ _ -> State#{stop_reason => internal_failure}
- end,
+ end,
- bounce_queue(StateData#state.queue, xmpp:err_remote_server_not_found()),
- bounce_messages(xmpp:err_remote_server_not_found()),
- case StateData#state.socket of
- undefined -> ok;
- _Socket ->
- catch send_trailer(StateData),
- ejabberd_socket:close(StateData#state.socket)
- end,
- ok.
+ bounce_queue(State1),
+ bounce_message_queue(State1).
- print_state(State) -> State.
+ code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
- %%%----------------------------------------------------------------------
+ %%%===================================================================
%%% Internal functions
- %%%----------------------------------------------------------------------
-
- -spec send_text(state(), iodata()) -> ok.
- send_text(StateData, Text) ->
- ?DEBUG("Send Text on stream = ~s", [Text]),
- ejabberd_socket:send(StateData#state.socket, Text).
-
- -spec send_element(state(), xmpp_element()) -> ok.
- send_element(StateData, El) ->
- El1 = xmpp:encode(El, ?NS_SERVER),
- send_text(StateData, fxml:element_to_binary(El1)).
-
- -spec send_header(state(), undefined | {integer(), integer()}) -> ok.
- send_header(StateData, Version) ->
- Header = xmpp:encode(
- #stream_start{xmlns = ?NS_SERVER,
- stream_xmlns = ?NS_STREAM,
- db_xmlns = ?NS_SERVER_DIALBACK,
- from = jid:make(StateData#state.myname),
- to = jid:make(StateData#state.server),
- version = Version}),
- send_text(StateData, fxml:element_to_header(Header)).
-
- -spec send_trailer(state()) -> ok.
- send_trailer(StateData) ->
- send_text(StateData, <<"</stream:stream>">>).
-
- -spec send_queue(state(), queue:queue()) -> ok.
- send_queue(StateData, Q) ->
- case queue:out(Q) of
- {{value, El}, Q1} ->
- send_element(StateData, El), send_queue(StateData, Q1);
- {empty, _Q1} -> ok
+ %%%===================================================================
+ -spec resend_queue(state()) -> state().
+ resend_queue(#{queue := Q} = State) ->
+ State1 = State#{queue => queue:new()},
+ jlib:queue_foldl(
+ fun(Pkt, AccState) ->
+ send(AccState, Pkt)
+ end, State1, Q).
+
+ -spec bounce_queue(state()) -> state().
+ bounce_queue(#{queue := Q} = State) ->
+ State1 = State#{queue => queue:new()},
+ jlib:queue_foldl(
+ fun(Pkt, AccState) ->
+ bounce_packet(Pkt, AccState)
+ end, State1, Q).
+
+ -spec bounce_message_queue(state()) -> state().
+ bounce_message_queue(State) ->
+ receive {route, Pkt} ->
+ State1 = bounce_packet(Pkt, State),
+ bounce_message_queue(State1)
+ after 0 ->
+ State
end.
- %% Bounce a single message (xmlelement)
- -spec bounce_element(stanza(), stanza_error()) -> ok.
- bounce_element(El, Error) ->
- From = xmpp:get_from(El),
- To = xmpp:get_to(El),
- ejabberd_router:route_error(To, From, El, Error).
-
- -spec bounce_queue(queue:queue(), stanza_error()) -> ok.
- bounce_queue(Q, Error) ->
- case queue:out(Q) of
- {{value, El}, Q1} ->
- bounce_element(El, Error), bounce_queue(Q1, Error);
- {empty, _} -> ok
- end.
-
- -spec new_id() -> binary().
- new_id() -> randoms:get_string().
-
- -spec cancel_timer(reference()) -> ok.
- cancel_timer(Timer) ->
- erlang:cancel_timer(Timer),
- receive {timeout, Timer, _} -> ok after 0 -> ok end.
-
- -spec bounce_messages(stanza_error()) -> ok.
- bounce_messages(Error) ->
- receive
- {send_element, El} ->
- bounce_element(El, Error), bounce_messages(Error)
- after 0 -> ok
- end.
-
- -spec send_db_request(state()) -> fsm_transition().
- send_db_request(StateData) ->
- Server = StateData#state.server,
- New = case StateData#state.new of
- false ->
- ejabberd_s2s:try_register({StateData#state.myname, Server});
- true ->
- true
- end,
- NewStateData = StateData#state{new = New},
- try case New of
- false -> ok;
- true ->
- Key1 = ejabberd_s2s:make_key(
- {StateData#state.myname, Server},
- StateData#state.remote_streamid),
- send_element(StateData,
- #db_result{from = StateData#state.myname,
- to = Server,
- key = Key1})
- end,
- case StateData#state.verify of
- false -> ok;
- {_Pid, Key2, SID} ->
- send_element(StateData,
- #db_verify{from = StateData#state.myname,
- to = StateData#state.server,
- id = SID,
- key = Key2})
- end,
- {next_state, wait_for_validation, NewStateData,
- (?FSMTIMEOUT) * 6}
- catch
- _:_ -> {stop, normal, NewStateData}
- end.
-
- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- %% SRV support
-
- -include_lib("kernel/include/inet.hrl").
-
- -spec get_addr_port(binary()) -> [{binary(), inet:port_number()}].
- get_addr_port(Server) ->
- Res = srv_lookup(Server),
- case Res of
- {error, Reason} ->
- ?DEBUG("srv lookup of '~s' failed: ~p~n",
- [Server, Reason]),
- [{Server, outgoing_s2s_port()}];
- {ok, HEnt} ->
- ?DEBUG("srv lookup of '~s': ~p~n",
- [Server, HEnt#hostent.h_addr_list]),
- AddrList = HEnt#hostent.h_addr_list,
- case catch lists:map(fun ({Priority, Weight, Port,
- Host}) ->
- N = case Weight of
- 0 -> 0;
+ -spec bounce_packet(xmpp_element(), state()) -> state().
+ bounce_packet(Pkt, State) when ?is_stanza(Pkt) ->
+ From = xmpp:get_from(Pkt),
+ To = xmpp:get_to(Pkt),
+ Lang = xmpp:get_lang(Pkt),
+ Err = mk_bounce_error(Lang, State),
+ ejabberd_router:route_error(To, From, Pkt, Err),
+ State;
+ bounce_packet(_, State) ->
+ State.
+
+ -spec mk_bounce_error(binary(), state()) -> stanza_error().
+ mk_bounce_error(Lang, #{stop_reason := Why}) ->
+ Reason = xmpp_stream_out:format_error(Why),
+ case Why of
+ internal_failure ->
+ xmpp:err_internal_server_error();
+ {dns, _} ->
+ xmpp:err_remote_server_not_found(Reason, Lang);
- _ ->
+ _ ->
- (Weight + 1) * randoms:uniform()
- end,
- {Priority * 65536 - N, Host, Port}
- end,
- AddrList)
- of
- SortedList = [_ | _] ->
- List = lists:map(fun ({_, Host, Port}) ->
- {list_to_binary(Host), Port}
- end,
- lists:keysort(1, SortedList)),
- ?DEBUG("srv lookup of '~s': ~p~n", [Server, List]),
- List;
- _ -> [{Server, outgoing_s2s_port()}]
- end
- end.
-
- srv_lookup(Server) ->
- TimeoutMs = timer:seconds(
- ejabberd_config:get_option(
- s2s_dns_timeout,
- fun(I) when is_integer(I), I>=0 -> I end,
- 10)),
- Retries = ejabberd_config:get_option(
- s2s_dns_retries,
- fun(I) when is_integer(I), I>=0 -> I end,
- 2),
- srv_lookup(binary_to_list(Server), TimeoutMs, Retries).
-
- %% XXX - this behaviour is suboptimal in the case that the domain
- %% has a "_xmpp-server._tcp." but not a "_jabber._tcp." record and
- %% we don't get a DNS reply for the "_xmpp-server._tcp." lookup. In this
- %% case we'll give up when we get the "_jabber._tcp." nxdomain reply.
- srv_lookup(_Server, _Timeout, Retries)
- when Retries < 1 ->
- {error, timeout};
- srv_lookup(Server, Timeout, Retries) ->
- case inet_res:getbyname("_xmpp-server._tcp." ++ Server,
- srv, Timeout)
- of
- {error, _Reason} ->
- case inet_res:getbyname("_jabber._tcp." ++ Server, srv,
- Timeout)
- of
- {error, timeout} ->
- ?ERROR_MSG("The DNS servers~n ~p~ntimed out on "
- "request for ~p IN SRV. You should check "
- "your DNS configuration.",
- [inet_db:res_option(nameserver), Server]),
- srv_lookup(Server, Timeout, Retries - 1);
- R -> R
+ xmpp:err_remote_server_timeout(Reason, Lang)
- end;
+ end;
- {ok, _HEnt} = R -> R
- end.
-
- test_get_addr_port(Server) ->
- lists:foldl(fun (_, Acc) ->
- [HostPort | _] = get_addr_port(Server),
- case lists:keysearch(HostPort, 1, Acc) of
- false -> [{HostPort, 1} | Acc];
- {value, {_, Num}} ->
- lists:keyreplace(HostPort, 1, Acc,
- {HostPort, Num + 1})
- end
- end,
- [], lists:seq(1, 100000)).
-
- get_addrs(Host, Family) ->
- Type = case Family of
- inet4 -> inet;
- ipv4 -> inet;
- inet6 -> inet6;
- ipv6 -> inet6
- end,
- case inet:gethostbyname(binary_to_list(Host), Type) of
- {ok, #hostent{h_addr_list = Addrs}} ->
- ?DEBUG("~s of ~s resolved to: ~p~n",
- [Type, Host, Addrs]),
- Addrs;
- {error, Reason} ->
- ?DEBUG("~s lookup of '~s' failed: ~p~n",
- [Type, Host, Reason]),
- []
- end.
-
- -spec outgoing_s2s_port() -> pos_integer().
- outgoing_s2s_port() ->
- ejabberd_config:get_option(
- outgoing_s2s_port,
- fun(I) when is_integer(I), I > 0, I =< 65536 -> I end,
- 5269).
-
- -spec outgoing_s2s_families() -> [ipv4 | ipv6].
- outgoing_s2s_families() ->
- ejabberd_config:get_option(
- outgoing_s2s_families,
- fun(Families) ->
- true = lists:all(
- fun(ipv4) -> true;
- (ipv6) -> true
- end, Families),
- Families
- end, [ipv4, ipv6]).
-
- -spec outgoing_s2s_timeout() -> pos_integer().
- outgoing_s2s_timeout() ->
- ejabberd_config:get_option(
- outgoing_s2s_timeout,
- fun(TimeOut) when is_integer(TimeOut), TimeOut > 0 ->
- TimeOut;
- (infinity) ->
- infinity
- end, 10000).
+ mk_bounce_error(_Lang, _State) ->
+ %% We should not be here. Probably :)
+ xmpp:err_remote_server_not_found().
+
+ -spec get_delay() -> non_neg_integer().
+ get_delay() ->
+ MaxDelay = ejabberd_config:get_option(
+ s2s_max_retry_delay,
+ fun(I) when is_integer(I), I > 0 -> I end,
+ 300),
+ crypto:rand_uniform(1, MaxDelay).
+
+ -spec set_idle_timeout(state()) -> state().
+ set_idle_timeout(#{on_route := send, server := LServer} = State) ->
+ Timeout = ejabberd_s2s:get_idle_timeout(LServer),
+ xmpp_stream_out:set_timeout(State, Timeout);
+ set_idle_timeout(State) ->
+ State.
transform_options(Opts) ->
lists:foldl(fun transform_options/2, [], Opts).
- %%%----------------------------------------------------------------------
- %%% File : ejabberd_service.erl
- %%% Author : Alexey Shchepin <alexey@process-one.net>
- %%% Purpose : External component management (XEP-0114)
- %%% Created : 6 Dec 2002 by Alexey Shchepin <alexey@process-one.net>
+ %%%-------------------------------------------------------------------
+ %%% Created : 11 Dec 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2016 ProcessOne
+%%% 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
p1_sha:sha(randoms:bytes(20))),
dict:from_list([{global, Pass}])
end,
- Shaper = case lists:keysearch(shaper_rule, 1, Opts) of
- {value, {_, S}} -> S;
- _ -> none
- end,
- CheckFrom = case lists:keysearch(service_check_from, 1,
- Opts)
- of
- {value, {_, CF}} -> CF;
- _ -> true
- end,
- SockMod:change_shaper(Socket, Shaper),
- {ok, wait_for_stream,
- #state{socket = Socket, sockmod = SockMod,
- streamid = new_id(), host_opts = HostOpts,
- access = Access, check_from = CheckFrom}}.
-
- wait_for_stream({xmlstreamstart, Name, Attrs}, StateData) ->
- try xmpp:decode(#xmlel{name = Name, attrs = Attrs}) of
- #stream_start{xmlns = NS_COMPONENT, stream_xmlns = NS_STREAM}
- when NS_COMPONENT /= ?NS_COMPONENT; NS_STREAM /= ?NS_STREAM ->
- send_header(StateData, ?MYNAME),
- send_element(StateData, xmpp:serr_invalid_namespace()),
- {stop, normal, StateData};
- #stream_start{to = To} when is_record(To, jid) ->
- Host = To#jid.lserver,
- send_header(StateData, Host),
- HostOpts = case dict:is_key(Host, StateData#state.host_opts) of
+ CheckFrom = gen_mod:get_opt(check_from, Opts,
+ fun(Flag) when is_boolean(Flag) -> Flag end,
+ true),
+ xmpp_stream_in:change_shaper(State, Shaper),
+ State1 = State#{access => Access,
+ xmlns => ?NS_COMPONENT,
+ lang => ?MYLANG,
+ server => ?MYNAME,
+ host_opts => HostOpts,
+ check_from => CheckFrom},
+ ejabberd_hooks:run_fold(component_init, {ok, State1}, [Opts]).
+
+ handle_stream_start(_StreamStart,
+ #{remote_server := RemoteServer,
+ lang := Lang,
+ host_opts := HostOpts} = State) ->
+ case ejabberd_router:is_my_host(RemoteServer) of
- true ->
+ true ->
- StateData#state.host_opts;
+ Txt = <<"Unable to register route on existing local domain">>,
+ xmpp_stream_in:send(State, xmpp:serr_conflict(Txt, Lang));
- false ->
+ false ->
- case dict:find(global, StateData#state.host_opts) of
+ NewHostOpts = case dict:is_key(RemoteServer, HostOpts) of
+ true ->
+ HostOpts;
+ false ->
+ case dict:find(global, HostOpts) of
- {ok, GlobalPass} ->
+ {ok, GlobalPass} ->
- dict:from_list([{Host, GlobalPass}]);
+ dict:from_list([{RemoteServer, GlobalPass}]);
- error ->
+ error ->
- StateData#state.host_opts
+ HostOpts
- end
- end,
+ end
+ end,
- {next_state, wait_for_handshake,
- StateData#state{host = Host, host_opts = HostOpts}};
- #stream_start{} ->
- send_header(StateData, ?MYNAME),
- send_element(StateData, xmpp:serr_improper_addressing()),
- {stop, normal, StateData};
- _ ->
- send_header(StateData, ?MYNAME),
- send_element(StateData, xmpp:serr_invalid_xml()),
- {stop, normal, StateData}
- catch _:{xmpp_codec, Why} ->
- Txt = xmpp:format_error(Why),
- send_header(StateData, ?MYNAME),
- send_element(StateData, xmpp:serr_invalid_xml(Txt, ?MYLANG)),
- {stop, normal, StateData}
- end;
- wait_for_stream({xmlstreamerror, _}, StateData) ->
- send_header(StateData, ?MYNAME),
- send_element(StateData, xmpp:serr_not_well_formed()),
- {stop, normal, StateData};
- wait_for_stream(closed, StateData) ->
- {stop, normal, StateData}.
+ State#{host_opts => NewHostOpts}
+ end.
- wait_for_handshake({xmlstreamelement, El}, StateData) ->
- decode_element(El, wait_for_handshake, StateData);
- wait_for_handshake(#handshake{data = Digest}, StateData) ->
- case dict:find(StateData#state.host, StateData#state.host_opts) of
+ get_password_fun(#{remote_server := RemoteServer,
+ socket := Socket, sockmod := SockMod,
+ ip := IP,
+ host_opts := HostOpts}) ->
+ fun(_) ->
+ case dict:find(RemoteServer, HostOpts) of
- {ok, Password} ->
+ {ok, Password} ->
- case p1_sha:sha(<<(StateData#state.streamid)/binary,
- Password/binary>>) of
- Digest ->
- send_element(StateData, #handshake{}),
+ {Password, undefined};
+ error ->
+ ?ERROR_MSG("(~s) Domain ~s is unconfigured for "
+ "external component from ~s",
+ [SockMod:pp(Socket), RemoteServer,
+ ejabberd_config:may_hide_data(jlib:ip_to_list(IP))]),
+ {false, undefined}
+ end
+ end.
+
+ handle_auth_success(_, Mech, _,
+ #{remote_server := RemoteServer, host_opts := HostOpts,
+ socket := Socket, sockmod := SockMod,
+ ip := IP} = State) ->
+ ?INFO_MSG("(~s) Accepted external component ~s authentication "
+ "for ~s from ~s",
+ [SockMod:pp(Socket), Mech, RemoteServer,
+ ejabberd_config:may_hide_data(jlib:ip_to_list(IP))]),
- lists:foreach(
- fun (H) ->
- ejabberd_router:register_route(H, ?MYNAME),
- ejabberd_hooks:run(component_connected, [H])
+ lists:foreach(
+ fun (H) ->
+ ejabberd_router:register_route(H, ?MYNAME),
- ?INFO_MSG("Route registered for service ~p~n",
- [H]),
+ ejabberd_hooks:run(component_connected, [H])
- end, dict:fetch_keys(StateData#state.host_opts)),
- {next_state, stream_established, StateData};
- _ ->
- send_element(StateData, xmpp:serr_not_authorized()),
- {stop, normal, StateData}
- end;
- _ ->
- send_element(StateData, xmpp:serr_not_authorized()),
- {stop, normal, StateData}
- end;
- wait_for_handshake({xmlstreamend, _Name}, StateData) ->
- {stop, normal, StateData};
- wait_for_handshake({xmlstreamerror, _}, StateData) ->
- send_element(StateData, xmpp:serr_not_well_formed()),
- {stop, normal, StateData};
- wait_for_handshake(closed, StateData) ->
- {stop, normal, StateData};
- wait_for_handshake(_Pkt, StateData) ->
- {next_state, wait_for_handshake, StateData}.
-
- stream_established({xmlstreamelement, El}, StateData) ->
- decode_element(El, stream_established, StateData);
- stream_established(El, StateData) when ?is_stanza(El) ->
- From = xmpp:get_from(El),
- To = xmpp:get_to(El),
- Lang = xmpp:get_lang(El),
- if From == undefined orelse To == undefined ->
- Txt = <<"Missing 'from' or 'to' attribute">>,
- send_error(StateData, El, xmpp:err_jid_malformed(Txt, Lang));
- true ->
- case check_from(From, StateData) of
+ end, dict:fetch_keys(HostOpts)),
+ State.
+
+ handle_auth_failure(_, Mech, Reason,
+ #{remote_server := RemoteServer,
+ sockmod := SockMod,
+ socket := Socket, ip := IP} = State) ->
+ ?ERROR_MSG("(~s) Failed external component ~s authentication "
+ "for ~s from ~s: ~s",
+ [SockMod:pp(Socket), Mech, RemoteServer,
+ ejabberd_config:may_hide_data(jlib:ip_to_list(IP)),
+ Reason]),
+ State.
+
+ handle_authenticated_packet(Pkt, #{lang := Lang} = State) ->
+ From = xmpp:get_from(Pkt),
+ case check_from(From, State) of
- true ->
+ true ->
- ejabberd_router:route(From, To, El);
+ To = xmpp:get_to(Pkt),
+ ejabberd_router:route(From, To, Pkt),
+ State;
- false ->
- Txt = <<"Improper domain part of 'from' attribute">>,
+ false ->
+ Txt = <<"Improper domain part of 'from' attribute">>,
- send_error(StateData, El, xmpp:err_not_allowed(Txt, Lang))
- end
- end,
- {next_state, stream_established, StateData};
- stream_established({xmlstreamend, _Name}, StateData) ->
- {stop, normal, StateData};
- stream_established({xmlstreamerror, _}, StateData) ->
- send_element(StateData, xmpp:serr_not_well_formed()),
- {stop, normal, StateData};
- stream_established(closed, StateData) ->
- {stop, normal, StateData};
- stream_established(_Event, StateData) ->
- {next_state, stream_established, StateData}.
-
- handle_event(_Event, StateName, StateData) ->
- {next_state, StateName, StateData}.
-
- handle_sync_event(_Event, _From, StateName,
- StateData) ->
- Reply = ok, {reply, Reply, StateName, StateData}.
-
- code_change(_OldVsn, StateName, StateData, _Extra) ->
- {ok, StateName, StateData}.
+ Err = xmpp:serr_invalid_from(Txt, Lang),
+ xmpp_stream_in:send(State, Err)
+ end.
- handle_info({send_text, Text}, StateName, StateData) ->
- send_text(StateData, Text),
- {next_state, StateName, StateData};
- handle_info({send_element, El}, StateName, StateData) ->
- send_element(StateData, El),
- {next_state, StateName, StateData};
- handle_info({route, From, To, Packet}, StateName,
- StateData) ->
- case acl:match_rule(global, StateData#state.access, From) of
+ handle_info({route, From, To, Packet}, #{access := Access} = State) ->
+ case acl:match_rule(global, Access, From) of
- allow ->
+ allow ->
Pkt = xmpp:set_from_to(Packet, From, To),
- send_element(StateData, Pkt);
+ xmpp_stream_in:send(State, Pkt);
deny ->
Lang = xmpp:get_lang(Packet),
Err = xmpp:err_not_allowed(<<"Denied by ACL">>, Lang),
- ejabberd_router:route_error(To, From, Packet, Err)
- end,
- {next_state, StateName, StateData};
- handle_info(Info, StateName, StateData) ->
+ ejabberd_router:route_error(To, From, Packet, Err),
+ State
+ end;
+ handle_info(Info, State) ->
?ERROR_MSG("Unexpected info: ~p", [Info]),
- {next_state, StateName, StateData}.
+ State.
- terminate(Reason, StateName, StateData) ->
- ?INFO_MSG("terminated: ~p", [Reason]),
- case StateName of
- stream_established ->
- lists:foreach(fun (H) ->
+ terminate(Reason, #{stream_state := StreamState, host_opts := HostOpts}) ->
+ case StreamState of
+ established ->
+ lists:foreach(
+ fun(H) ->
- ejabberd_router:unregister_route(H),
+ ejabberd_router:unregister_route(H),
- ejabberd_hooks:run(component_disconnected,
- [H, Reason])
- end,
- dict:fetch_keys(StateData#state.host_opts));
- _ -> ok
- end,
- catch send_trailer(StateData),
- (StateData#state.sockmod):close(StateData#state.socket),
- ok.
-
- %%----------------------------------------------------------------------
- %% Func: print_state/1
- %% Purpose: Prepare the state to be printed on error log
- %% Returns: State to print
- %%----------------------------------------------------------------------
- print_state(State) -> State.
-
- %%%----------------------------------------------------------------------
- %%% Internal functions
- %%%----------------------------------------------------------------------
-
- -spec send_text(state(), iodata()) -> ok.
- send_text(StateData, Text) ->
- (StateData#state.sockmod):send(StateData#state.socket,
- Text).
-
- -spec send_element(state(), xmpp_element()) -> ok.
- send_element(StateData, El) ->
- El1 = xmpp:encode(El, ?NS_COMPONENT),
- send_text(StateData, fxml:element_to_binary(El1)).
-
- -spec send_error(state(), xmlel() | stanza(), stanza_error()) -> ok.
- send_error(StateData, Stanza, Error) ->
- Type = xmpp:get_type(Stanza),
- if Type == error; Type == result;
- Type == <<"error">>; Type == <<"result">> ->
- ok;
- true ->
- send_element(StateData, xmpp:make_error(Stanza, Error))
- end.
-
- -spec send_header(state(), binary()) -> ok.
- send_header(StateData, Host) ->
- Header = xmpp:encode(
- #stream_start{xmlns = ?NS_COMPONENT,
- stream_xmlns = ?NS_STREAM,
- from = jid:make(Host),
- id = StateData#state.streamid}),
- send_text(StateData, fxml:element_to_header(Header)).
-
- -spec send_trailer(state()) -> ok.
- send_trailer(StateData) ->
- send_text(StateData, <<"</stream:stream>">>).
-
- -spec decode_element(xmlel(), state_name(), state()) -> fsm_transition().
- decode_element(#xmlel{} = El, StateName, StateData) ->
- try xmpp:decode(El, ?NS_COMPONENT, [ignore_els]) of
- Pkt -> ?MODULE:StateName(Pkt, StateData)
- catch error:{xmpp_codec, Why} ->
- case xmpp:is_stanza(El) of
- true ->
- Lang = xmpp:get_lang(El),
- Txt = xmpp:format_error(Why),
- send_error(StateData, El, xmpp:err_bad_request(Txt, Lang));
- false ->
+ ejabberd_hooks:run(component_disconnected, [H, Reason])
+ end, dict:fetch_keys(HostOpts));
+ _ ->
- ok
+ ok
- end,
- {next_state, StateName, StateData}
end.
+ code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+ %%%===================================================================
+ %%% Internal functions
+ %%%===================================================================
-spec check_from(jid(), state()) -> boolean().
- check_from(_From, #state{check_from = false}) ->
+ check_from(_From, #{check_from := false}) ->
%% If the admin does not want to check the from field
%% when accept packets from any address.
%% In this case, the component can send packet of
-spec get_sm_backend(binary()) -> module().
get_sm_backend(Host) ->
- DBType = ejabberd_config:get_option(
+ DBType = case ejabberd_config:get_option(
- {sm_db_type, Host},
+ {sm_db_type, Host},
- fun(T) -> ejabberd_config:v_db(?MODULE, T) end,
- mnesia),
+ fun(T) -> ejabberd_config:v_db(?MODULE, T) end) of
+ undefined ->
+ ejabberd_config:default_ram_db(Host, ?MODULE);
+ T ->
+ T
+ end,
list_to_atom("ejabberd_sm_" ++ atom_to_list(DBType)).
-spec get_sm_backends() -> [module()].
sockname/1, peername/1]).
-include("ejabberd.hrl").
+ -include("xmpp.hrl").
-include("logger.hrl").
--type sockmod() :: ejabberd_http_bind |
- ejabberd_bosh |
+-type sockmod() :: ejabberd_bosh |
ejabberd_http_ws |
gen_tcp | fast_tls | ezlib.
-type receiver() :: pid () | atom().
%%====================================================================
%% API
%%====================================================================
- -spec start(atom(), sockmod(), socket(), [{atom(), any()}]) -> any().
-
+ -spec start(atom(), sockmod(), socket(), [proplists:propery()])
+ -> {ok, pid() | independent} | {error, inet:posix() | any()}.
start(Module, SockMod, Socket, Opts) ->
case Module:socket_type() of
- xml_stream ->
+ independent -> {ok, independent};
- MaxStanzaSize = case lists:keysearch(max_stanza_size, 1,
- Opts)
- of
- {value, {_, Size}} -> Size;
- _ -> infinity
- end,
- {ReceiverMod, Receiver, RecRef} = case catch
- SockMod:custom_receiver(Socket)
- of
+ xml_stream ->
- {receiver, RecMod, RecPid} ->
+ MaxStanzaSize = proplists:get_value(max_stanza_size, Opts, infinity),
+ {ReceiverMod, Receiver, RecRef} =
+ try SockMod:custom_receiver(Socket) of
- {RecMod, RecPid, RecMod};
- _ ->
- RecPid =
- ejabberd_receiver:start(Socket,
- SockMod,
- none,
- MaxStanzaSize),
- {ejabberd_receiver, RecPid,
- RecPid}
+ {receiver, RecMod, RecPid} ->
- end,
- SocketData = #socket_state{sockmod = SockMod,
- socket = Socket, receiver = RecRef},
- case Module:start({?MODULE, SocketData}, Opts) of
- {ok, Pid} ->
- case SockMod:controlling_process(Socket, Receiver) of
+ {RecMod, RecPid, RecMod}
+ catch _:_ ->
+ RecPid = ejabberd_receiver:start(
+ Socket, SockMod, none, MaxStanzaSize),
+ {ejabberd_receiver, RecPid, RecPid}
- ok -> ok;
- {error, _Reason} -> SockMod:close(Socket)
- end,
- ReceiverMod:become_controller(Receiver, Pid);
- {error, _Reason} ->
+ end,
+ SocketData = #socket_state{sockmod = SockMod,
+ socket = Socket, receiver = RecRef},
+ case Module:start({?MODULE, SocketData}, Opts) of
+ {ok, Pid} ->
+ case SockMod:controlling_process(Socket, Receiver) of
- SockMod:close(Socket),
- case ReceiverMod of
- ejabberd_receiver -> ReceiverMod:close(Receiver);
- _ -> ok
+ ok ->
+ ReceiverMod:become_controller(Receiver, Pid),
+ {ok, Receiver};
+ Err ->
+ SockMod:close(Socket),
+ Err
+ end;
+ Err ->
- end
+ SockMod:close(Socket),
+ case ReceiverMod of
+ ejabberd_receiver -> ReceiverMod:close(Receiver);
+ _ -> ok
- end;
- raw ->
- case Module:start({SockMod, Socket}, Opts) of
- {ok, Pid} ->
- case SockMod:controlling_process(Socket, Pid) of
+ end,
+ Err
- independent -> ok;
+ end;
- ok -> ok;
- {error, _Reason} -> SockMod:close(Socket)
+ raw ->
+ case Module:start({SockMod, Socket}, Opts) of
+ {ok, Pid} ->
+ case SockMod:controlling_process(Socket, Pid) of
- end;
+ ok ->
+ {ok, Pid};
+ {error, _} = Err ->
+ SockMod:close(Socket),
+ Err
- {error, _Reason} -> SockMod:close(Socket)
+ end;
- end
+ Err ->
+ SockMod:close(Socket),
+ Err
+ end
end.
connect(Addr, Port, Opts) ->
case SubEl of
#block{items = []} ->
Txt = <<"No items found in this query">>,
- {error, xmpp:err_bad_request(Txt, Lang)};
+ xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang));
#block{items = Items} ->
JIDs = [jid:tolower(Item) || Item <- Items],
- process_blocklist_block(LUser, LServer, JIDs, Lang);
+ process_block(IQ, JIDs);
#unblock{items = []} ->
- process_blocklist_unblock_all(LUser, LServer, Lang);
+ process_unblock_all(IQ);
#unblock{items = Items} ->
JIDs = [jid:tolower(Item) || Item <- Items],
- process_blocklist_unblock(LUser, LServer, JIDs, Lang);
+ process_unblock(IQ, JIDs);
_ ->
- Acc
- end;
- process_iq_set(Acc, _, _) -> Acc.
+ Txt = <<"No module is handling this query">>,
+ xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang))
+ end.
- -spec list_to_blocklist_jids([listitem()], [ljid()]) -> [ljid()].
- list_to_blocklist_jids([], JIDs) -> JIDs;
- list_to_blocklist_jids([#listitem{type = jid,
- action = deny, value = JID} =
- Item
- | Items],
+ -spec listitems_to_jids([listitem()], [ljid()]) -> [ljid()].
+ listitems_to_jids([], JIDs) ->
+ JIDs;
+ listitems_to_jids([#listitem{type = jid,
+ action = deny, value = JID} = Item | Items],
- JIDs) ->
+ JIDs) ->
Match = case Item of
#listitem{match_all = true} ->
true;
end,
Mod = db_mod(LServer),
case Mod:process_blocklist_block(LUser, LServer, Filter) of
- {atomic, {ok, Default, List}} ->
- UserList = make_userlist(Default, List),
+ {atomic, {ok, Default, List}} ->
+ UserList = make_userlist(Default, List),
- broadcast_list_update(LUser, LServer, Default,
- UserList),
- broadcast_blocklist_event(LUser, LServer,
- {block, [jid:make(J) || J <- JIDs]}),
- {result, undefined, UserList};
+ broadcast_list_update(LUser, LServer, UserList, Default),
+ broadcast_event(LUser, LServer,
+ #block{items = [jid:make(J) || J <- JIDs]}),
+ xmpp:make_iq_result(IQ);
- _Err ->
+ _Err ->
?ERROR_MSG("Error processing ~p: ~p", [{LUser, LServer, JIDs}, _Err]),
- {error, xmpp:err_internal_server_error(<<"Database failure">>, Lang)}
+ Err = xmpp:err_internal_server_error(<<"Database failure">>, Lang),
+ xmpp:make_error(IQ, Err)
end.
- -spec process_blocklist_unblock_all(binary(), binary(), binary()) ->
- {error, stanza_error()} |
- {result, undefined} |
- {result, undefined, userlist()}.
- process_blocklist_unblock_all(LUser, LServer, Lang) ->
+ -spec process_unblock_all(iq()) -> iq().
+ process_unblock_all(#iq{from = #jid{luser = LUser, lserver = LServer},
+ lang = Lang} = IQ) ->
Filter = fun (List) ->
lists:filter(fun (#listitem{action = A}) -> A =/= deny
end,
end,
Mod = db_mod(LServer),
case Mod:unblock_by_filter(LUser, LServer, Filter) of
- {atomic, ok} -> {result, undefined};
+ {atomic, ok} ->
+ xmpp:make_iq_result(IQ);
- {atomic, {ok, Default, List}} ->
- UserList = make_userlist(Default, List),
+ {atomic, {ok, Default, List}} ->
+ UserList = make_userlist(Default, List),
- broadcast_list_update(LUser, LServer, Default,
- UserList),
- broadcast_blocklist_event(LUser, LServer, unblock_all),
- {result, undefined, UserList};
+ broadcast_list_update(LUser, LServer, UserList, Default),
+ broadcast_event(LUser, LServer, #unblock{}),
+ xmpp:make_iq_result(IQ);
- _Err ->
+ _Err ->
?ERROR_MSG("Error processing ~p: ~p", [{LUser, LServer}, _Err]),
- {error, xmpp:err_internal_server_error(<<"Database failure">>, Lang)}
+ Err = xmpp:err_internal_server_error(<<"Database failure">>, Lang),
+ xmpp:make_error(IQ, Err)
end.
- -spec process_blocklist_unblock(binary(), binary(), [ljid()], binary()) ->
- {error, stanza_error()} |
- {result, undefined} |
- {result, undefined, userlist()}.
- process_blocklist_unblock(LUser, LServer, JIDs, Lang) ->
+ -spec process_unblock(iq(), [ljid()]) -> iq().
+ process_unblock(#iq{from = #jid{luser = LUser, lserver = LServer},
+ lang = Lang} = IQ, JIDs) ->
Filter = fun (List) ->
lists:filter(fun (#listitem{action = deny, type = jid,
value = JID}) ->
end,
Mod = db_mod(LServer),
case Mod:unblock_by_filter(LUser, LServer, Filter) of
- {atomic, ok} -> {result, undefined};
+ {atomic, ok} ->
+ xmpp:make_iq_result(IQ);
- {atomic, {ok, Default, List}} ->
- UserList = make_userlist(Default, List),
+ {atomic, {ok, Default, List}} ->
+ UserList = make_userlist(Default, List),
- broadcast_list_update(LUser, LServer, Default,
- UserList),
- broadcast_blocklist_event(LUser, LServer,
- {unblock, [jid:make(J) || J <- JIDs]}),
- {result, undefined, UserList};
+ broadcast_list_update(LUser, LServer, UserList, Default),
+ broadcast_event(LUser, LServer,
+ #unblock{items = [jid:make(J) || J <- JIDs]}),
+ xmpp:make_iq_result(IQ);
- _Err ->
+ _Err ->
?ERROR_MSG("Error processing ~p: ~p", [{LUser, LServer, JIDs}, _Err]),
- {error, xmpp:err_internal_server_error(<<"Database failure">>, Lang)}
+ Err = xmpp:err_internal_server_error(<<"Database failure">>, Lang),
+ xmpp:make_error(IQ, Err)
end.
-spec make_userlist(binary(), [listitem()]) -> userlist().
NeedDb = mod_privacy:is_list_needdb(List),
#userlist{name = Name, list = List, needdb = NeedDb}.
- -spec broadcast_list_update(binary(), binary(), binary(), userlist()) -> ok.
- broadcast_list_update(LUser, LServer, Name, UserList) ->
- ejabberd_sm:route(jid:make(LUser, LServer, <<"">>),
- jid:make(LUser, LServer, <<"">>),
- {broadcast, {privacy_list, UserList, Name}}).
+ -spec broadcast_list_update(binary(), binary(), userlist(), binary()) -> ok.
+ broadcast_list_update(LUser, LServer, UserList, Name) ->
+ mod_privacy:push_list_update(jid:make(LUser, LServer), UserList, Name).
- -spec broadcast_blocklist_event(binary(), binary(), block_event()) -> ok.
- broadcast_blocklist_event(LUser, LServer, Event) ->
- JID = jid:make(LUser, LServer, <<"">>),
- ejabberd_sm:route(JID, JID,
- {broadcast, {blocking, Event}}).
+ -spec broadcast_event(binary(), binary(), block_event()) -> ok.
+ broadcast_event(LUser, LServer, Event) ->
+ From = jid:make(LUser, LServer),
+ lists:foreach(
+ fun(R) ->
+ To = jid:replace_resource(From, R),
+ IQ = #iq{type = set, from = From, to = To,
+ id = <<"push", (randoms:get_string())/binary>>,
+ sub_els = [Event]},
+ ejabberd_router:route(From, To, IQ)
+ end, ejabberd_sm:get_user_resources(LUser, LServer)).
- -spec process_blocklist_get(binary(), binary(), binary()) ->
- {error, stanza_error()} | {result, block_list()}.
- process_blocklist_get(LUser, LServer, Lang) ->
+ -spec process_get(iq()) -> iq().
+ process_get(#iq{from = #jid{luser = LUser, lserver = LServer},
+ lang = Lang} = IQ) ->
Mod = db_mod(LServer),
case Mod:process_blocklist_get(LUser, LServer) of
- error ->
+ error ->
- {error, xmpp:err_internal_server_error(<<"Database failure">>, Lang)};
+ Err = xmpp:err_internal_server_error(<<"Database failure">>, Lang),
+ xmpp:make_error(IQ, Err);
- List ->
+ List ->
- LJIDs = list_to_blocklist_jids(List, []),
+ LJIDs = listitems_to_jids(List, []),
- Items = [jid:make(J) || J <- LJIDs],
+ Items = [jid:make(J) || J <- LJIDs],
- {result, #block_list{items = Items}}
+ xmpp:make_iq_result(IQ, #block_list{items = Items})
end.
-spec db_mod(binary()) -> module().
#xmlel{name = <<"h1">>, attrs = [],
children = [{xmlcdata, <<"400 Bad Request">>}]}}.
-get_human_html_xmlel() ->
- Heading = <<"ejabberd ", (jlib:atom_to_binary(?MODULE))/binary>>,
- #xmlel{name = <<"html">>,
- attrs =
- [{<<"xmlns">>, <<"http://www.w3.org/1999/xhtml">>}],
- children =
- [#xmlel{name = <<"head">>, attrs = [],
- children =
- [#xmlel{name = <<"title">>, attrs = [],
- children = [{xmlcdata, Heading}]}]},
- #xmlel{name = <<"body">>, attrs = [],
- children =
- [#xmlel{name = <<"h1">>, attrs = [],
- children = [{xmlcdata, Heading}]},
- #xmlel{name = <<"p">>, attrs = [],
- children =
- [{xmlcdata, <<"An implementation of ">>},
- #xmlel{name = <<"a">>,
- attrs =
- [{<<"href">>,
- <<"http://xmpp.org/extensions/xep-0206.html">>}],
- children =
- [{xmlcdata,
- <<"XMPP over BOSH (XEP-0206)">>}]}]},
- #xmlel{name = <<"p">>, attrs = [],
- children =
- [{xmlcdata,
- <<"This web page is only informative. To "
- "use HTTP-Bind you need a Jabber/XMPP "
- "client that supports it.">>}]}]}]}.
-
open_session(SID, Pid) ->
- Session = #bosh{sid = SID, timestamp = p1_time_compat:timestamp(), pid = Pid},
- lists:foreach(
- fun(Node) when Node == node() ->
- gen_server:call(?MODULE, {write, Session});
- (Node) ->
- cluster_send({?MODULE, Node}, {write, Session})
- end, ejabberd_cluster:get_nodes()).
+ Mod = gen_mod:ram_db_mod(global, ?MODULE),
+ Mod:open_session(SID, Pid).
close_session(SID) ->
- case mnesia:dirty_read(bosh, SID) of
- [Session] ->
- lists:foreach(
- fun(Node) when Node == node() ->
- gen_server:call(?MODULE, {delete, Session});
- (Node) ->
- cluster_send({?MODULE, Node}, {delete, Session})
- end, ejabberd_cluster:get_nodes());
- [] ->
- ok
- end.
-
- write_session(#bosh{pid = Pid1, sid = SID, timestamp = T1} = S1) ->
- case mnesia:dirty_read(bosh, SID) of
- [#bosh{pid = Pid2, timestamp = T2} = S2] ->
- if Pid1 == Pid2 ->
- mnesia:dirty_write(S1);
- T1 < T2 ->
- cluster_send(Pid2, replaced),
- mnesia:dirty_write(S1);
- true ->
- cluster_send(Pid1, replaced),
- mnesia:dirty_write(S2)
- end;
- [] ->
- mnesia:dirty_write(S1)
- end.
-
- delete_session(#bosh{sid = SID, pid = Pid1}) ->
- case mnesia:dirty_read(bosh, SID) of
- [#bosh{pid = Pid2}] ->
- if Pid1 == Pid2 ->
- mnesia:dirty_delete(bosh, SID);
- true ->
- ok
- end;
- [] ->
- ok
- end.
+ Mod = gen_mod:ram_db_mod(global, ?MODULE),
+ Mod:close_session(SID).
find_session(SID) ->
- case mnesia:dirty_read(bosh, SID) of
- [#bosh{pid = Pid}] ->
- {ok, Pid};
- [] ->
- error
- end.
+ Mod = gen_mod:ram_db_mod(global, ?MODULE),
+ Mod:find_session(SID).
start(Host, Opts) ->
- setup_database(),
start_jiffy(Opts),
TmpSup = gen_mod:get_module_proc(Host, ?PROCNAME),
TmpSupSpec = {TmpSup,
fun (I) when is_integer(I), I > 0 -> I end;
mod_opt_type(prebind) ->
fun (B) when is_boolean(B) -> B end;
+ mod_opt_type(ram_db_type) ->
+ fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
mod_opt_type(_) ->
- [json, max_concat, max_inactivity, max_pause, prebind].
+ [json, max_concat, max_inactivity, max_pause, prebind, ram_db_type].
+
+
+%%%----------------------------------------------------------------------
+%%% Help Web Page
+%%%----------------------------------------------------------------------
+
+get_human_html_xmlel() ->
+ Heading = <<"ejabberd ",
+ (iolist_to_binary(atom_to_list(?MODULE)))/binary>>,
+ #xmlel{name = <<"html">>,
+ attrs =
+ [{<<"xmlns">>, <<"http://www.w3.org/1999/xhtml">>}],
+ children =
+ [#xmlel{name = <<"head">>,
+ children =
+ [#xmlel{name = <<"title">>,
+ children = [{xmlcdata, Heading}]},
+ #xmlel{name = <<"style">>,
+ children = [{xmlcdata, get_style_cdata()}]}]},
+ #xmlel{name = <<"body">>,
+ children =
+ [#xmlel{name = <<"div">>,
+ attrs = [{<<"class">>, <<"container">>}],
+ children = get_container_children(Heading)}]}]}.
+
+get_container_children(Heading) ->
+ [#xmlel{name = <<"div">>,
+ attrs = [{<<"class">>, <<"section">>}],
+ children =
+ [#xmlel{name = <<"div">>,
+ attrs = [{<<"class">>, <<"block">>}],
+ children =
+ [#xmlel{name = <<"a">>,
+ attrs = [{<<"href">>, <<"https://www.ejabberd.im">>}],
+ children =
+ [#xmlel{name = <<"img">>,
+ attrs = [{<<"height">>, <<"32">>},
+ {<<"src">>, get_image_src()}]}]}]}]},
+ #xmlel{name = <<"div">>,
+ attrs = [{<<"class">>, <<"white section">>}],
+ children =
+ [#xmlel{name = <<"div">>,
+ attrs = [{<<"class">>, <<"block">>}],
+ children =
+ [#xmlel{name = <<"h1">>, children = [{xmlcdata, Heading}]},
+ #xmlel{name = <<"p">>, children =
+ [{xmlcdata, <<"An implementation of ">>},
+ #xmlel{name = <<"a">>,
+ attrs = [{<<"href">>, <<"http://xmpp.org/extensions/xep-0206.html">>}],
+ children = [{xmlcdata, <<"XMPP over BOSH (XEP-0206)">>}]}]},
+ #xmlel{name = <<"p">>, children =
+ [{xmlcdata, <<"This web page is only informative. To "
+ "use HTTP-Bind you need a Jabber/XMPP "
+ "client that supports it.">>}]}]}]},
+ #xmlel{name = <<"div">>,
+ attrs = [{<<"class">>, <<"section">>}],
+ children =
+ [#xmlel{name = <<"div">>,
+ attrs = [{<<"class">>, <<"block">>}],
+ children =
+ [#xmlel{name = <<"a">>,
+ attrs = [{<<"href">>, <<"https://www.ejabberd.im">>},
+ {<<"title">>, <<"ejabberd XMPP server">>}],
+ children = [{xmlcdata, <<"ejabberd">>}]},
+ {xmlcdata, <<" is maintained by ">>},
+ #xmlel{name = <<"a">>,
+ attrs = [{<<"href">>, <<"https://www.process-one.net">>},
+ {<<"title">>, <<"ProcessOne - Leader in Instant Messaging and Push Solutions">>}],
+ children = [{xmlcdata, <<"ProcessOne">>}]} ]}]}
+ ].
+
+get_style_cdata() ->
+ <<"
+ body {
+ margin: 0;
+ padding: 0;
+ font-family: sans-serif;
+ color: #fff;
+ }
+ h1 {
+ font-size: 3em;
+ color: #444;
+ }
+ p {
+ line-height: 1.5em;
+ color: #888;
+ }
+ a {
+ color: #fff;
+ }
+ a:hover,
+ a:active {
+ text-decoration: underline;
+ }
+ .container {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: #424A55;
+ background-image: -webkit-linear-gradient(270deg, rgba(48,52,62,0) 24%, #30353e 100%);
+ background-image: linear-gradient(-180deg, rgba(48,52,62,0) 24%, #30353e 100%);
+ }
+ .section {
+ padding: 3em;
+ }
+ .white.section {
+ background: #fff;
+ border-bottom: 4px solid #41AFCA;
+ }
+ .white.section a {
+ text-decoration: none;
+ color: #41AFCA;
+ }
+ .white.section a:hover,
+ .white.section a:active {
+ text-decoration: underline;
+ }
+ .block {
+ margin: 0 auto;
+ max-width: 900px;
+ width: 100%;
+ }">>.
+
+get_image_src() ->
+ <<"data:image/png;base64,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">>.
case read_caps(Pkt) of
nothing -> ok;
#caps{version = Version, exts = Exts} = Caps ->
- feature_request(Server, From, Caps, [Version | Exts])
+ feature_request(LServer, From, Caps, [Version | Exts])
end,
- Pkt;
- user_send_packet(Pkt, _C2SState, _From, _To) ->
- Pkt.
-
- -spec user_receive_packet(stanza(), ejabberd_c2s:state(),
- jid(), jid(), jid()) -> stanza().
- user_receive_packet(#presence{type = available} = Pkt,
- _C2SState,
- #jid{lserver = Server},
- From, _To) ->
- IsRemote = not lists:member(From#jid.lserver, ?MYHOSTS),
+ {Pkt, State};
+ user_send_packet(Acc) ->
+ Acc.
+
+ -spec user_receive_packet({stanza(), ejabberd_c2s:state()}) -> {stanza(), ejabberd_c2s:state()}.
+ user_receive_packet({#presence{from = From, type = available} = Pkt,
+ #{lserver := LServer} = State}) ->
+ IsRemote = not ejabberd_router:is_my_host(From#jid.lserver),
if IsRemote ->
- case read_caps(Pkt) of
- nothing -> ok;
- #caps{version = Version, exts = Exts} = Caps ->
+ case read_caps(Pkt) of
+ nothing -> ok;
+ #caps{version = Version, exts = Exts} = Caps ->
- feature_request(Server, From, Caps, [Version | Exts])
+ feature_request(LServer, From, Caps, [Version | Exts])
- end;
+ end;
true -> ok
end,
- Pkt;
- user_receive_packet(Pkt, _C2SState, _JID, _From, _To) ->
- Pkt.
+ {Pkt, State};
+ user_receive_packet(Acc) ->
+ Acc.
-spec caps_stream_features([xmpp_element()], binary()) -> [xmpp_element()].
caps_stream_features(Acc, MyHost) ->
- case make_my_disco_hash(MyHost) of
+ case gen_mod:is_loaded(MyHost, ?MODULE) of
+ true ->
- <<"">> -> Acc;
+ case make_my_disco_hash(MyHost) of
- Hash ->
+ <<"">> ->
+ Acc;
- [#caps{hash = <<"sha-1">>, node = ?EJABBERD_URI, version = Hash}|Acc]
+ Hash ->
+ [#caps{hash = <<"sha-1">>, node = ?EJABBERD_URI,
+ version = Hash}|Acc]
+ end;
+ false ->
+ Acc
end.
-spec disco_features({error, stanza_error()} | {result, [binary()]} | empty,
and ((Subscription == both) or (Subscription == to)),
Delete = (Type == unavailable) or (Type == error),
if Insert or Delete ->
- LFrom = jid:tolower(From),
+ LFrom = jid:tolower(From),
- Rs = case ejabberd_c2s:get_aux_field(caps_resources,
- C2SState)
- of
- {ok, Rs1} -> Rs1;
- error -> gb_trees:empty()
- end,
+ Rs = maps:get(caps_resources, C2SState, gb_trees:empty()),
- Caps = read_caps(Presence),
- NewRs = case Caps of
- nothing when Insert == true -> Rs;
- _ when Insert == true ->
- case gb_trees:lookup(LFrom, Rs) of
- {value, Caps} -> Rs;
- none ->
- ejabberd_hooks:run(caps_add, To#jid.lserver,
- [From, To,
- get_features(To#jid.lserver, Caps)]),
- gb_trees:insert(LFrom, Caps, Rs);
- _ ->
- ejabberd_hooks:run(caps_update, To#jid.lserver,
- [From, To,
- get_features(To#jid.lserver, Caps)]),
- gb_trees:update(LFrom, Caps, Rs)
- end;
- _ -> gb_trees:delete_any(LFrom, Rs)
- end,
+ Caps = read_caps(Presence),
+ NewRs = case Caps of
+ nothing when Insert == true -> Rs;
+ _ when Insert == true ->
+ case gb_trees:lookup(LFrom, Rs) of
+ {value, Caps} -> Rs;
+ none ->
+ ejabberd_hooks:run(caps_add, To#jid.lserver,
+ [From, To,
+ get_features(To#jid.lserver, Caps)]),
+ gb_trees:insert(LFrom, Caps, Rs);
+ _ ->
+ ejabberd_hooks:run(caps_update, To#jid.lserver,
+ [From, To,
+ get_features(To#jid.lserver, Caps)]),
+ gb_trees:update(LFrom, Caps, Rs)
+ end;
+ _ -> gb_trees:delete_any(LFrom, Rs)
+ end,
- ejabberd_c2s:set_aux_field(caps_resources, NewRs,
- C2SState);
- true -> C2SState
+ C2SState#{caps_resources => NewRs};
+ true ->
+ C2SState
end.
- -spec c2s_filter_packet(boolean(), binary(), ejabberd_c2s:state(),
- {pep_message, binary()}, jid(), stanza()) ->
- boolean().
- c2s_filter_packet(InAcc, Host, C2SState, {pep_message, Feature}, To, _Packet) ->
- case ejabberd_c2s:get_aux_field(caps_resources, C2SState) of
- {ok, Rs} ->
- LTo = jid:tolower(To),
- case gb_trees:lookup(LTo, Rs) of
- {value, Caps} ->
- Drop = not lists:member(Feature, get_features(Host, Caps)),
- {stop, Drop};
- none ->
- {stop, true}
- end;
- _ -> InAcc
- end;
- c2s_filter_packet(Acc, _, _, _, _, _) -> Acc.
-
- -spec c2s_broadcast_recipients([ljid()], binary(), ejabberd_c2s:state(),
- {pep_message, binary()}, jid(), stanza()) ->
- [ljid()].
- c2s_broadcast_recipients(InAcc, Host, C2SState,
- {pep_message, Feature}, _From, _Packet) ->
- case ejabberd_c2s:get_aux_field(caps_resources,
- C2SState)
- of
- {ok, Rs} ->
- gb_trees_fold(fun (USR, Caps, Acc) ->
- case lists:member(Feature,
- get_features(Host, Caps))
- of
- true -> [USR | Acc];
- false -> Acc
- end
- end,
- InAcc, Rs);
- _ -> InAcc
- end;
- c2s_broadcast_recipients(Acc, _, _, _, _, _) -> Acc.
-
-spec depends(binary(), gen_mod:opts()) -> [{module(), hard | soft}].
depends(_Host, _Opts) ->
[].
%% conversations across clients.
Acc;
_ ->
- ?DEBUG("Got standalone chat state notification for ~s",
- [jid:to_string(To)]),
+ ?DEBUG("Got standalone chat state notification for ~s",
+ [jid:to_string(To)]),
- queue_add(chatstate, Stanza, Host, C2SState)
+ enqueue_stanza(chatstate, Msg, C2SState)
end;
false ->
Acc
%%--------------------------------------------------------------------
%% Internal functions.
%%--------------------------------------------------------------------
-
- -spec queue_add(csi_type(), stanza(), binary(), term())
- -> {stop, {term(), [stanza()]}}.
-
- queue_add(Type, Stanza, Host, C2SState) ->
- case get_queue(C2SState) of
- Queue when length(Queue) >= ?CSI_QUEUE_MAX ->
+ -spec enqueue_stanza(csi_type(), stanza(), c2s_state()) -> filter_acc().
+ enqueue_stanza(Type, Stanza, #{csi_state := inactive,
+ csi_queue := Q} = C2SState) ->
+ case queue_len(Q) >= ?CSI_QUEUE_MAX of
+ true ->
- ?DEBUG("CSI queue too large, going to flush it", []),
+ ?DEBUG("CSI queue too large, going to flush it", []),
- NewState = set_queue([], C2SState),
- {stop, {NewState, get_stanzas(Queue, Host) ++ [Stanza]}};
- Queue ->
- ?DEBUG("Adding stanza to CSI queue", []),
- From = xmpp:get_from(Stanza),
- Key = {jid:tolower(From), Type},
- Entry = {Key, p1_time_compat:timestamp(), Stanza},
- NewQueue = lists:keystore(Key, 1, Queue, Entry),
- NewState = set_queue(NewQueue, C2SState),
- {stop, {NewState, []}}
- end.
-
- -spec queue_take(stanza(), binary(), term()) -> {term(), [stanza()]}.
-
- queue_take(Stanza, Host, C2SState) ->
- From = xmpp:get_from(Stanza),
- {LUser, LServer, _LResource} = jid:tolower(From),
- {Selected, Rest} = lists:partition(
- fun({{{U, S, _R}, _Type}, _Time, _Stanza}) ->
- U == LUser andalso S == LServer
- end, get_queue(C2SState)),
- NewState = set_queue(Rest, C2SState),
- {NewState, get_stanzas(Selected, Host) ++ [Stanza]}.
-
- -spec set_queue(csi_queue(), term()) -> term().
-
- set_queue(Queue, C2SState) ->
- ejabberd_c2s:set_aux_field(csi_queue, Queue, C2SState).
-
- -spec get_queue(term()) -> csi_queue().
-
- get_queue(C2SState) ->
- case ejabberd_c2s:get_aux_field(csi_queue, C2SState) of
- {ok, Queue} ->
- Queue;
+ C2SState1 = flush_queue(C2SState),
+ enqueue_stanza(Type, Stanza, C2SState1);
+ false ->
+ #jid{luser = U, lserver = S} = xmpp:get_from(Stanza),
+ Q1 = queue_in({U, S}, Type, Stanza, Q),
+ {stop, {drop, C2SState#{csi_queue => Q1}}}
+ end;
+ enqueue_stanza(_Type, Stanza, State) ->
+ {Stanza, State}.
+
+ -spec dequeue_sender(jid(), c2s_state()) -> c2s_state().
+ dequeue_sender(#jid{luser = U, lserver = S},
+ #{csi_queue := Q, jid := JID} = C2SState) ->
+ ?DEBUG("Flushing packets of ~s@~s from CSI queue of ~s",
+ [U, S, jid:to_string(JID)]),
+ case queue_take({U, S}, Q) of
+ {Stanzas, Q1} ->
+ C2SState1 = flush_stanzas(C2SState, Stanzas),
+ C2SState1#{csi_queue => Q1};
- error ->
+ error ->
- []
+ C2SState
end.
- -spec get_stanzas(csi_queue(), binary()) -> [stanza()].
-
- get_stanzas(Queue, Host) ->
- lists:map(fun({_Key, Time, Stanza}) ->
- xmpp_util:add_delay_info(Stanza, jid:make(Host), Time,
- <<"Client Inactive">>)
- end, Queue).
+ -spec flush_queue(c2s_state()) -> c2s_state().
+ flush_queue(#{csi_queue := Q, jid := JID} = C2SState) ->
+ ?DEBUG("Flushing CSI queue of ~s", [jid:to_string(JID)]),
+ C2SState1 = flush_stanzas(C2SState, queue_to_list(Q)),
+ C2SState1#{csi_queue => queue_new()}.
+
+ -spec flush_stanzas(c2s_state(),
+ [{csi_type(), csi_timestamp(), stanza()}]) -> c2s_state().
+ flush_stanzas(#{lserver := LServer} = C2SState, Elems) ->
+ lists:foldl(
+ fun({_Type, Time, Stanza}, AccState) ->
+ Stanza1 = add_delay_info(Stanza, LServer, Time),
+ ejabberd_c2s:send(AccState, Stanza1)
+ end, C2SState, Elems).
+
+ -spec add_delay_info(stanza(), binary(), csi_timestamp()) -> stanza().
+ add_delay_info(Stanza, LServer, {_Seq, TimeStamp}) ->
+ Stanza1 = xmpp_util:add_delay_info(
+ Stanza, jid:make(LServer), TimeStamp,
+ <<"Client Inactive">>),
+ xmpp:put_meta(Stanza1, csi_resend, true).
-spec get_pep_node(message()) -> binary() | undefined.
-
get_pep_node(#message{from = #jid{luser = <<>>}}) ->
%% It's not PEP.
undefined;
fun(I) when is_integer(I), I > 0 -> I end,
?C2S_MAX_AUTH_FAILURES),
UnbanTS = p1_time_compat:system_time(seconds) + BanLifetime,
- case ets:lookup(failed_auth, Addr) of
+ Attempts = case ets:lookup(failed_auth, Addr) of
- [{Addr, N, _, _}] ->
+ [{Addr, N, _, _}] ->
- ets:insert(failed_auth, {Addr, N+1, UnbanTS, MaxFailures});
+ ets:insert(failed_auth,
+ {Addr, N+1, UnbanTS, MaxFailures}),
+ N+1;
- [] ->
+ [] ->
- ets:insert(failed_auth, {Addr, 1, UnbanTS, MaxFailures})
+ ets:insert(failed_auth,
+ {Addr, 1, UnbanTS, MaxFailures}),
+ 1
- end,
+ end,
- ok
+ if Attempts >= MaxFailures ->
+ log_and_disconnect(State, Attempts, UnbanTS);
+ true ->
+ State
+ end
end;
- c2s_auth_result(true, _User, _Server, _AddrPort) ->
- ok.
-
- -spec check_bl_c2s({true, binary(), binary()} | false,
- {inet:ip_address(), non_neg_integer()},
- binary()) -> {stop, {true, binary(), binary()}} | false.
- check_bl_c2s(_Acc, Addr, Lang) ->
+ c2s_auth_result(#{ip := {Addr, _}} = State, true, _User) ->
+ ets:delete(failed_auth, Addr),
+ State.
+
+ -spec c2s_stream_started(ejabberd_c2s:state(), stream_start())
+ -> ejabberd_c2s:state() | {stop, ejabberd_c2s:state()}.
+ c2s_stream_started(#{ip := {Addr, _}} = State, _) ->
+ ets:tab2list(failed_auth),
case ets:lookup(failed_auth, Addr) of
[{Addr, N, TS, MaxFailures}] when N >= MaxFailures ->
case TS > p1_time_compat:system_time(seconds) of
set_room_option(Acc, _Property, _Lang) ->
Acc.
- -spec user_receive_packet(stanza(), ejabberd_c2s:state(), jid(), jid(), jid()) -> stanza().
- user_receive_packet(Pkt, C2SState, JID, Peer, _To) ->
+ -spec user_receive_packet({stanza(), ejabberd_c2s:state()}) -> {stanza(), ejabberd_c2s:state()}.
+ user_receive_packet({Pkt, #{jid := JID} = C2SState}) ->
+ Peer = xmpp:get_from(Pkt),
LUser = JID#jid.luser,
LServer = JID#jid.lserver,
- case should_archive(Pkt, LServer) of
+ Pkt2 = case should_archive(Pkt, LServer) of
- true ->
+ true ->
- NewPkt = strip_my_archived_tag(Pkt, LServer),
- case store_msg(C2SState, NewPkt, LUser, LServer, Peer, recv) of
+ Pkt1 = strip_my_archived_tag(Pkt, LServer),
+ case store_msg(C2SState, Pkt1, LUser, LServer, Peer, recv) of
- {ok, ID} ->
+ {ok, ID} ->
- set_stanza_id(NewPkt, JID, ID);
+ set_stanza_id(Pkt1, JID, ID);
- _ ->
+ _ ->
- NewPkt
+ Pkt1
- end;
- _ ->
- Pkt
+ end;
+ _ ->
+ Pkt
- end.
+ end,
+ {Pkt2, C2SState}.
- -spec user_send_packet(stanza(), ejabberd_c2s:state(), jid(), jid()) -> stanza().
- user_send_packet(Pkt, C2SState, JID, Peer) ->
+ -spec user_send_packet({stanza(), ejabberd_c2s:state()}) -> {stanza(), ejabberd_c2s:state()}.
+ user_send_packet({Pkt, #{jid := JID} = C2SState}) ->
+ Peer = xmpp:get_to(Pkt),
LUser = JID#jid.luser,
LServer = JID#jid.lserver,
- case should_archive(Pkt, LServer) of
+ Pkt2 = case should_archive(Pkt, LServer) of
- true ->
+ true ->
- NewPkt = strip_my_archived_tag(Pkt, LServer),
- case store_msg(C2SState, xmpp:set_from_to(NewPkt, JID, Peer),
+ Pkt1 = strip_my_archived_tag(Pkt, LServer),
+ case store_msg(C2SState, xmpp:set_from_to(Pkt1, JID, Peer),
- LUser, LServer, Peer, send) of
- {ok, ID} ->
+ LUser, LServer, Peer, send) of
+ {ok, ID} ->
- set_stanza_id(NewPkt, JID, ID);
+ set_stanza_id(Pkt1, JID, ID);
- _ ->
+ _ ->
- NewPkt
+ Pkt1
- end;
- false ->
- Pkt
+ end;
+ false ->
+ Pkt
- end.
+ end,
+ {Pkt2, C2SState}.
- -spec user_send_packet_strip_tag(stanza(), ejabberd_c2s:state(),
- jid(), jid()) -> stanza().
- user_send_packet_strip_tag(Pkt, _C2SState, JID, _Peer) ->
+ -spec user_send_packet_strip_tag({stanza(), ejabberd_c2s:state()}) ->
+ {stanza(), ejabberd_c2s:state()}.
+ user_send_packet_strip_tag({Pkt, #{jid := JID} = C2SState}) ->
LServer = JID#jid.lserver,
- strip_my_archived_tag(Pkt, LServer).
+ {strip_my_archived_tag(Pkt, LServer), C2SState}.
-spec muc_filter_message(message(), mod_muc_room:state(),
jid(), jid(), binary()) -> message().
load_permanent_rooms(Host, ServerHost, Access,
HistorySize, RoomShaper) ->
+ RMod = gen_mod:ram_db_mod(ServerHost, ?MODULE),
lists:foreach(
fun(R) ->
- {Room, Host} = R#muc_room.name_host,
+ {Room, Host} = R#muc_room.name_host,
- case mnesia:dirty_read(muc_online_room, {Room, Host}) of
- [] ->
+ case RMod:find_online_room(Room, Host) of
+ error ->
- {ok, Pid} = mod_muc_room:start(Host,
- ServerHost, Access, Room,
- HistorySize, RoomShaper,
- R#muc_room.opts),
+ {ok, Pid} = mod_muc_room:start(Host,
+ ServerHost, Access, Room,
+ HistorySize, RoomShaper,
+ R#muc_room.opts),
- register_room(Host, Room, Pid);
- _ -> ok
+ RMod:register_online_room(Room, Host, Pid);
+ {ok, _} ->
+ ok
- end
- end,
- get_rooms(ServerHost, Host)).
+ end
+ end,
+ get_rooms(ServerHost, Host)).
start_new_room(Host, ServerHost, Access, Room,
HistorySize, RoomShaper, From,
undefined
end,
{result, #disco_items{node = Node, items = Items, rsm = ResRSM}};
- iq_disco_items(_Host, _From, Lang, _MaxRoomsDiscoItems, _Node, _RSM) ->
+ iq_disco_items(_ServerHost, _Host, _From, Lang, _MaxRoomsDiscoItems, _Node, _RSM) ->
{error, xmpp:err_item_not_found(<<"Node not found">>, Lang)}.
- -spec get_vh_rooms(binary, term(), rsm_set() | undefined) -> [disco_item()].
- get_vh_rooms(Host, Query,
- #rsm_set{max = Max, 'after' = After, before = undefined})
- when is_binary(After), After /= <<"">> ->
- lists:reverse(get_vh_rooms(next, {After, Host}, Host, Query, 0, Max, []));
- get_vh_rooms(Host, Query,
- #rsm_set{max = Max, 'after' = undefined, before = Before})
- when is_binary(Before), Before /= <<"">> ->
- get_vh_rooms(prev, {Before, Host}, Host, Query, 0, Max, []);
- get_vh_rooms(Host, Query,
- #rsm_set{max = Max, 'after' = undefined, before = <<"">>}) ->
- get_vh_rooms(last, {<<"">>, Host}, Host, Query, 0, Max, []);
- get_vh_rooms(Host, Query, #rsm_set{max = Max}) ->
- lists:reverse(get_vh_rooms(first, {<<"">>, Host}, Host, Query, 0, Max, []));
- get_vh_rooms(Host, Query, undefined) ->
- lists:reverse(get_vh_rooms(first, {<<"">>, Host}, Host, Query, 0, undefined, [])).
-
- -spec get_vh_rooms(prev | next | last | first,
- {binary(), binary()}, binary(), term(),
- non_neg_integer(), non_neg_integer() | undefined,
- [disco_item()]) -> [disco_item()].
- get_vh_rooms(_Action, _Key, _Host, _Query, Count, Max, Items) when Count >= Max ->
- Items;
- get_vh_rooms(Action, Key, Host, Query, Count, Max, Items) ->
- Call = fun() ->
- case Action of
- prev -> mnesia:dirty_prev(muc_online_room, Key);
- next -> mnesia:dirty_next(muc_online_room, Key);
- last -> mnesia:dirty_last(muc_online_room);
- first -> mnesia:dirty_first(muc_online_room)
- end
- end,
- NewAction = case Action of
- last -> prev;
- first -> next;
- _ -> Action
- end,
- try Call() of
- '$end_of_table' ->
- Items;
- {_, Host} = NewKey ->
- case get_room_disco_item(NewKey, Query) of
- {ok, Item} ->
- get_vh_rooms(NewAction, NewKey, Host, Query,
- Count + 1, Max, [Item|Items]);
- {error, _} ->
- get_vh_rooms(NewAction, NewKey, Host, Query,
- Count, Max, Items)
- end;
- NewKey ->
- get_vh_rooms(NewAction, NewKey, Host, Query, Count, Max, Items)
- catch _:{aborted, {badarg, _}} ->
- Items
- end.
-
- -spec get_room_disco_item({binary(), binary()}, term()) -> {ok, disco_item()} |
+ -spec get_room_disco_item({binary(), binary(), pid()},
+ term()) -> {ok, disco_item()} |
- {error, timeout | notfound}.
+ {error, timeout | notfound}.
- get_room_disco_item({Name, Host}, Query) ->
- case mnesia:dirty_read(muc_online_room, {Name, Host}) of
- [#muc_online_room{pid = Pid}|_] ->
+ get_room_disco_item({Name, Host, Pid}, Query) ->
- RoomJID = jid:make(Name, Host),
- try gen_fsm:sync_send_all_state_event(Pid, Query, 100) of
- {item, Desc} ->
- {ok, #disco_item{jid = RoomJID, name = Desc}};
- false ->
- {error, notfound}
- catch _:{timeout, _} ->
- {error, timeout};
- _:{noproc, _} ->
- {error, notfound}
+ RoomJID = jid:make(Name, Host),
+ try gen_fsm:sync_send_all_state_event(Pid, Query, 100) of
+ {item, Desc} ->
+ {ok, #disco_item{jid = RoomJID, name = Desc}};
+ false ->
+ {error, notfound}
+ catch _:{timeout, _} ->
+ {error, timeout};
+ _:{noproc, _} ->
+ {error, notfound}
- end;
- _ ->
- {error, notfound}
end.
- get_subscribed_rooms(_ServerHost, Host, From) ->
- Rooms = get_vh_rooms(Host),
+ get_subscribed_rooms(ServerHost, Host, From) ->
+ Rooms = get_online_rooms(ServerHost, Host),
BareFrom = jid:remove_resource(From),
lists:flatmap(
- fun(#muc_online_room{name_host = {Name, _}, pid = Pid}) ->
+ fun({Name, _, Pid}) ->
case gen_fsm:sync_send_all_state_event(Pid, {is_subscribed, BareFrom}) of
true -> [jid:make(Name, Host)];
false -> []
{error, xmpp:err_not_acceptable(ErrText, Lang)}
end.
- broadcast_service_message(Host, Msg) ->
+ -spec broadcast_service_message(binary(), binary(), message()) -> ok.
+ broadcast_service_message(ServerHost, Host, Msg) ->
lists:foreach(
- fun(#muc_online_room{pid = Pid}) ->
+ fun({_, _, Pid}) ->
- gen_fsm:send_all_state_event(
- Pid, {service_message, Msg})
+ gen_fsm:send_all_state_event(
+ Pid, {service_message, Msg})
- end, get_vh_rooms(Host)).
-
-
- get_vh_rooms(Host) ->
- mnesia:dirty_select(muc_online_room,
- [{#muc_online_room{name_host = '$1', _ = '_'},
- [{'==', {element, 2, '$1'}, Host}],
- ['$_']}]).
-
- -spec get_vh_rooms_count(binary()) -> non_neg_integer().
- get_vh_rooms_count(Host) ->
- ets:select_count(muc_online_room,
- ets:fun2ms(
- fun(#muc_online_room{name_host = {_, H}}) ->
- H == Host
- end)).
-
- clean_table_from_bad_node(Node) ->
- F = fun() ->
- Es = mnesia:select(
- muc_online_room,
- [{#muc_online_room{pid = '$1', _ = '_'},
- [{'==', {node, '$1'}, Node}],
- ['$_']}]),
- lists:foreach(fun(E) ->
- mnesia:delete_object(E)
- end, Es)
- end,
- mnesia:async_dirty(F).
-
- clean_table_from_bad_node(Node, Host) ->
- F = fun() ->
- Es = mnesia:select(
- muc_online_room,
- [{#muc_online_room{pid = '$1',
- name_host = {'_', Host},
- _ = '_'},
- [{'==', {node, '$1'}, Node}],
- ['$_']}]),
- lists:foreach(fun(E) ->
- mnesia:delete_object(E)
- end, Es)
- end,
- mnesia:async_dirty(F).
-
- update_tables() ->
- try
- case mnesia:table_info(muc_online_room, type) of
- ordered_set -> ok;
- _ ->
- case mnesia:delete_table(muc_online_room) of
- {atomic, ok} -> ok;
- Err -> erlang:error(Err)
- end
- end
- catch _:{aborted, {no_exists, muc_online_room}} -> ok;
- _:{aborted, {no_exists, muc_online_room, type}} -> ok;
- E:R ->
- ?ERROR_MSG("failed to update mnesia table '~s': ~p",
- [muc_online_room, {E, R}])
- end.
+ end, get_online_rooms(ServerHost, Host)).
+
+ -spec get_online_rooms(binary(), binary()) -> [{binary(), binary(), pid()}].
+ get_online_rooms(ServerHost, Host) ->
+ get_online_rooms(ServerHost, Host, undefined).
+
+ -spec get_online_rooms(binary(), binary(), undefined | rsm_set()) ->
+ [{binary(), binary(), pid()}].
+ get_online_rooms(ServerHost, Host, RSM) ->
+ RMod = gen_mod:ram_db_mod(ServerHost, ?MODULE),
+ RMod:get_online_rooms(Host, RSM).
+
+ -spec count_online_rooms(binary(), binary()) -> non_neg_integer().
+ count_online_rooms(ServerHost, Host) ->
+ RMod = gen_mod:ram_db_mod(ServerHost, ?MODULE),
+ RMod:count_online_rooms(Host).
opts_to_binary(Opts) ->
lists:map(
-spec get_room_state(binary(), binary()) -> mod_muc_room:state().
get_room_state(RoomName, MucService) ->
- case mnesia:dirty_read(muc_online_room,
- {RoomName, MucService})
- of
- [R] ->
- RoomPid = R#muc_online_room.pid,
+ case mod_muc:find_online_room(RoomName, MucService) of
+ {ok, RoomPid} ->
- get_room_state(RoomPid);
+ get_room_state(RoomPid);
- [] -> #state{}
+ error ->
+ #state{}
end.
-spec get_room_state(pid()) -> mod_muc_room:state().
#muc_registered{us_host = {{U, S}, RoomHost},
nick = Nick}).
- record_info(fields, muc_registered)}]),
- update_tables(MyHost),
- mnesia:add_table_index(muc_registered, nick);
+ %%%===================================================================
+ %%% gen_server callbacks
+ %%%===================================================================
+ init([Host, Opts]) ->
+ MyHost = proplists:get_value(host, Opts),
+ case gen_mod:db_mod(Host, Opts, mod_muc) of
+ ?MODULE ->
+ ejabberd_mnesia:create(?MODULE, muc_room,
+ [{disc_copies, [node()]},
+ {attributes,
+ record_info(fields, muc_room)}]),
+ ejabberd_mnesia:create(?MODULE, muc_registered,
+ [{disc_copies, [node()]},
+ {attributes,
- {attributes,
- record_info(fields, muc_online_room)}]),
++ record_info(fields, muc_registered)},
++ {index, [nick]}]),
++ update_tables(MyHost);
+ _ ->
+ ok
+ end,
+ case gen_mod:ram_db_mod(Host, Opts, mod_muc) of
+ ?MODULE ->
+ update_muc_online_table(),
+ ejabberd_mnesia:create(?MODULE, muc_online_room,
+ [{ram_copies, [node()]},
+ {type, ordered_set},
- catch ets:new(muc_online_users,
- [bag, named_table, public, {keypos, 2}]),
++ {attributes, record_info(fields, muc_online_room)}]),
+ mnesia:add_table_copy(muc_online_room, node(), ram_copies),
++ catch ets:new(muc_online_users, [bag, named_table, public, {keypos, 2}]),
+ clean_table_from_bad_node(node(), MyHost),
+ mnesia:subscribe(system);
+ _ ->
+ ok
+ end,
+ {ok, #state{}}.
+
+ handle_call(_Request, _From, State) ->
+ Reply = ok,
+ {reply, Reply, 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(Info, State) ->
+ ?ERROR_MSG("unexpected info: ~p", [Info]),
+ {noreply, State}.
+
+ terminate(_Reason, _State) ->
+ ok.
+
+ code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
%%%===================================================================
%%% Internal functions
%%%===================================================================
get_sm_identity(Acc, _From, _To, _Node, _Lang) ->
Acc.
- get_sm_items(_Acc, #jid{luser = U, lserver = S, lresource = R} = JID,
+ get_sm_items(_Acc, #jid{luser = U, lserver = S} = JID,
#jid{luser = U, lserver = S},
?NS_FLEX_OFFLINE, _Lang) ->
- case ejabberd_sm:get_session_pid(U, S, R) of
- Pid when is_pid(Pid) ->
+ ejabberd_sm:route(JID, {resend_offline, false}),
- Mod = gen_mod:db_mod(S, ?MODULE),
- Hdrs = Mod:read_message_headers(U, S),
- BareJID = jid:remove_resource(JID),
- {result, lists:map(
- fun({Seq, From, _To, _TS, _El}) ->
- Node = integer_to_binary(Seq),
- #disco_item{jid = BareJID,
- node = Node,
- name = jid:to_string(From)}
- end, Hdrs)};
+ Mod = gen_mod:db_mod(S, ?MODULE),
+ Hdrs = Mod:read_message_headers(U, S),
+ BareJID = jid:remove_resource(JID),
- Pid ! dont_ask_offline,
+ {result, lists:map(
+ fun({Seq, From, _To, _TS, _El}) ->
+ Node = integer_to_binary(Seq),
+ #disco_item{jid = BareJID,
+ node = Node,
+ name = jid:to_string(From)}
+ end, Hdrs)};
- none ->
- {result, []}
- end;
get_sm_items(Acc, _From, _To, _Node, _Lang) ->
Acc.
xmpp:set_subtag(Msg, #offline{items = [#offline_item{node = Node}]}).
-spec handle_offline_fetch(jid()) -> ok.
- handle_offline_fetch(#jid{luser = U, lserver = S, lresource = R}) ->
- case ejabberd_sm:get_session_pid(U, S, R) of
- none ->
- ok;
- Pid when is_pid(Pid) ->
- Pid ! dont_ask_offline,
+ handle_offline_fetch(#jid{luser = U, lserver = S} = JID) ->
+ ejabberd_sm:route(JID, {resend_offline, false}),
- lists:foreach(
- fun({Node, El}) ->
+ lists:foreach(
+ fun({Node, El}) ->
- NewEl = set_offline_tag(El, Node),
- Pid ! {route, xmpp:get_from(El), xmpp:get_to(El), NewEl}
- end, read_messages(U, S))
- end.
+ El1 = set_offline_tag(El, Node),
+ From = xmpp:get_from(El1),
+ To = xmpp:get_to(El1),
+ ejabberd_router:route(From, To, El1)
+ end, read_messages(U, S)).
-spec fetch_msg_by_node(jid(), binary()) -> error | {ok, #offline_msg{}}.
fetch_msg_by_node(To, Seq) ->
-spec add_delay_info(message(), binary(),
undefined | erlang:timestamp()) -> message().
-add_delay_info(Packet, _LServer, undefined) ->
- Packet;
-add_delay_info(Packet, LServer, {_, _, _} = TS) ->
+add_delay_info(Packet, LServer, TS) ->
+ NewTS = case TS of
+ undefined -> p1_time_compat:timestamp();
+ _ -> TS
+ end,
- xmpp_util:add_delay_info(Packet, jid:make(LServer), NewTS,
+ Packet1 = xmpp:put_meta(Packet, from_offline, true),
- xmpp_util:add_delay_info(Packet1, jid:make(LServer), TS,
++ xmpp_util:add_delay_info(Packet1, jid:make(LServer), NewTS,
<<"Offline storage">>).
+ -spec get_priority_from_presence(presence()) -> integer().
+ get_priority_from_presence(#presence{priority = Prio}) ->
+ case Prio of
+ undefined -> 0;
+ _ -> Prio
+ end.
+
export(LServer) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:export(LServer).
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host,
?NS_PRIVACY).
+ -spec disco_features({error, stanza_error()} | {result, [binary()]} | empty,
+ jid(), jid(), binary(), binary()) ->
+ {error, stanza_error()} | {result, [binary()]}.
+ disco_features({error, Err}, _From, _To, _Node, _Lang) ->
+ {error, Err};
+ disco_features(empty, _From, _To, <<"">>, _Lang) ->
+ {result, [?NS_PRIVACY]};
+ disco_features({result, Feats}, _From, _To, <<"">>, _Lang) ->
+ {result, [?NS_PRIVACY|Feats]};
+ disco_features(Acc, _From, _To, _Node, _Lang) ->
+ Acc.
+
-spec process_iq(iq()) -> iq().
- process_iq(IQ) ->
- xmpp:make_error(IQ, xmpp:err_not_allowed()).
+ process_iq(#iq{type = Type,
+ from = #jid{luser = U, lserver = S},
+ to = #jid{luser = U, lserver = S}} = IQ) ->
+ case Type of
+ get -> process_iq_get(IQ);
+ set -> process_iq_set(IQ)
+ end;
+ process_iq(#iq{lang = Lang} = IQ) ->
+ Txt = <<"Query to another users is forbidden">>,
+ xmpp:make_error(IQ, xmpp:err_forbidden(Txt, Lang)).
- -spec process_iq_get({error, stanza_error()} | {result, xmpp_element() | undefined},
- iq(), userlist()) -> {error, stanza_error()} |
- {result, xmpp_element() | undefined}.
- process_iq_get(_, #iq{lang = Lang,
+ -spec process_iq_get(iq()) -> iq().
+ process_iq_get(#iq{lang = Lang,
- sub_els = [#privacy_query{default = Default,
+ sub_els = [#privacy_query{default = Default,
- active = Active}]},
- _) when Default /= undefined; Active /= undefined ->
+ active = Active}]} = IQ)
+ when Default /= undefined; Active /= undefined ->
Txt = <<"Only <list/> element is allowed in this query">>,
- {error, xmpp:err_bad_request(Txt, Lang)};
- process_iq_get(_, #iq{from = From, lang = Lang,
- sub_els = [#privacy_query{lists = Lists}]},
- #userlist{name = Active}) ->
- #jid{luser = LUser, lserver = LServer} = From,
+ xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang));
+ process_iq_get(#iq{lang = Lang,
+ sub_els = [#privacy_query{lists = Lists}]} = IQ) ->
case Lists of
[] ->
- process_lists_get(LUser, LServer, Active, Lang);
+ process_lists_get(IQ);
[#privacy_list{name = ListName}] ->
- process_list_get(LUser, LServer, ListName, Lang);
+ process_list_get(IQ, ListName);
_ ->
Txt = <<"Too many <list/> elements">>,
- {error, xmpp:err_bad_request(Txt, Lang)}
+ xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang))
end;
- process_iq_get(Acc, _, _) ->
- Acc.
-
- -spec process_lists_get(binary(), binary(), binary(), binary()) ->
- {error, stanza_error()} | {result, privacy_query()}.
- process_lists_get(LUser, LServer, Active, Lang) ->
+ process_iq_get(#iq{lang = Lang} = IQ) ->
+ Txt = <<"No module is handling this query">>,
+ xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang)).
+
+ -spec process_lists_get(iq()) -> iq().
+ process_lists_get(#iq{from = #jid{luser = LUser, lserver = LServer},
+ lang = Lang,
+ meta = #{privacy_active_list := Active}} = IQ) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
case Mod:process_lists_get(LUser, LServer) of
error ->
Txt = <<"Database failure">>,
- {error, xmpp:err_internal_server_error(Txt, Lang)};
+ xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang));
{_Default, []} ->
- {result, #privacy_query{}};
+ xmpp:make_iq_result(IQ, #privacy_query{});
{Default, ListNames} ->
- {result,
+ xmpp:make_iq_result(
+ IQ,
- #privacy_query{active = Active,
- default = Default,
- lists = [#privacy_list{name = ListName}
+ #privacy_query{active = Active,
+ default = Default,
+ lists = [#privacy_list{name = ListName}
- || ListName <- ListNames]}}
+ || ListName <- ListNames]})
end.
- -spec process_list_get(binary(), binary(), binary(), binary()) ->
- {error, stanza_error()} | {result, privacy_query()}.
- process_list_get(LUser, LServer, Name, Lang) ->
+ -spec process_list_get(iq(), binary()) -> iq().
+ process_list_get(#iq{from = #jid{luser = LUser, lserver = LServer},
+ lang = Lang} = IQ, Name) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
case Mod:process_list_get(LUser, LServer, Name) of
error ->
Txt = <<"Database failure">>,
- {error, xmpp:err_internal_server_error(Txt, Lang)};
+ xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang));
not_found ->
Txt = <<"No privacy list with this name found">>,
- {error, xmpp:err_item_not_found(Txt, Lang)};
+ xmpp:make_error(IQ, xmpp:err_item_not_found(Txt, Lang));
Items ->
LItems = lists:map(fun encode_list_item/1, Items),
- {result,
+ xmpp:make_iq_result(
+ IQ,
- #privacy_query{
+ #privacy_query{
- lists = [#privacy_list{name = Name, items = LItems}]}}
+ lists = [#privacy_list{name = Name, items = LItems}]})
end.
-spec item_to_xml(listitem()) -> xmlel().
undefined -> none
end.
- -spec process_iq_set({error, stanza_error()} |
- {result, xmpp_element() | undefined} |
- {result, xmpp_element() | undefined, userlist()},
- iq(), #userlist{}) ->
- {error, stanza_error()} |
- {result, xmpp_element() | undefined} |
- {result, xmpp_element() | undefined, userlist()}.
- process_iq_set(_, #iq{from = From, lang = Lang,
+ -spec process_iq_set(iq()) -> iq().
+ process_iq_set(#iq{lang = Lang,
- sub_els = [#privacy_query{default = Default,
- active = Active,
+ sub_els = [#privacy_query{default = Default,
+ active = Active,
- lists = Lists}]},
- #userlist{} = UserList) ->
- #jid{luser = LUser, lserver = LServer} = From,
+ lists = Lists}]} = IQ) ->
case Lists of
[#privacy_list{items = Items, name = ListName}]
when Default == undefined, Active == undefined ->
%% From is the sender, To is the destination.
%% If Dir = out, User@Server is the sender account (From).
%% If Dir = in, User@Server is the destination account (To).
- -spec check_packet(allow | deny, binary(), binary(), userlist(),
- {jid(), jid(), stanza()}, in | out) -> allow | deny.
- check_packet(_, _User, _Server, _UserList,
- {#jid{luser = <<"">>, lserver = Server} = _From,
- #jid{lserver = Server} = _To, _},
- in) ->
+ -spec check_packet(allow | deny, ejabberd_c2s:state() | jid(),
+ stanza(), in | out) -> allow | deny.
+ check_packet(_, #{jid := #jid{luser = LUser, lserver = LServer},
+ privacy_list := #userlist{list = List, needdb = NeedDb}},
+ Packet, Dir) ->
+ From = xmpp:get_from(Packet),
+ To = xmpp:get_to(Packet),
+ case {From, To} of
+ {#jid{luser = <<"">>, lserver = LServer},
+ #jid{lserver = LServer}} when Dir == in ->
+ %% Allow any packets from local server
+ allow;
+ {#jid{lserver = LServer},
+ #jid{luser = <<"">>, lserver = LServer}} when Dir == out ->
+ %% Allow any packets to local server
- allow;
+ allow;
- check_packet(_, _User, _Server, _UserList,
- {#jid{lserver = Server} = _From,
- #jid{luser = <<"">>, lserver = Server} = _To, _},
- out) ->
+ {#jid{luser = LUser, lserver = LServer, lresource = <<"">>},
+ #jid{luser = LUser, lserver = LServer}} when Dir == in ->
+ %% Allow incoming packets from user's bare jid to his full jid
- allow;
+ allow;
- check_packet(_, _User, _Server, _UserList,
- {#jid{luser = User, lserver = Server} = _From,
- #jid{luser = User, lserver = Server} = _To, _},
- _Dir) ->
+ {#jid{luser = LUser, lserver = LServer},
+ #jid{luser = LUser, lserver = LServer, lresource = <<"">>}} when Dir == out ->
+ %% Allow outgoing packets from user's full jid to his bare JID
+ allow;
+ _ when List == [] ->
- allow;
- _ ->
- PType = case Packet of
- #message{} -> message;
- #iq{} -> iq;
- #presence{type = available} -> presence;
- #presence{type = unavailable} -> presence;
- _ -> other
- end,
- PType2 = case {PType, Dir} of
- {message, in} -> message;
- {iq, in} -> iq;
- {presence, in} -> presence_in;
- {presence, out} -> presence_out;
- {_, _} -> other
- end,
- LJID = case Dir of
- in -> jid:tolower(From);
- out -> jid:tolower(To)
+ allow;
- check_packet(_, User, Server,
- #userlist{list = List, needdb = NeedDb},
- {From, To, Packet}, Dir) ->
- case List of
- [] -> allow;
+ _ ->
+ PType = case Packet of
+ #message{} -> message;
+ #iq{} -> iq;
+ #presence{type = available} -> presence;
+ #presence{type = unavailable} -> presence;
+ _ -> other
+ end,
+ PType2 = case {PType, Dir} of
+ {message, in} -> message;
+ {iq, in} -> iq;
+ {presence, in} -> presence_in;
+ {presence, out} -> presence_out;
+ {_, _} -> other
end,
- {Subscription, Groups} = case NeedDb of
+ LJID = case Dir of
+ in -> jid:tolower(From);
+ out -> jid:tolower(To)
+ end,
- true ->
- ejabberd_hooks:run_fold(roster_get_jid_info,
+ {Subscription, Groups} =
+ case NeedDb of
- jid:nameprep(Server),
+ true ->
+ ejabberd_hooks:run_fold(roster_get_jid_info,
- {none, []},
+ LServer,
- [User, Server,
- LJID]);
- false -> {[], []}
+ {none, []},
- end,
+ [LUser, LServer, LJID]);
+ false ->
+ {[], []}
- check_packet_aux(List, PType2, LJID, Subscription,
- Groups)
- end.
+ end,
+ check_packet_aux(List, PType2, LJID, Subscription, Groups)
+ end;
+ check_packet(Acc, #jid{luser = LUser, lserver = LServer} = JID, Packet, Dir) ->
+ List = get_user_list(LUser, LServer),
+ check_packet(Acc, #{jid => JID, privacy_list => List}, Packet, Dir).
-spec check_packet_aux([listitem()],
message | iq | presence_in | presence_out | other,
dispatch_items(Host, LJID, Node, Stanza).
dispatch_items({FromU, FromS, FromR} = From, {ToU, ToS, ToR} = To,
- Node, Stanza) ->
+ Node, Stanza) ->
C2SPid = case ejabberd_sm:get_session_pid(ToU, ToS, ToR) of
- ToPid when is_pid(ToPid) -> ToPid;
- _ ->
- R = user_resource(FromU, FromS, FromR),
- case ejabberd_sm:get_session_pid(FromU, FromS, R) of
- FromPid when is_pid(FromPid) -> FromPid;
- _ -> undefined
- end
- end,
+ ToPid when is_pid(ToPid) -> ToPid;
+ _ ->
+ R = user_resource(FromU, FromS, FromR),
+ case ejabberd_sm:get_session_pid(FromU, FromS, R) of
+ FromPid when is_pid(FromPid) -> FromPid;
+ _ -> undefined
+ end
+ end,
if C2SPid == undefined -> ok;
- true ->
+ true ->
- ejabberd_c2s:send_filtered(C2SPid,
- {pep_message, <<Node/binary, "+notify">>},
+ C2SPid ! {send_filtered, {pep_message, <<Node/binary, "+notify">>},
- service_jid(From), jid:make(To),
+ service_jid(From), jid:make(To),
- Stanza)
+ Stanza}
end;
dispatch_items(From, To, _Node, Stanza) ->
ejabberd_router:route(service_jid(From), jid:make(To), Stanza).
lists:append([{U, S, R}], JIDs);
Pid ->
Show = case ejabberd_c2s:get_presence(Pid) of
- {_, _, <<"available">>, _} -> <<"online">>;
- {_, _, State, _} -> State
+ #presence{type = unavailable} -> <<"unavailable">>;
+ #presence{show = undefined} -> <<"online">>;
+ #presence{show = S} -> atom_to_binary(S, latin1)
- end,
+ end,
case lists:member(Show, ShowValues) of
%% If yes, item can be delivered
true -> lists:append([{U, S, R}], JIDs);
broadcast_stanza({LUser, LServer, <<>>}, Node, Nidx, Type, NodeOptions, SubsByDepth, NotifyType, BaseStanza, SHIM),
%% Handles implicit presence subscriptions
SenderResource = user_resource(LUser, LServer, LResource),
- case ejabberd_sm:get_session_pid(LUser, LServer, SenderResource) of
- C2SPid when is_pid(C2SPid) ->
- NotificationType = get_option(NodeOptions, notification_type, headline),
- Stanza = add_message_type(BaseStanza, NotificationType),
- %% set the from address on the notification to the bare JID of the account owner
- %% Also, add "replyto" if entity has presence subscription to the account owner
- %% See XEP-0163 1.1 section 4.3.1
+ NotificationType = get_option(NodeOptions, notification_type, headline),
+ Stanza = add_message_type(BaseStanza, NotificationType),
+ %% set the from address on the notification to the bare JID of the account owner
+ %% Also, add "replyto" if entity has presence subscription to the account owner
+ %% See XEP-0163 1.1 section 4.3.1
- ejabberd_c2s:broadcast(C2SPid,
- {pep_message, <<((Node))/binary, "+notify">>},
- _Sender = jid:make(LUser, LServer, <<"">>),
- _StanzaToSend = add_extended_headers(
- Stanza,
- _ReplyTo = extended_headers([Publisher])));
- _ ->
- ?DEBUG("~p@~p has no session; can't deliver ~p to contacts", [LUser, LServer, BaseStanza])
- end;
+ ejabberd_sm:route(jid:make(LUser, LServer, SenderResource),
+ {pep_message, <<((Node))/binary, "+notify">>,
+ jid:make(LUser, LServer, <<"">>),
+ add_extended_headers(
+ Stanza, extended_headers([Publisher]))});
broadcast_stanza(Host, _Publisher, Node, Nidx, Type, NodeOptions, SubsByDepth, NotifyType, BaseStanza, SHIM) ->
broadcast_stanza(Host, Node, Nidx, Type, NodeOptions, SubsByDepth, NotifyType, BaseStanza, SHIM).
%% Returns a list that may contain an xmlelement with the XEP-237 feature if it's enabled.
-spec get_versioning_feature([xmpp_element()], binary()) -> [xmpp_element()].
get_versioning_feature(Acc, Host) ->
- case roster_versioning_enabled(Host) of
- true ->
- [#rosterver_feature{}|Acc];
+ case gen_mod:is_loaded(Host, ?MODULE) of
+ true ->
- false -> []
+ case roster_versioning_enabled(Host) of
+ true ->
+ [#rosterver_feature{}|Acc];
+ false ->
+ Acc
+ end;
+ false ->
+ Acc
end.
roster_version(LServer, LUser) ->
end,
ejabberd_sm:get_user_resources(User, Server)).
- -spec get_subscription_lists({[ljid()], [ljid()]}, binary(), binary())
- -> {[ljid()], [ljid()]}.
- get_subscription_lists(_Acc, User, Server) ->
- LUser = jid:nodeprep(User),
- LServer = jid:nameprep(Server),
+ -spec user_receive_packet({stanza(), ejabberd_c2s:state()}) -> {stanza(), ejabberd_c2s:state()}.
+ user_receive_packet({#iq{type = set, meta = #{roster_item := Item}} = IQ, State}) ->
+ {IQ, roster_change(State, Item)};
+ user_receive_packet(Acc) ->
+ Acc.
+
+ -spec roster_change(ejabberd_c2s:state(), #roster{}) -> ejabberd_c2s:state().
+ roster_change(#{user := U, server := S, resource := R,
+ pres_a := PresA, pres_f := PresF, pres_t := PresT} = State,
+ #roster{jid = IJID, subscription = ISubscription}) ->
+ LIJID = jid:tolower(IJID),
+ IsFrom = (ISubscription == both) or (ISubscription == from),
+ IsTo = (ISubscription == both) or (ISubscription == to),
+ OldIsFrom = ?SETS:is_element(LIJID, PresF),
+ FSet = if IsFrom -> ?SETS:add_element(LIJID, PresF);
+ true -> ?SETS:del_element(LIJID, PresF)
+ end,
+ TSet = if IsTo -> ?SETS:add_element(LIJID, PresT);
+ true -> ?SETS:del_element(LIJID, PresT)
+ end,
+ State1 = State#{pres_f => FSet, pres_t => TSet},
+ case maps:get(pres_last, State, undefined) of
+ undefined ->
+ State1;
+ LastPres ->
+ From = jid:make(U, S, R),
+ To = jid:make(IJID),
+ Cond1 = IsFrom andalso not OldIsFrom,
+ Cond2 = not IsFrom andalso OldIsFrom andalso
+ ?SETS:is_element(LIJID, PresA),
+ if Cond1 ->
+ case ejabberd_hooks:run_fold(
+ privacy_check_packet, allow,
+ [State1, LastPres, out]) of
+ deny ->
+ ok;
+ allow ->
+ Pres = xmpp:set_from_to(LastPres, From, To),
+ ejabberd_router:route(From, To, Pres)
+ end,
+ A = ?SETS:add_element(LIJID, PresA),
+ State1#{pres_a => A};
+ Cond2 ->
+ PU = #presence{from = From, to = To, type = unavailable},
+ case ejabberd_hooks:run_fold(
+ privacy_check_packet, allow,
+ [State1, PU, out]) of
+ deny ->
+ ok;
+ allow ->
+ ejabberd_router:route(From, To, PU)
+ end,
+ A = ?SETS:del_element(LIJID, PresA),
+ State1#{pres_a => A};
+ true ->
+ State1
+ end
+ end.
+
+ -spec c2s_session_opened(ejabberd_c2s:state()) -> ejabberd_c2s:state().
+ c2s_session_opened(#{jid := #jid{luser = LUser, lserver = LServer} = JID,
+ pres_f := PresF, pres_t := PresT} = State) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
Items = Mod:get_only_items(LUser, LServer),
- fill_subscription_lists(LServer, Items, [], []).
+ {F, T} = fill_subscription_lists(Items, PresF, PresT),
+ LJID = jid:tolower(jid:remove_resource(JID)),
+ State#{pres_f => ?SETS:add(LJID, F), pres_t => ?SETS:add(LJID, T)}.
- fill_subscription_lists(LServer, [I | Is], F, T) ->
+ fill_subscription_lists([I | Is], F, T) ->
J = element(3, I#roster.usj),
- case I#roster.subscription of
+ {F1, T1} = case I#roster.subscription of
- both ->
+ both ->
- fill_subscription_lists(LServer, Is, [J | F], [J | T]);
+ {?SETS:add_element(J, F), ?SETS:add_element(J, T)};
- from ->
+ from ->
- fill_subscription_lists(LServer, Is, [J | F], T);
- to -> fill_subscription_lists(LServer, Is, F, [J | T]);
- _ -> fill_subscription_lists(LServer, Is, F, T)
- end;
- fill_subscription_lists(_LServer, [], F, T) ->
+ {?SETS:add_element(J, F), T};
+ to ->
+ {F, ?SETS:add_element(J, T)};
+ _ ->
+ {F, T}
+ end,
+ fill_subscription_lists(Is, F1, T1);
+ fill_subscription_lists([], F, T) ->
{F, T}.
ask_to_pending(subscribe) -> out;
end;
process_item_set_t(_LUser, _LServer, _) -> ok.
- -spec get_in_pending_subscriptions([presence()], binary(), binary()) -> [presence()].
- get_in_pending_subscriptions(Ls, User, Server) ->
- LServer = jid:nameprep(Server),
+ -spec c2s_self_presence({presence(), ejabberd_c2s:state()})
+ -> {presence(), ejabberd_c2s:state()}.
+ c2s_self_presence({_, #{pres_last := _}} = Acc) ->
+ Acc;
+ c2s_self_presence({#presence{type = available} = Pkt,
+ #{lserver := LServer} = State}) ->
+ Prio = get_priority_from_presence(Pkt),
+ if Prio >= 0 ->
- Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
- get_in_pending_subscriptions(Ls, User, Server, Mod).
+ State1 = resend_pending_subscriptions(State, Mod),
+ {Pkt, State1};
+ true ->
+ {Pkt, State}
+ end;
+ c2s_self_presence(Acc) ->
+ Acc.
- get_in_pending_subscriptions(Ls, User, Server, Mod) ->
- JID = jid:make(User, Server, <<"">>),
+ -spec resend_pending_subscriptions(ejabberd_c2s:state(), module()) -> ejabberd_c2s:state().
+ resend_pending_subscriptions(#{jid := JID} = State, Mod) ->
+ BareJID = jid:remove_resource(JID),
Result = Mod:get_only_items(JID#jid.luser, JID#jid.lserver),
- Ls ++ lists:flatmap(
- fun(#roster{ask = Ask} = R) when Ask == in; Ask == both ->
+ lists:foldl(
+ fun(#roster{ask = Ask} = R, AccState) when Ask == in; Ask == both ->
- Message = R#roster.askmessage,
- Status = if is_binary(Message) -> (Message);
- true -> <<"">>
- end,
+ Message = R#roster.askmessage,
+ Status = if is_binary(Message) -> (Message);
+ true -> <<"">>
+ end,
- [#presence{from = R#roster.jid, to = JID,
+ Sub = #presence{from = R#roster.jid, to = BareJID,
- type = subscribe,
+ type = subscribe,
- status = xmpp:mk_text(Status)}];
- (_) ->
- []
- end, Result).
+ status = xmpp:mk_text(Status)},
+ ejabberd_c2s:send(AccState, Sub);
+ (_, AccState) ->
+ AccState
+ end, State, Result).
+
+ -spec get_priority_from_presence(presence()) -> integer().
+ get_priority_from_presence(#presence{priority = Prio}) ->
+ case Prio of
+ undefined -> 0;
+ _ -> Prio
+ end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
jid:make(Server),
ResIQ).
- -spec get_subscription_lists({[ljid()], [ljid()]}, binary(), binary())
- -> {[ljid()], [ljid()]}.
- get_subscription_lists({F, T}, User, Server) ->
- LUser = jid:nodeprep(User),
- LServer = jid:nameprep(Server),
+ c2s_session_opened(#{jid := #jid{luser = LUser, lserver = LServer} = JID,
+ pres_f := PresF, pres_t := PresT} = State) ->
US = {LUser, LServer},
DisplayedGroups = get_user_displayed_groups(US),
- SRUsers = lists:usort(lists:flatmap(fun (Group) ->
+ SRUsers = lists:flatmap(fun(Group) ->
- get_group_users(LServer, Group)
- end,
+ get_group_users(LServer, Group)
+ end,
- DisplayedGroups)),
- SRJIDs = [{U1, S1, <<"">>} || {U1, S1} <- SRUsers],
- {lists:usort(SRJIDs ++ F), lists:usort(SRJIDs ++ T)}.
+ DisplayedGroups),
+ BareLJID = jid:tolower(jid:remove_resource(JID)),
+ PresBoth = lists:foldl(
+ fun({U, S}, Acc) ->
+ ?SETS:add_element({U, S, <<"">>}, Acc)
+ end, ?SETS:new(), [BareLJID|SRUsers]),
+ State#{pres_f => ?SETS:union(PresBoth, PresF),
+ pres_t => ?SETS:union(PresBoth, PresT)}.
-spec get_jid_info({subscription(), [binary()]}, binary(), binary(), jid())
-> {subscription(), [binary()]}.
_ -> RosterItem#roster{subscription = both, ask = none}
end.
- -spec get_subscription_lists({[ljid()], [ljid()]}, binary(), binary())
- -> {[ljid()], [ljid()]}.
- get_subscription_lists({F, T}, User, Server) ->
- LUser = jid:nodeprep(User),
- LServer = jid:nameprep(Server),
+ c2s_session_opened(#{jid := #jid{luser = LUser, lserver = LServer} = JID,
+ pres_f := PresF, pres_t := PresT} = State) ->
US = {LUser, LServer},
DisplayedGroups = get_user_displayed_groups(US),
- SRUsers = lists:usort(lists:flatmap(fun (Group) ->
+ SRUsers = lists:flatmap(fun(Group) ->
- get_group_users(LServer, Group)
- end,
+ get_group_users(LServer, Group)
+ end,
- DisplayedGroups)),
- SRJIDs = [{U1, S1, <<"">>} || {U1, S1} <- SRUsers],
- {lists:usort(SRJIDs ++ F), lists:usort(SRJIDs ++ T)}.
+ DisplayedGroups),
+ BareLJID = jid:tolower(jid:remove_resource(JID)),
+ PresBoth = lists:foldl(
+ fun({U, S}, Acc) ->
+ ?SETS:add_element({U, S, <<"">>}, Acc)
+ end, ?SETS:new(), [BareLJID|SRUsers]),
+ State#{pres_f => ?SETS:union(PresBoth, PresF),
+ pres_t => ?SETS:union(PresBoth, PresT)}.
-spec get_jid_info({subscription(), [binary()]}, binary(), binary(), jid())
-> {subscription(), [binary()]}.