From: Evgeniy Khramtsov Date: Fri, 20 Jan 2017 16:35:46 +0000 (+0300) Subject: Merge branch 'new_stream' X-Git-Tag: 17.03-beta~91 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=d5d906184f41232e8ab0b4de1308bfa49a783a61;p=ejabberd Merge branch 'new_stream' Conflicts: src/cyrsasl.erl src/ejabberd_c2s.erl src/ejabberd_cluster.erl src/ejabberd_frontend_socket.erl src/ejabberd_node_groups.erl src/ejabberd_router.erl src/mod_bosh.erl src/mod_ip_blacklist.erl src/mod_muc_mnesia.erl src/mod_offline.erl src/mod_proxy65_sm.erl --- d5d906184f41232e8ab0b4de1308bfa49a783a61 diff --cc include/ejabberd.hrl index f10d8d81e,ddf41f094..419e91d0e --- a/include/ejabberd.hrl +++ b/include/ejabberd.hrl @@@ -39,10 -39,8 +39,8 @@@ -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, diff --cc src/cyrsasl.erl index fcc83d975,5c7eb7edb..014df7e80 --- a/src/cyrsasl.erl +++ b/src/cyrsasl.erl @@@ -113,15 -111,9 +111,9 @@@ format_error(Mech, Reason) - 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, <<>>), @@@ -134,20 -126,19 +126,19 @@@ -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(). diff --cc src/ejabberd_cluster.erl index 17e21af94,5826d6d31..a331a0084 --- a/src/ejabberd_cluster.erl +++ b/src/ejabberd_cluster.erl @@@ -27,7 -27,8 +27,8 @@@ %% 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"). diff --cc src/ejabberd_hooks.erl index 589b0d6a3,f63d1d75c..9f782b235 --- a/src/ejabberd_hooks.erl +++ b/src/ejabberd_hooks.erl @@@ -375,9 -373,20 +373,20 @@@ run_fold1([{_Seq, Module, Function} | L 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()]. diff --cc src/ejabberd_piefxis.erl index 6eefc045b,9e6cbd715..1115f16cb --- a/src/ejabberd_piefxis.erl +++ b/src/ejabberd_piefxis.erl @@@ -491,8 -493,8 +490,8 @@@ process_privacy(#privacy_query{lists = %% 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. diff --cc src/ejabberd_s2s.erl index 07ae5e70e,bf3c5c06f..86cf1a1f5 --- a/src/ejabberd_s2s.erl +++ b/src/ejabberd_s2s.erl @@@ -296,25 -353,35 +353,35 @@@ do_route(From, To, Packet) - ?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}, @@@ -329,18 -395,24 +395,24 @@@ %% 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, @@@ -377,14 -449,17 +449,17 @@@ choose_pid(From, Pids) - 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, diff --cc src/ejabberd_s2s_in.erl index ffdadc135,f447cf9dd..3b4b6a989 --- a/src/ejabberd_s2s_in.erl +++ b/src/ejabberd_s2s_in.erl @@@ -1,11 -1,8 +1,8 @@@ - %%%---------------------------------------------------------------------- - %%% File : ejabberd_s2s_in.erl - %%% Author : Alexey Shchepin - %%% Purpose : Serve incoming s2s connection - %%% Created : 6 Dec 2002 by Alexey Shchepin + %%%------------------------------------------------------------------- + %%% Created : 12 Dec 2016 by Evgeny Khramtsov %%% %%% -%%% 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 @@@ -21,645 -18,314 +18,314 @@@ %%% 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, <<"">>). - - -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]. + []. diff --cc src/ejabberd_s2s_out.erl index fb3dfcbd5,70ab0cfe4..a923860f3 --- a/src/ejabberd_s2s_out.erl +++ b/src/ejabberd_s2s_out.erl @@@ -1,11 -1,8 +1,8 @@@ - %%%---------------------------------------------------------------------- - %%% File : ejabberd_s2s_out.erl - %%% Author : Alexey Shchepin - %%% Purpose : Manage outgoing server-to-server connections - %%% Created : 6 Dec 2002 by Alexey Shchepin + %%%------------------------------------------------------------------- + %%% Created : 16 Dec 2016 by Evgeny Khramtsov %%% %%% -%%% 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 @@@ -21,953 -18,356 +18,356 @@@ %%% 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, <<"">>). - - -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). diff --cc src/ejabberd_service.erl index 5003ff6ab,d84de3db4..dd949f2f9 --- a/src/ejabberd_service.erl +++ b/src/ejabberd_service.erl @@@ -1,11 -1,8 +1,8 @@@ - %%%---------------------------------------------------------------------- - %%% File : ejabberd_service.erl - %%% Author : Alexey Shchepin - %%% Purpose : External component management (XEP-0114) - %%% Created : 6 Dec 2002 by Alexey Shchepin + %%%------------------------------------------------------------------- + %%% Created : 11 Dec 2016 by Evgeny Khramtsov %%% %%% -%%% 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 @@@ -107,240 -84,132 +84,132 @@@ init([State, Opts]) - 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, <<"">>). - - -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 diff --cc src/ejabberd_sm.erl index 5abcaa572,5eb671149..98aaed573 --- a/src/ejabberd_sm.erl +++ b/src/ejabberd_sm.erl @@@ -745,10 -776,14 +776,14 @@@ force_update_presence({LUser, LServer} -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()]. diff --cc src/ejabberd_socket.erl index 4cf36a81c,83b7ae9b9..c7b57a6a1 --- a/src/ejabberd_socket.erl +++ b/src/ejabberd_socket.erl @@@ -49,9 -52,11 +52,10 @@@ 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(). @@@ -72,57 -87,56 +85,56 @@@ %%==================================================================== %% 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 + independent -> {ok, independent}; - xml_stream -> + xml_stream -> - 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 + MaxStanzaSize = proplists:get_value(max_stanza_size, Opts, infinity), + {ReceiverMod, Receiver, RecRef} = + try SockMod:custom_receiver(Socket) of - {receiver, RecMod, RecPid} -> + {receiver, RecMod, RecPid} -> - {RecMod, RecPid, RecMod}; - _ -> - RecPid = - ejabberd_receiver:start(Socket, - SockMod, - none, - MaxStanzaSize), - {ejabberd_receiver, RecPid, - RecPid} + {RecMod, RecPid, RecMod} + catch _:_ -> + RecPid = ejabberd_receiver:start( + Socket, SockMod, none, MaxStanzaSize), + {ejabberd_receiver, RecPid, 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 + 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 - ok -> ok; - {error, _Reason} -> SockMod:close(Socket) - end, - ReceiverMod:become_controller(Receiver, Pid); - {error, _Reason} -> + ok -> + ReceiverMod:become_controller(Receiver, Pid), + {ok, Receiver}; + Err -> + SockMod:close(Socket), + Err + end; + Err -> - SockMod:close(Socket), - case ReceiverMod of - ejabberd_receiver -> ReceiverMod:close(Receiver); - _ -> ok + SockMod:close(Socket), + case ReceiverMod of + ejabberd_receiver -> ReceiverMod:close(Receiver); + _ -> ok - end + end, + Err - end; - raw -> - case Module:start({SockMod, Socket}, Opts) of - {ok, Pid} -> - case SockMod:controlling_process(Socket, Pid) of + end; - independent -> ok; + raw -> + case Module:start({SockMod, Socket}, Opts) of + {ok, Pid} -> + case SockMod:controlling_process(Socket, Pid) of - ok -> ok; - {error, _Reason} -> SockMod:close(Socket) + ok -> + {ok, Pid}; + {error, _} = Err -> + SockMod:close(Socket), + Err - end; + end; - {error, _Reason} -> SockMod:close(Socket) + Err -> + SockMod:close(Socket), + Err - end + end end. connect(Addr, Port, Opts) -> diff --cc src/mod_blocking.erl index d4192447a,5195bfb73..3f9e90256 --- a/src/mod_blocking.erl +++ b/src/mod_blocking.erl @@@ -94,27 -95,26 +95,26 @@@ process_iq_set(#iq{lang = Lang, sub_el 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; @@@ -157,23 -155,21 +155,21 @@@ process_block(#iq{from = #jid{luser = L 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, @@@ -181,23 -177,22 +177,22 @@@ 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}) -> @@@ -208,17 -203,18 +203,18 @@@ 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(). @@@ -226,29 -222,34 +222,34 @@@ make_userlist(Name, List) - 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(). diff --cc src/mod_bosh.erl index cebf4e6ba,43a00edce..62dc31ac8 --- a/src/mod_bosh.erl +++ b/src/mod_bosh.erl @@@ -82,66 -76,50 +76,19 @@@ process(_Path, _Request) - #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, @@@ -261,132 -181,7 +150,134 @@@ mod_opt_type(max_pause) - 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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">>. diff --cc src/mod_caps.erl index 132f1ee72,d5a623669..391a3ba74 --- a/src/mod_caps.erl +++ b/src/mod_caps.erl @@@ -120,38 -135,42 +135,42 @@@ user_send_packet({#presence{type = avai 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 gen_mod:is_loaded(MyHost, ?MODULE) of + true -> - case make_my_disco_hash(MyHost) of + case make_my_disco_hash(MyHost) of - <<"">> -> Acc; + <<"">> -> + Acc; - Hash -> + Hash -> - [#caps{hash = <<"sha-1">>, node = ?EJABBERD_URI, version = Hash}|Acc] + [#caps{hash = <<"sha-1">>, node = ?EJABBERD_URI, + version = Hash}|Acc] + end; + false -> + Acc end. -spec disco_features({error, stanza_error()} | {result, [binary()]} | empty, @@@ -204,77 -221,32 +221,32 @@@ c2s_presence_in(C2SState 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) -> []. diff --cc src/mod_client_state.erl index aab89f0d6,175929a57..d38de6832 --- a/src/mod_client_state.erl +++ b/src/mod_client_state.erl @@@ -181,9 -214,9 +214,9 @@@ filter_chat_states({#message{from = Fro %% 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 @@@ -228,63 -265,58 +265,58 @@@ add_stream_feature(Features, Host) - %%-------------------------------------------------------------------- %% 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; diff --cc src/mod_fail2ban.erl index 2b5e0bfc5,e8cc29816..2c6ff618c --- a/src/mod_fail2ban.erl +++ b/src/mod_fail2ban.erl @@@ -67,21 -70,30 +69,30 @@@ c2s_auth_result(#{ip := {Addr, _}, lser 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 diff --cc src/mod_mam.erl index 721b06f03,ca5930218..f55c1ccf2 --- a/src/mod_mam.erl +++ b/src/mod_mam.erl @@@ -199,46 -196,50 +195,50 @@@ set_room_option(_Acc, {mam, Val}, _Lang 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(). diff --cc src/mod_muc.erl index dadc3e0f7,e75fb3893..a91fcc810 --- a/src/mod_muc.erl +++ b/src/mod_muc.erl @@@ -564,20 -620,22 +620,22 @@@ get_rooms(ServerHost, Host) - 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, @@@ -630,89 -687,30 +687,30 @@@ iq_disco_items(ServerHost, Host, 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 -> [] @@@ -793,72 -791,28 +791,28 @@@ process_iq_register_set(ServerHost, Hos {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( diff --cc src/mod_muc_log.erl index f1089212e,2f5b31992..700f7284e --- a/src/mod_muc_log.erl +++ b/src/mod_muc_log.erl @@@ -1169,13 -1168,11 +1168,11 @@@ get_room_occupants(RoomJIDString) - -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(). diff --cc src/mod_muc_mnesia.erl index 9c6ebf924,e73c31bcd..9fdd1dce2 --- a/src/mod_muc_mnesia.erl +++ b/src/mod_muc_mnesia.erl @@@ -161,6 -268,64 +284,62 @@@ import(_LServer, <<"muc_registered">> #muc_registered{us_host = {{U, S}, RoomHost}, nick = 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, - record_info(fields, muc_registered)}]), - update_tables(MyHost), - mnesia:add_table_index(muc_registered, nick); ++ 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}, - {attributes, - record_info(fields, muc_online_room)}]), ++ {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}]), ++ 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 %%%=================================================================== diff --cc src/mod_offline.erl index 432214f2e,8443fde0c..b34572ba8 --- a/src/mod_offline.erl +++ b/src/mod_offline.erl @@@ -276,25 -279,20 +278,20 @@@ get_sm_identity(Acc, #jid{luser = U, ls 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. @@@ -394,18 -399,15 +398,15 @@@ set_offline_tag(Msg, Node) - 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) -> @@@ -842,14 -869,20 +868,22 @@@ count_offline_messages(User, Server) - -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). diff --cc src/mod_privacy.erl index 97de32eee,6eb939c3c..cfced6d06 --- a/src/mod_privacy.erl +++ b/src/mod_privacy.erl @@@ -97,69 -99,89 +99,89 @@@ stop(Host) - 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 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 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(). @@@ -224,19 -246,11 +246,11 @@@ decode_value(Type, Value) - 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 -> @@@ -414,59 -469,66 +469,66 @@@ get_user_lists(User, Server) - %% 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, + LJID = case Dir of + in -> jid:tolower(From); + out -> jid:tolower(To) + end, - {Subscription, Groups} = case NeedDb of + {Subscription, Groups} = + case NeedDb of - true -> - ejabberd_hooks:run_fold(roster_get_jid_info, + true -> + ejabberd_hooks:run_fold(roster_get_jid_info, - jid:nameprep(Server), + LServer, - {none, []}, + {none, []}, - [User, Server, - LJID]); - false -> {[], []} + [LUser, LServer, LJID]); + false -> + {[], []} - end, + end, - check_packet_aux(List, PType2, LJID, Subscription, - Groups) - 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, diff --cc src/mod_pubsub.erl index 108c0b593,527c010a0..eba2cab29 --- a/src/mod_pubsub.erl +++ b/src/mod_pubsub.erl @@@ -2224,22 -2211,21 +2223,21 @@@ send_items(Host, Node, _Nidx, _Type, Op 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, <>}, + C2SPid ! {send_filtered, {pep_message, <>}, - 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). @@@ -2773,9 -2759,10 +2771,10 @@@ get_resource_state({U, S, R}, ShowValue 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); @@@ -3020,22 -3007,16 +3019,16 @@@ broadcast_stanza({LUser, LServer, LReso 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). diff --cc src/mod_roster.erl index 792bc1d43,c22d02bb6..44649631a --- a/src/mod_roster.erl +++ b/src/mod_roster.erl @@@ -214,10 -216,16 +216,16 @@@ roster_version_on_db(Host) - %% 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 gen_mod:is_loaded(Host, ?MODULE) of + true -> - case roster_versioning_enabled(Host) of - true -> - [#rosterver_feature{}|Acc]; + case roster_versioning_enabled(Host) of + true -> + [#rosterver_feature{}|Acc]; - false -> [] + false -> + Acc + end; + false -> + Acc end. roster_version(LServer, LUser) -> @@@ -460,26 -461,88 +461,88 @@@ push_item_version(Server, User, From, I 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; @@@ -772,27 -835,47 +835,47 @@@ process_item_set_t(LUser, LServer, #ros 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. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --cc src/mod_shared_roster.erl index 8400823f4,783954317..077f9bfab --- a/src/mod_shared_roster.erl +++ b/src/mod_shared_roster.erl @@@ -294,19 -292,21 +292,21 @@@ set_item(User, Server, Resource, Item) 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()]}. diff --cc src/mod_shared_roster_ldap.erl index 7ebceb9b3,777854b8e..e79bcc5c0 --- a/src/mod_shared_roster_ldap.erl +++ b/src/mod_shared_roster_ldap.erl @@@ -160,19 -161,21 +161,21 @@@ process_item(RosterItem, _Host) - _ -> 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()]}.