]> granicus.if.org Git - ejabberd/commitdiff
Merge branch 'new_stream'
authorEvgeniy Khramtsov <ekhramtsov@process-one.net>
Fri, 20 Jan 2017 16:35:46 +0000 (19:35 +0300)
committerEvgeniy Khramtsov <ekhramtsov@process-one.net>
Fri, 20 Jan 2017 16:35:46 +0000 (19:35 +0300)
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

68 files changed:
1  2 
ejabberd.yml.example
include/ejabberd.hrl
include/mod_muc.hrl
include/mod_muc_room.hrl
src/cyrsasl.erl
src/cyrsasl_digest.erl
src/ejabberd_admin.erl
src/ejabberd_app.erl
src/ejabberd_auth.erl
src/ejabberd_auth_anonymous.erl
src/ejabberd_bosh.erl
src/ejabberd_cluster.erl
src/ejabberd_config.erl
src/ejabberd_hooks.erl
src/ejabberd_http.erl
src/ejabberd_http_ws.erl
src/ejabberd_listener.erl
src/ejabberd_local.erl
src/ejabberd_piefxis.erl
src/ejabberd_receiver.erl
src/ejabberd_router_multicast.erl
src/ejabberd_s2s.erl
src/ejabberd_s2s_in.erl
src/ejabberd_s2s_out.erl
src/ejabberd_service.erl
src/ejabberd_sm.erl
src/ejabberd_socket.erl
src/ejabberd_sup.erl
src/ejabberd_web_admin.erl
src/gen_mod.erl
src/jlib.erl
src/mod_admin_extra.erl
src/mod_announce.erl
src/mod_blocking.erl
src/mod_bosh.erl
src/mod_caps.erl
src/mod_carboncopy.erl
src/mod_client_state.erl
src/mod_disco.erl
src/mod_fail2ban.erl
src/mod_http_fileserver.erl
src/mod_http_upload.erl
src/mod_last.erl
src/mod_mam.erl
src/mod_metrics.erl
src/mod_muc.erl
src/mod_muc_admin.erl
src/mod_muc_log.erl
src/mod_muc_mnesia.erl
src/mod_muc_riak.erl
src/mod_muc_room.erl
src/mod_muc_sql.erl
src/mod_offline.erl
src/mod_ping.erl
src/mod_pres_counter.erl
src/mod_privacy.erl
src/mod_privilege.erl
src/mod_proxy65.erl
src/mod_proxy65_service.erl
src/mod_proxy65_stream.erl
src/mod_pubsub.erl
src/mod_register.erl
src/mod_roster.erl
src/mod_service_log.erl
src/mod_shared_roster.erl
src/mod_shared_roster_ldap.erl
src/mod_vcard_xupdate.erl
src/scram.erl

Simple merge
index f10d8d81e22577c548cbf4dd01c6c71c927609aa,ddf41f0946caa092015ad25baddb170e58268404..419e91d0e1daf62e382247311cbb9bcefbfccc24
  
  -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,
Simple merge
Simple merge
diff --cc src/cyrsasl.erl
index fcc83d975c9b5a58e550d74dba00e2166e07071c,5c7eb7edb51146c3860061e15bd1f22b6a0c733b..014df7e809c60eb8b28ad30932eb05b3734a76ac
@@@ -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, <<>>),
  -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().
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
index 17e21af94ac04b92b16b9f7d223983ae4a342db1,5826d6d31be6bd442cdd0adda657384d371e5e0d..a331a008438fbf14f8b91016af8933a83e6324c9
@@@ -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").
Simple merge
index 589b0d6a37d9c70c34004b2f07833302df715711,f63d1d75cb9890f89fa1c7c5ee408d65f1726c61..9f782b235688b63ec72632faa904993e631799ea
@@@ -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()].
Simple merge
Simple merge
Simple merge
Simple merge
index 6eefc045bc9cc9c14dcd7f047e1e09d6917148a9,9e6cbd71583089d6dcdbdded9e2e402b41d2058e..1115f16cb0341fd238200b092c8a71d5ba7bf415
@@@ -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.
Simple merge
Simple merge
index 07ae5e70e7459221e767c8ff2a5c5727cdfa8359,bf3c5c06f9ce449d98caed404f0c55fb36e13e77..86cf1a1f5f35c9cea67a249de4a1ae0f9d49c869
@@@ -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},
          %% 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,
index ffdadc1351257b2acf9cb9479ca29f4efd79d100,f447cf9dd44980247896985f33e944beb49ac9b3..3b4b6a989446a605abca0a495f29c161b1df6d53
@@@ -1,11 -1,8 +1,8 @@@
- %%%----------------------------------------------------------------------
- %%% File    : ejabberd_s2s_in.erl
- %%% Author  : Alexey Shchepin <alexey@process-one.net>
- %%% Purpose : Serve incoming s2s connection
- %%% Created :  6 Dec 2002 by Alexey Shchepin <alexey@process-one.net>
+ %%%-------------------------------------------------------------------
+ %%% Created : 12 Dec 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
  %%%
  %%%
 -%%% ejabberd, Copyright (C) 2002-2016   ProcessOne
 +%%% ejabberd, Copyright (C) 2002-2017   ProcessOne
  %%%
  %%% This program is free software; you can redistribute it and/or
  %%% modify it under the terms of the GNU General Public License as
  %%% with this program; if not, write to the Free Software Foundation, Inc.,
  %%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  %%%
- %%%----------------------------------------------------------------------
+ %%%-------------------------------------------------------------------
  -module(ejabberd_s2s_in).
+ -behaviour(xmpp_stream_in).
  -behaviour(ejabberd_config).
+ -behaviour(ejabberd_socket).
  
- -author('alexey@process-one.net').
- -behaviour(p1_fsm).
- %% External exports
+ %% ejabberd_socket callbacks
  -export([start/2, start_link/2, socket_type/0]).
- -export([init/1, wait_for_stream/2,
-        wait_for_feature_request/2, stream_established/2,
-        handle_event/3, handle_sync_event/4, code_change/4,
-        handle_info/3, print_state/1, terminate/3, opt_type/1]).
+ %% ejabberd_config callbacks
+ -export([opt_type/1]).
+ %% xmpp_stream_in callbacks
+ -export([init/1, handle_call/3, handle_cast/2,
+        handle_info/2, terminate/2, code_change/3]).
+ -export([tls_options/1, tls_required/1, tls_verify/1, tls_enabled/1,
+        compress_methods/1,
+        unauthenticated_stream_features/1, authenticated_stream_features/1,
+        handle_stream_start/2, handle_stream_end/2,
+        handle_stream_established/1, handle_auth_success/4,
+        handle_auth_failure/4, handle_send/3, handle_recv/3, handle_cdata/2,
+        handle_unauthenticated_packet/2, handle_authenticated_packet/2]).
+ %% Hooks
+ -export([handle_unexpected_info/2, handle_unexpected_cast/2,
+        reject_unauthenticated_packet/2, process_closed/2]).
+ %% API
+ -export([stop/1, close/1, send/2, update_state/2, establish/1, add_hooks/0]).
  
  -include("ejabberd.hrl").
- -include("logger.hrl").
  -include("xmpp.hrl").
+ -include("logger.hrl").
  
- -define(DICT, dict).
- -record(state,
-       {socket                      :: ejabberd_socket:socket_state(),
-          sockmod = ejabberd_socket   :: ejabberd_socket | ejabberd_frontend_socket,
-          streamid = <<"">>           :: binary(),
-          shaper = none               :: shaper:shaper(),
-          tls = false                 :: boolean(),
-        tls_enabled = false         :: boolean(),
-          tls_required = false        :: boolean(),
-        tls_certverify = false      :: boolean(),
-          tls_options = []            :: list(),
-          server = <<"">>             :: binary(),
-        authenticated = false       :: boolean(),
-          auth_domain = <<"">>        :: binary(),
-        connections = (?DICT):new() :: ?TDICT,
-          timer = make_ref()          :: reference()}).
- -type state_name() :: wait_for_stream | wait_for_feature_request | stream_established.
- -type state() :: #state{}.
- -type fsm_next() :: {next_state, state_name(), state()}.
- -type fsm_stop() :: {stop, normal, state()}.
- -type fsm_transition() :: fsm_stop() | fsm_next().
- %%-define(DBGFSM, true).
- -ifdef(DBGFSM).
- -define(FSMOPTS, [{debug, [trace]}]).
- -else.
- -define(FSMOPTS, []).
- -endif.
+ -type state() :: map().
+ -export_type([state/0]).
  
+ %%%===================================================================
+ %%% API
+ %%%===================================================================
  start(SockData, Opts) ->
-     supervisor:start_child(ejabberd_s2s_in_sup,
-                             [SockData, Opts]).
+     case proplists:get_value(supervisor, Opts, true) of
+       true ->
+           supervisor:start_child(ejabberd_s2s_in_sup, [SockData, Opts]);
+       _ ->
+           xmpp_stream_in:start(?MODULE, [SockData, Opts],
+                                ejabberd_config:fsm_limit_opts(Opts))
+     end.
  
  start_link(SockData, Opts) ->
-     p1_fsm:start_link(ejabberd_s2s_in, [SockData, Opts],
-                       ?FSMOPTS ++ fsm_limit_opts(Opts)).
- socket_type() -> xml_stream.
- %%%----------------------------------------------------------------------
- %%% Callback functions from gen_fsm
- %%%----------------------------------------------------------------------
- init([{SockMod, Socket}, Opts]) ->
-     ?DEBUG("started: ~p", [{SockMod, Socket}]),
-     Shaper = case lists:keysearch(shaper, 1, Opts) of
-              {value, {_, S}} -> S;
-              _ -> none
-            end,
-     {StartTLS, TLSRequired, TLSCertverify} =
-         case ejabberd_config:get_option(
-                s2s_use_starttls,
-                fun(false) -> false;
-                   (true) -> true;
-                   (optional) -> optional;
-                   (required) -> required;
-                   (required_trusted) -> required_trusted
-                end,
-                false) of
-             UseTls
-               when (UseTls == undefined) or
-                    (UseTls == false) ->
-                 {false, false, false};
-             UseTls
-               when (UseTls == true) or
-                    (UseTls ==
-                         optional) ->
-                 {true, false, false};
-             required -> {true, true, false};
-             required_trusted ->
-                 {true, true, true}
-         end,
-     TLSOpts1 = case ejabberd_config:get_option(
-                      s2s_certfile,
-                      fun iolist_to_binary/1) of
-                   undefined -> [];
-                   CertFile -> [{certfile, CertFile}]
-             end,
-     TLSOpts2 = case ejabberd_config:get_option(
-                       s2s_ciphers, fun iolist_to_binary/1) of
-                    undefined -> TLSOpts1;
-                    Ciphers -> [{ciphers, Ciphers} | TLSOpts1]
-                end,
-     TLSOpts3 = case ejabberd_config:get_option(
-                       s2s_protocol_options,
-                       fun (Options) ->
-                               [_|O] = lists:foldl(
-                                            fun(X, Acc) -> X ++ Acc end, [],
-                                            [["|" | binary_to_list(Opt)] || Opt <- Options, is_binary(Opt)]
-                                           ),
-                               iolist_to_binary(O)
-                       end) of
-                    undefined -> TLSOpts2;
-                    ProtocolOpts -> [{protocol_options, ProtocolOpts} | TLSOpts2]
-                end,
-     TLSOpts4 = case ejabberd_config:get_option(
-                       s2s_dhfile, fun iolist_to_binary/1) of
-                    undefined -> TLSOpts3;
-                    DHFile -> [{dhfile, DHFile} | TLSOpts3]
-                end,
-     TLSOpts = case proplists:get_bool(tls_compression, Opts) of
-                   false -> [compression_none | TLSOpts4];
-                   true -> TLSOpts4
-               end,
-     Timer = erlang:start_timer(?S2STIMEOUT, self(), []),
-     {ok, wait_for_stream,
-      #state{socket = Socket, sockmod = SockMod,
-           streamid = new_id(), shaper = Shaper, tls = StartTLS,
-           tls_enabled = false, tls_required = TLSRequired,
-           tls_certverify = TLSCertverify, tls_options = TLSOpts,
-           timer = Timer}}.
- %%----------------------------------------------------------------------
- %% Func: StateName/2
- %% Returns: {next_state, NextStateName, NextStateData}          |
- %%          {next_state, NextStateName, NextStateData, Timeout} |
- %%          {stop, Reason, NewStateData}
- %%----------------------------------------------------------------------
- wait_for_stream({xmlstreamstart, Name, Attrs}, StateData) ->
-     try xmpp:decode(#xmlel{name = Name, attrs = Attrs}) of
-       #stream_start{xmlns = NS_SERVER, stream_xmlns = NS_STREAM}
-         when NS_SERVER /= ?NS_SERVER; NS_STREAM /= ?NS_STREAM ->
-           send_header(StateData, {1,0}),
-           send_element(StateData, xmpp:serr_invalid_namespace()),
-           {stop, normal, StateData};
-       #stream_start{to = #jid{lserver = Server},
-                     from = From, version = {1,0}}
-         when StateData#state.tls and not StateData#state.authenticated ->
-           send_header(StateData, {1,0}),
-           Auth = if StateData#state.tls_enabled ->
-                          case From of
-                              #jid{} ->
-                                  {Result, Message} =
-                                      ejabberd_s2s:check_peer_certificate(
-                                        StateData#state.sockmod,
-                                        StateData#state.socket,
-                                        From#jid.lserver),
-                                  {Result, From#jid.lserver, Message};
-                              undefined ->
-                                  {error, <<"(unknown)">>,
-                                   <<"Got no valid 'from' attribute">>}
-                          end;
+     xmpp_stream_in:start_link(?MODULE, [SockData, Opts],
+                             ejabberd_config:fsm_limit_opts(Opts)).
+ close(Ref) ->
+     xmpp_stream_in:close(Ref).
+ stop(Ref) ->
+     xmpp_stream_in:stop(Ref).
+ socket_type() ->
+     xml_stream.
+ -spec send(pid(), xmpp_element()) -> ok;
+         (state(), xmpp_element()) -> state().
+ send(Stream, Pkt) ->
+     xmpp_stream_in:send(Stream, Pkt).
+ -spec establish(state()) -> state().
+ establish(State) ->
+     xmpp_stream_in:establish(State).
+ -spec update_state(pid(), fun((state()) -> state()) |
+                  {module(), atom(), list()}) -> ok.
+ update_state(Ref, Callback) ->
+     xmpp_stream_in:cast(Ref, {update_state, Callback}).
+ -spec add_hooks() -> ok.
+ add_hooks() ->
+     lists:foreach(
+       fun(Host) ->
+             ejabberd_hooks:add(s2s_in_closed, Host, ?MODULE,
+                                process_closed, 100),
+             ejabberd_hooks:add(s2s_in_unauthenticated_packet, Host, ?MODULE,
+                                reject_unauthenticated_packet, 100),
+             ejabberd_hooks:add(s2s_in_handle_info, Host, ?MODULE,
+                                handle_unexpected_info, 100),
+             ejabberd_hooks:add(s2s_in_handle_cast, Host, ?MODULE,
+                                handle_unexpected_cast, 100)
+       end, ?MYHOSTS).
+ %%%===================================================================
+ %%% Hooks
+ %%%===================================================================
+ handle_unexpected_info(State, Info) ->
+     ?WARNING_MSG("got unexpected info: ~p", [Info]),
+     State.
+ handle_unexpected_cast(State, Msg) ->
+     ?WARNING_MSG("got unexpected cast: ~p", [Msg]),
+     State.
+ reject_unauthenticated_packet(State, _Pkt) ->
+     Err = xmpp:serr_not_authorized(),
+     send(State, Err).
+ process_closed(State, _Reason) ->
+     stop(State).
+ %%%===================================================================
+ %%% xmpp_stream_in callbacks
+ %%%===================================================================
+ tls_options(#{tls_options := TLSOpts, server_host := LServer}) ->
+     ejabberd_s2s:tls_options(LServer, TLSOpts).
+ tls_required(#{server_host := LServer}) ->
+     ejabberd_s2s:tls_required(LServer).
+ tls_verify(#{server_host := LServer}) ->
+     ejabberd_s2s:tls_verify(LServer).
+ tls_enabled(#{server_host := LServer}) ->
+     ejabberd_s2s:tls_enabled(LServer).
+ compress_methods(#{server_host := LServer}) ->
+     case ejabberd_s2s:zlib_enabled(LServer) of
+       true -> [<<"zlib">>];
+       false -> []
+     end.
+ unauthenticated_stream_features(#{server_host := LServer}) ->
+     ejabberd_hooks:run_fold(s2s_in_pre_auth_features, LServer, [], [LServer]).
+ authenticated_stream_features(#{server_host := LServer}) ->
+     ejabberd_hooks:run_fold(s2s_in_post_auth_features, LServer, [], [LServer]).
+ handle_stream_start(_StreamStart, #{lserver := LServer} = State) ->
+     case check_to(jid:make(LServer), State) of
+       false ->
+           send(State, xmpp:serr_host_unknown());
 -      true ->
 +                    true ->
-                          {no_verify, <<"(unknown)">>, <<"TLS not (yet) enabled">>}
-                  end,
-           StartTLS = if StateData#state.tls_enabled -> [];
-                         not StateData#state.tls_enabled and
-                         not StateData#state.tls_required ->
-                              [#starttls{required = false}];
-                         not StateData#state.tls_enabled and
-                         StateData#state.tls_required ->
-                              [#starttls{required = true}]
-                      end,
-           case Auth of
-               {error, RemoteServer, CertError}
-                 when StateData#state.tls_certverify ->
-                   ?INFO_MSG("Closing s2s connection: ~s <--> ~s (~s)",
-                             [StateData#state.server, RemoteServer, CertError]),
-                   send_element(StateData,
-                                xmpp:serr_policy_violation(CertError, ?MYLANG)),
-                   {stop, normal, StateData};
-               {VerifyResult, RemoteServer, Msg} ->
-                   {SASL, NewStateData} =
-                       case VerifyResult of
-                           ok ->
-                               {[#sasl_mechanisms{list = [<<"EXTERNAL">>]}],
-                                StateData#state{auth_domain = RemoteServer}};
-                           error ->
-                               ?DEBUG("Won't accept certificate of ~s: ~s",
-                                      [RemoteServer, Msg]),
-                               {[], StateData};
-                           no_verify ->
-                               {[], StateData}
-                       end,
-                   send_element(NewStateData,
-                                #stream_features{
-                                   sub_els = SASL ++ StartTLS ++
-                                       ejabberd_hooks:run_fold(
-                                         s2s_stream_features, Server, [],
-                                         [Server])}),
-                   {next_state, wait_for_feature_request,
-                    NewStateData#state{server = Server}}
-           end;
-       #stream_start{to = #jid{lserver = Server},
-                     version = {1,0}} when StateData#state.authenticated ->
-           send_header(StateData, {1,0}),
-           send_element(StateData,
-                        #stream_features{
-                           sub_els = ejabberd_hooks:run_fold(
-                                       s2s_stream_features, Server, [],
-                                       [Server])}),
-           {next_state, stream_established, StateData};
-       #stream_start{db_xmlns = ?NS_SERVER_DIALBACK}
-         when (StateData#state.tls_required and StateData#state.tls_enabled)
-              or (not StateData#state.tls_required) ->
-           send_header(StateData, undefined),
-           {next_state, stream_established, StateData};
-       #stream_start{} ->
-           send_header(StateData, {1,0}),
-           send_element(StateData, xmpp:serr_undefined_condition()),
-           {stop, normal, StateData};
-       _ ->
-           send_header(StateData, {1,0}),
-             send_element(StateData, xmpp:serr_invalid_xml()),
-             {stop, normal, StateData}
-     catch _:{xmpp_codec, Why} ->
-           Txt = xmpp:format_error(Why),
-           send_header(StateData, {1,0}),
-           send_element(StateData, xmpp:serr_invalid_xml(Txt, ?MYLANG)),
-           {stop, normal, StateData}
-     end;
- wait_for_stream({xmlstreamerror, _}, StateData) ->
-     send_header(StateData, {1,0}),
-     send_element(StateData, xmpp:serr_not_well_formed()),
-     {stop, normal, StateData};
- wait_for_stream(timeout, StateData) ->
-     send_header(StateData, {1,0}),
-     send_element(StateData, xmpp:serr_connection_timeout()),
-     {stop, normal, StateData};
- wait_for_stream(closed, StateData) ->
-     {stop, normal, StateData}.
- wait_for_feature_request({xmlstreamelement, El}, StateData) ->
-     decode_element(El, wait_for_feature_request, StateData);
- wait_for_feature_request(#starttls{},
-                        #state{tls = true, tls_enabled = false} = StateData) ->
-     case (StateData#state.sockmod):get_sockmod(StateData#state.socket) of
-       gen_tcp ->
-           ?DEBUG("starttls", []),
-           Socket = StateData#state.socket,
-           TLSOpts1 = case
-                          ejabberd_config:get_option(
-                            {domain_certfile, StateData#state.server},
-                            fun iolist_to_binary/1) of
-                          undefined -> StateData#state.tls_options;
-                          CertFile ->
-                              lists:keystore(certfile, 1,
-                                             StateData#state.tls_options,
-                                             {certfile, CertFile})
-                      end,
-           TLSOpts2 = case ejabberd_config:get_option(
-                             {s2s_cafile, StateData#state.server},
-                             fun iolist_to_binary/1) of
-                          undefined -> TLSOpts1;
-                          CAFile ->
-                              lists:keystore(cafile, 1, TLSOpts1,
-                                             {cafile, CAFile})
-                      end,
-           TLSOpts = case ejabberd_config:get_option(
-                            {s2s_tls_compression, StateData#state.server},
-                            fun(true) -> true;
-                               (false) -> false
-                            end, false) of
-                         true -> lists:delete(compression_none, TLSOpts2);
-                         false -> [compression_none | TLSOpts2]
-                     end,
-           TLSSocket = (StateData#state.sockmod):starttls(
-                         Socket, TLSOpts,
-                         fxml:element_to_binary(
-                           xmpp:encode(#starttls_proceed{}))),
-           {next_state, wait_for_stream,
-            StateData#state{socket = TLSSocket, streamid = new_id(),
-                            tls_enabled = true, tls_options = TLSOpts}};
-       _ ->
-             send_element(StateData, #starttls_failure{}),
-             {stop, normal, StateData}
-     end;
- wait_for_feature_request(#sasl_auth{mechanism = Mech},
-                        #state{tls_enabled = true} = StateData) ->
-     case Mech of
-       <<"EXTERNAL">> when StateData#state.auth_domain /= <<"">> ->
-           AuthDomain = StateData#state.auth_domain,
-           AllowRemoteHost = ejabberd_s2s:allow_host(<<"">>, AuthDomain),
-           if AllowRemoteHost ->
-                   (StateData#state.sockmod):reset_stream(StateData#state.socket),
-                   send_element(StateData, #sasl_success{}),
-                   ?INFO_MSG("Accepted s2s EXTERNAL authentication for ~s (TLS=~p)",
-                             [AuthDomain, StateData#state.tls_enabled]),
-                   change_shaper(StateData, <<"">>, jid:make(AuthDomain)),
-                   {next_state, wait_for_stream,
-                    StateData#state{streamid = new_id(),
-                                    authenticated = true}};
+           ServerHost = ejabberd_router:host_of_route(LServer),
+           State#{server_host => ServerHost}
+     end.
+ handle_stream_end(Reason, #{server_host := LServer} = State) ->
+     ejabberd_hooks:run_fold(s2s_in_closed, LServer, State, [Reason]).
+ handle_stream_established(State) ->
+     set_idle_timeout(State#{established => true}).
+ handle_auth_success(RServer, Mech, _AuthModule,
+                   #{sockmod := SockMod,
+                     socket := Socket, ip := IP,
+                     auth_domains := AuthDomains,
+                     server_host := ServerHost,
+                     lserver := LServer} = State) ->
+     ?INFO_MSG("(~s) Accepted inbound s2s ~s authentication ~s -> ~s (~s)",
+             [SockMod:pp(Socket), Mech, RServer, LServer,
+              ejabberd_config:may_hide_data(jlib:ip_to_list(IP))]),
+     State1 = case ejabberd_s2s:allow_host(ServerHost, RServer) of
 -               true ->
 +             true ->
-                   Txt = xmpp:mk_text(<<"Denied by ACL">>, ?MYLANG),
-                   send_element(StateData,
-                                #sasl_failure{reason = 'not-authorized',
-                                              text = Txt}),
-                   {stop, normal, StateData}
-           end;
-       _ ->
-           send_element(StateData, #sasl_failure{reason = 'invalid-mechanism'}),
-           {stop, normal, StateData}
-     end;
- wait_for_feature_request({xmlstreamend, _Name}, StateData) ->
-     {stop, normal, StateData};
- wait_for_feature_request({xmlstreamerror, _}, StateData) ->
-     send_element(StateData, xmpp:serr_not_well_formed()),
-     {stop, normal, StateData};
- wait_for_feature_request(closed, StateData) ->
-     {stop, normal, StateData};
- wait_for_feature_request(_Pkt, #state{tls_required = TLSRequired,
-                                     tls_enabled = TLSEnabled} = StateData)
-   when TLSRequired and not TLSEnabled ->
-     Txt = <<"Use of STARTTLS required">>,
-     send_element(StateData, xmpp:serr_policy_violation(Txt, ?MYLANG)),
-     {stop, normal, StateData};
- wait_for_feature_request(El, StateData) ->
-     stream_established({xmlstreamelement, El}, StateData).
- stream_established({xmlstreamelement, El}, StateData) ->
-     cancel_timer(StateData#state.timer),
-     Timer = erlang:start_timer(?S2STIMEOUT, self(), []),
-     decode_element(El, stream_established, StateData#state{timer = Timer});
- stream_established(#db_result{to = To, from = From, key = Key},
-                  StateData) ->
-     ?DEBUG("GET KEY: ~p", [{To, From, Key}]),
-     case {ejabberd_s2s:allow_host(To, From),
-         lists:member(To, ejabberd_router:dirty_get_all_domains())} of
-       {true, true} ->
-           ejabberd_s2s_out:terminate_if_waiting_delay(To, From),
-           ejabberd_s2s_out:start(To, From,
-                                  {verify, self(), Key,
-                                   StateData#state.streamid}),
-           Conns = (?DICT):store({From, To},
-                                 wait_for_verification,
-                                 StateData#state.connections),
-           change_shaper(StateData, To, jid:make(From)),
-           {next_state, stream_established,
-            StateData#state{connections = Conns}};
-       {_, false} ->
-           send_element(StateData, xmpp:serr_host_unknown()),
-           {stop, normal, StateData};
-       {false, _} ->
-           send_element(StateData, xmpp:serr_invalid_from()),
-           {stop, normal, StateData}
-     end;
- stream_established(#db_verify{to = To, from = From, id = Id, key = Key},
-                  StateData) ->
-     ?DEBUG("VERIFY KEY: ~p", [{To, From, Id, Key}]),
-     Type = case ejabberd_s2s:make_key({To, From}, Id) of
-              Key -> valid;
-              _ -> invalid
+                    AuthDomains1 = sets:add_element(RServer, AuthDomains),
+                    change_shaper(State, RServer),
+                    State#{auth_domains => AuthDomains1};
+                false ->
+                    State
 -           end,
 +         end,
-     send_element(StateData,
-                #db_verify{from = To, to = From, id = Id, type = Type}),
-     {next_state, stream_established, StateData};
- stream_established(Pkt, StateData) when ?is_stanza(Pkt) ->
+     ejabberd_hooks:run_fold(s2s_in_auth_result, ServerHost, State1, [true, RServer]).
+ handle_auth_failure(RServer, Mech, Reason,
+                   #{sockmod := SockMod,
+                     socket := Socket, ip := IP,
+                     server_host := ServerHost,
+                     lserver := LServer} = State) ->
+     ?INFO_MSG("(~s) Failed inbound s2s ~s authentication ~s -> ~s (~s): ~s",
+             [SockMod:pp(Socket), Mech, RServer, LServer,
+              ejabberd_config:may_hide_data(jlib:ip_to_list(IP)), Reason]),
+     ejabberd_hooks:run_fold(s2s_in_auth_result,
+                           ServerHost, State, [false, RServer]).
+ handle_unauthenticated_packet(Pkt, #{server_host := LServer} = State) ->
+     ejabberd_hooks:run_fold(s2s_in_unauthenticated_packet,
+                           LServer, State, [Pkt]).
+ handle_authenticated_packet(Pkt, #{server_host := LServer} = State) when not ?is_stanza(Pkt) ->
+     ejabberd_hooks:run_fold(s2s_in_authenticated_packet, LServer, State, [Pkt]);
+ handle_authenticated_packet(Pkt, State) ->
      From = xmpp:get_from(Pkt),
      To = xmpp:get_to(Pkt),
-     if To /= undefined, From /= undefined ->
-           LFrom = From#jid.lserver,
-           LTo = To#jid.lserver,
-           if StateData#state.authenticated ->
-                   case LFrom == StateData#state.auth_domain andalso
-                       lists:member(LTo, ejabberd_router:dirty_get_all_domains()) of
-                       true ->
-                           ejabberd_hooks:run(s2s_receive_packet, LTo,
-                                              [From, To, Pkt]),
-                           ejabberd_router:route(From, To, Pkt);
-                       false ->
-                           send_error(StateData, Pkt, xmpp:err_not_authorized())
-                   end;
-              true ->
-                   case (?DICT):find({LFrom, LTo}, StateData#state.connections) of
-                       {ok, established} ->
-                           ejabberd_hooks:run(s2s_receive_packet, LTo,
-                                              [From, To, Pkt]),
-                           ejabberd_router:route(From, To, Pkt);
-                       _ ->
-                           send_error(StateData, Pkt, xmpp:err_not_authorized())
-                   end
-           end;
-        true ->
-           send_error(StateData, Pkt, xmpp:err_jid_malformed())
+     case check_from_to(From, To, State) of
+       ok ->
+           LServer = ejabberd_router:host_of_route(To#jid.lserver),
+           State1 = ejabberd_hooks:run_fold(s2s_in_authenticated_packet,
+                                            LServer, State, [Pkt]),
+           {Pkt1, State2} = ejabberd_hooks:run_fold(s2s_receive_packet, LServer,
+                                                    {Pkt, State1}, []),
+           case Pkt1 of
+               drop -> ok;
+               _ -> ejabberd_router:route(From, To, Pkt1)
 -          end,
 +    end,
-     ejabberd_hooks:run(s2s_loop_debug, [{xmlstreamelement, Pkt}]),
-     {next_state, stream_established, StateData};
- stream_established({valid, From, To}, StateData) ->
-     send_element(StateData,
-                #db_result{from = To, to = From, type = valid}),
-     ?INFO_MSG("Accepted s2s dialback authentication for ~s (TLS=~p)",
-             [From, StateData#state.tls_enabled]),
-     NSD = StateData#state{connections =
-                             (?DICT):store({From, To}, established,
-                                           StateData#state.connections)},
-     {next_state, stream_established, NSD};
- stream_established({invalid, From, To}, StateData) ->
-     send_element(StateData,
-                #db_result{from = To, to = From, type = invalid}),
-     NSD = StateData#state{connections =
-                             (?DICT):erase({From, To},
-                                           StateData#state.connections)},
-     {next_state, stream_established, NSD};
- stream_established({xmlstreamend, _Name}, StateData) ->
-     {stop, normal, StateData};
- stream_established({xmlstreamerror, _}, StateData) ->
-     send_element(StateData, xmpp:serr_not_well_formed()),
-     {stop, normal, StateData};
- stream_established(timeout, StateData) ->
-     send_element(StateData, xmpp:serr_connection_timeout()),
-     {stop, normal, StateData};
- stream_established(closed, StateData) ->
-     {stop, normal, StateData};
- stream_established(Pkt, StateData) ->
-     ejabberd_hooks:run(s2s_loop_debug, [{xmlstreamelement, Pkt}]),
-     {next_state, stream_established, StateData}.
- %%----------------------------------------------------------------------
- %% Func: StateName/3
- %% Returns: {next_state, NextStateName, NextStateData}            |
- %%          {next_state, NextStateName, NextStateData, Timeout}   |
- %%          {reply, Reply, NextStateName, NextStateData}          |
- %%          {reply, Reply, NextStateName, NextStateData, Timeout} |
- %%          {stop, Reason, NewStateData}                          |
- %%          {stop, Reason, Reply, NewStateData}
- %%----------------------------------------------------------------------
- %state_name(Event, From, StateData) ->
- %    Reply = ok,
- %    {reply, Reply, state_name, StateData}.
- handle_event(_Event, StateName, StateData) ->
-     {next_state, StateName, StateData}.
- handle_sync_event(get_state_infos, _From, StateName,
-                 StateData) ->
-     SockMod = StateData#state.sockmod,
-     {Addr, Port} = try
-                    SockMod:peername(StateData#state.socket)
-                  of
-                    {ok, {A, P}} -> {A, P};
-                    {error, _} -> {unknown, unknown}
-                  catch
-                    _:_ -> {unknown, unknown}
+           State2;
+       {error, Err} ->
+           send(State, Err)
+     end.
+ handle_cdata(Data, #{server_host := LServer} = State) ->
+     ejabberd_hooks:run_fold(s2s_in_handle_cdata, LServer, State, [Data]).
+ handle_recv(El, Pkt, #{server_host := LServer} = State) ->
+     State1 = set_idle_timeout(State),
+     ejabberd_hooks:run_fold(s2s_in_handle_recv, LServer, State1, [El, Pkt]).
+ handle_send(Pkt, Result, #{server_host := LServer} = State) ->
+     ejabberd_hooks:run_fold(s2s_in_handle_send, LServer,
+                           State, [Pkt, Result]).
+ init([State, Opts]) ->
+     Shaper = gen_mod:get_opt(shaper, Opts, fun acl:shaper_rules_validator/1, none),
+     TLSOpts1 = lists:filter(
+                fun({certfile, _}) -> true;
+                   ({ciphers, _}) -> true;
+                   ({dhfile, _}) -> true;
+                   ({cafile, _}) -> true;
+                   (_) -> false
+                end, Opts),
+     TLSOpts2 = case lists:keyfind(protocol_options, 1, Opts) of
+                  false -> TLSOpts1;
+                  {_, OptString} ->
+                      ProtoOpts = str:join(OptString, <<$|>>),
+                      [{protocol_options, ProtoOpts}|TLSOpts1]
 -             end,
 +                 end,
-     Domains = get_external_hosts(StateData),
-     Infos = [{direction, in}, {statename, StateName},
-            {addr, Addr}, {port, Port},
-            {streamid, StateData#state.streamid},
-            {tls, StateData#state.tls},
-            {tls_enabled, StateData#state.tls_enabled},
-            {tls_options, StateData#state.tls_options},
-            {authenticated, StateData#state.authenticated},
-            {shaper, StateData#state.shaper}, {sockmod, SockMod},
-            {domains, Domains}],
-     Reply = {state_infos, Infos},
-     {reply, Reply, StateName, StateData};
- %%----------------------------------------------------------------------
- %% Func: handle_sync_event/4
- %% Returns: {next_state, NextStateName, NextStateData}            |
- %%          {next_state, NextStateName, NextStateData, Timeout}   |
- %%          {reply, Reply, NextStateName, NextStateData}          |
- %%          {reply, Reply, NextStateName, NextStateData, Timeout} |
- %%          {stop, Reason, NewStateData}                          |
- %%          {stop, Reason, Reply, NewStateData}
- %%----------------------------------------------------------------------
- handle_sync_event(_Event, _From, StateName,
-                 StateData) ->
-     Reply = ok, {reply, Reply, StateName, StateData}.
- code_change(_OldVsn, StateName, StateData, _Extra) ->
-     {ok, StateName, StateData}.
- handle_info({send_text, Text}, StateName, StateData) ->
-     send_text(StateData, Text),
-     {next_state, StateName, StateData};
- handle_info({timeout, Timer, _}, StateName,
-           #state{timer = Timer} = StateData) ->
-     if StateName == wait_for_stream ->
-           send_header(StateData, undefined);
-        true ->
-           ok
+     TLSOpts3 = case proplists:get_bool(tls_compression, Opts) of
+                    false -> [compression_none | TLSOpts2];
+                    true -> TLSOpts2
 -               end,
 +    end,
-     send_element(StateData, xmpp:serr_connection_timeout()),
-     {stop, normal, StateData};
- handle_info(_, StateName, StateData) ->
-     {next_state, StateName, StateData}.
+     State1 = State#{tls_options => TLSOpts3,
+                   auth_domains => sets:new(),
+                   xmlns => ?NS_SERVER,
+                   lang => ?MYLANG,
+                   server => ?MYNAME,
+                   lserver => ?MYNAME,
+                   server_host => ?MYNAME,
+                   established => false,
+                   shaper => Shaper},
+     ejabberd_hooks:run_fold(s2s_in_init, {ok, State1}, [Opts]).
+ handle_call(Request, From, #{server_host := LServer} = State) ->
+     ejabberd_hooks:run_fold(s2s_in_handle_call, LServer, State, [Request, From]).
+ handle_cast({update_state, Fun}, State) ->
+     case Fun of
+       {M, F, A} -> erlang:apply(M, F, [State|A]);
+       _ when is_function(Fun) -> Fun(State)
+     end;
+ handle_cast(Msg, #{server_host := LServer} = State) ->
+     ejabberd_hooks:run_fold(s2s_in_handle_cast, LServer, State, [Msg]).
  
- terminate(Reason, _StateName, StateData) ->
-     ?DEBUG("terminated: ~p", [Reason]),
+ handle_info(Info, #{server_host := LServer} = State) ->
+     ejabberd_hooks:run_fold(s2s_in_handle_info, LServer, State, [Info]).
+ terminate(Reason, #{auth_domains := AuthDomains}) ->
      case Reason of
 -      {process_limit, _} ->
 +      {process_limit, _} ->
-         [ejabberd_s2s:external_host_overloaded(Host)
-          || Host <- get_external_hosts(StateData)];
-       _ -> ok
-     end,
-     catch send_trailer(StateData),
-     (StateData#state.sockmod):close(StateData#state.socket),
-     ok.
- get_external_hosts(StateData) ->
-     case StateData#state.authenticated of
-       true -> [StateData#state.auth_domain];
-       false ->
-         Connections = StateData#state.connections,
-         [D
-          || {{D, _}, established} <- dict:to_list(Connections)]
+           sets:fold(
+             fun(Host, _) ->
+                     ejabberd_s2s:external_host_overloaded(Host)
+             end, ok, AuthDomains);
+       _ ->
+           ok
      end.
  
- print_state(State) -> State.
+ code_change(_OldVsn, State, _Extra) ->
+     {ok, State}.
  
- %%%----------------------------------------------------------------------
+ %%%===================================================================
  %%% Internal functions
- %%%----------------------------------------------------------------------
- -spec send_text(state(), iodata()) -> ok.
- send_text(StateData, Text) ->
-     (StateData#state.sockmod):send(StateData#state.socket,
-                                  Text).
- -spec send_element(state(), xmpp_element()) -> ok.
- send_element(StateData, El) ->
-     El1 = xmpp:encode(El, ?NS_SERVER),
-     send_text(StateData, fxml:element_to_binary(El1)).
- -spec send_error(state(), xmlel() | stanza(), stanza_error()) -> ok.
- send_error(StateData, Stanza, Error) ->
-     Type = xmpp:get_type(Stanza),
-     if Type == error; Type == result;
-        Type == <<"error">>; Type == <<"result">> ->
-           ok;
+ %%%===================================================================
+ -spec check_from_to(jid(), jid(), state()) -> ok | {error, stream_error()}.
+ check_from_to(From, To, State) ->
+     case check_from(From, State) of
 -      true ->
 +       true ->
-           send_element(StateData, xmpp:make_error(Stanza, Error))
-     end.
- -spec send_trailer(state()) -> ok.
- send_trailer(StateData) ->
-     send_text(StateData, <<"</stream:stream>">>).
- -spec send_header(state(), undefined | {integer(), integer()}) -> ok.
- send_header(StateData, Version) ->
-     Header = xmpp:encode(
-              #stream_start{xmlns = ?NS_SERVER,
-                            stream_xmlns = ?NS_STREAM,
-                            db_xmlns = ?NS_SERVER_DIALBACK,
-                            id = StateData#state.streamid,
-                            version = Version}),
-     send_text(StateData, fxml:element_to_header(Header)).
- -spec change_shaper(state(), binary(), jid()) -> ok.
- change_shaper(StateData, Host, JID) ->
-     Shaper = acl:match_rule(Host, StateData#state.shaper,
-                           JID),
-     (StateData#state.sockmod):change_shaper(StateData#state.socket,
-                                           Shaper).
- -spec new_id() -> binary().
- new_id() -> randoms:get_string().
- -spec cancel_timer(reference()) -> ok.
- cancel_timer(Timer) ->
-     erlang:cancel_timer(Timer),
-     receive {timeout, Timer, _} -> ok after 0 -> ok end.
- fsm_limit_opts(Opts) ->
-     case lists:keysearch(max_fsm_queue, 1, Opts) of
-       {value, {_, N}} when is_integer(N) -> [{max_queue, N}];
-       _ ->
-         case ejabberd_config:get_option(
-                  max_fsm_queue,
-                  fun(I) when is_integer(I), I > 0 -> I end) of
-             undefined -> [];
-           N -> [{max_queue, N}]
-         end
-     end.
- -spec decode_element(xmlel() | xmpp_element(), state_name(), state()) -> fsm_transition().
- decode_element(#xmlel{} = El, StateName, StateData) ->
-     Opts = if StateName == stream_established ->
-                  [ignore_els];
+           case check_to(To, State) of
 -              true ->
 +            true ->
-                  []
-          end,
-     try xmpp:decode(El, ?NS_SERVER, Opts) of
-       Pkt -> ?MODULE:StateName(Pkt, StateData)
-     catch error:{xmpp_codec, Why} ->
-             case xmpp:is_stanza(El) of
-                 true ->
-                   Lang = xmpp:get_lang(El),
-                     Txt = xmpp:format_error(Why),
-                   send_error(StateData, El, xmpp:err_bad_request(Txt, Lang));
+                   ok;
 -              false ->
 +                false ->
-                     ok
-             end,
-             {next_state, StateName, StateData}
-     end;
- decode_element(Pkt, StateName, StateData) ->
-     ?MODULE:StateName(Pkt, StateData).
- opt_type(domain_certfile) -> fun iolist_to_binary/1;
- opt_type(max_fsm_queue) ->
-     fun (I) when is_integer(I), I > 0 -> I end;
- opt_type(s2s_certfile) -> fun iolist_to_binary/1;
- opt_type(s2s_cafile) -> fun iolist_to_binary/1;
- opt_type(s2s_ciphers) -> fun iolist_to_binary/1;
- opt_type(s2s_dhfile) -> fun iolist_to_binary/1;
- opt_type(s2s_protocol_options) ->
-     fun (Options) ->
-           [_ | O] = lists:foldl(fun (X, Acc) -> X ++ Acc end, [],
-                                 [["|" | binary_to_list(Opt)]
-                                  || Opt <- Options, is_binary(Opt)]),
-           iolist_to_binary(O)
-     end;
- opt_type(s2s_tls_compression) ->
-     fun (true) -> true;
-       (false) -> false
-     end;
- opt_type(s2s_use_starttls) ->
-     fun (false) -> false;
-       (true) -> true;
-       (optional) -> optional;
-       (required) -> required;
-       (required_trusted) -> required_trusted
+                   {error, xmpp:serr_host_unknown()}
 -          end;
 +    end;
+       false ->
+           {error, xmpp:serr_invalid_from()}
+     end.
+ -spec check_from(jid(), state()) -> boolean().
+ check_from(#jid{lserver = S1}, #{auth_domains := AuthDomains}) ->
+     sets:is_element(S1, AuthDomains).
+ -spec check_to(jid(), state()) -> boolean().
+ check_to(#jid{lserver = LServer}, _State) ->
+     ejabberd_router:is_my_route(LServer).
+ -spec set_idle_timeout(state()) -> state().
+ set_idle_timeout(#{server_host := LServer,
+                  established := true} = State) ->
+     Timeout = ejabberd_s2s:get_idle_timeout(LServer),
+     xmpp_stream_in:set_timeout(State, Timeout);
+ set_idle_timeout(State) ->
+     State.
+ -spec change_shaper(state(), binary()) -> ok.
+ change_shaper(#{shaper := ShaperName, server_host := ServerHost} = State,
+             RServer) ->
+     Shaper = acl:match_rule(ServerHost, ShaperName, jid:make(RServer)),
+     xmpp_stream_in:change_shaper(State, Shaper).
  opt_type(_) ->
-     [domain_certfile, max_fsm_queue, s2s_certfile, s2s_cafile,
-      s2s_ciphers, s2s_dhfile, s2s_protocol_options,
-      s2s_tls_compression, s2s_use_starttls].
+     [].
index fb3dfcbd583117db9733419b1312edfba4b46877,70ab0cfe4f2ccd56c75f79ce270576d348446c33..a923860f39d93bfb0fe9a7615bb06b5330831ac5
@@@ -1,11 -1,8 +1,8 @@@
- %%%----------------------------------------------------------------------
- %%% File    : ejabberd_s2s_out.erl
- %%% Author  : Alexey Shchepin <alexey@process-one.net>
- %%% Purpose : Manage outgoing server-to-server connections
- %%% Created :  6 Dec 2002 by Alexey Shchepin <alexey@process-one.net>
+ %%%-------------------------------------------------------------------
+ %%% Created : 16 Dec 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
  %%%
  %%%
 -%%% ejabberd, Copyright (C) 2002-2016   ProcessOne
 +%%% ejabberd, Copyright (C) 2002-2017   ProcessOne
  %%%
  %%% This program is free software; you can redistribute it and/or
  %%% modify it under the terms of the GNU General Public License as
  %%% with this program; if not, write to the Free Software Foundation, Inc.,
  %%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  %%%
- %%%----------------------------------------------------------------------
+ %%%-------------------------------------------------------------------
  -module(ejabberd_s2s_out).
+ -behaviour(xmpp_stream_out).
  -behaviour(ejabberd_config).
  
- -author('alexey@process-one.net').
- -behaviour(p1_fsm).
- %% External exports
- -export([start/3,
-        start_link/3,
-        start_connection/1,
-        terminate_if_waiting_delay/2,
-        stop_connection/1,
-        transform_options/1]).
- -export([init/1, open_socket/2, wait_for_stream/2,
-        wait_for_validation/2, wait_for_features/2,
-        wait_for_auth_result/2, wait_for_starttls_proceed/2,
-        relay_to_bridge/2, reopen_socket/2, wait_before_retry/2,
-        stream_established/2, handle_event/3,
-        handle_sync_event/4, handle_info/3, terminate/3,
-        print_state/1, code_change/4, test_get_addr_port/1,
-        get_addr_port/1, opt_type/1]).
+ %% ejabberd_config callbacks
+ -export([opt_type/1, transform_options/1]).
+ %% xmpp_stream_out callbacks
+ -export([tls_options/1, tls_required/1, tls_verify/1, tls_enabled/1,
+        connect_timeout/1, address_families/1, default_port/1,
+        dns_retries/1, dns_timeout/1,
+        handle_auth_success/2, handle_auth_failure/3, handle_packet/2,
+        handle_stream_end/2, handle_stream_downgraded/2,
+        handle_recv/3, handle_send/3, handle_cdata/2,
+        handle_stream_established/1, handle_timeout/1]).
+ -export([init/1, handle_call/3, handle_cast/2, handle_info/2,
+        terminate/2, code_change/3]).
+ %% Hooks
+ -export([process_auth_result/2, process_closed/2, handle_unexpected_info/2,
+        handle_unexpected_cast/2, process_downgraded/2]).
+ %% API
+ -export([start/3, start_link/3, connect/1, close/1, stop/1, send/2,
+        route/2, establish/1, update_state/2, add_hooks/0]).
  
  -include("ejabberd.hrl").
- -include("logger.hrl").
  -include("xmpp.hrl").
+ -include("logger.hrl").
  
- -record(state,
-       {socket                           :: ejabberd_socket:socket_state(),
-          streamid = <<"">>                :: binary(),
-        remote_streamid = <<"">>         :: binary(),
-          use_v10 = true                   :: boolean(),
-          tls = false                      :: boolean(),
-        tls_required = false             :: boolean(),
-        tls_certverify = false           :: boolean(),
-          tls_enabled = false              :: boolean(),
-        tls_options = [connect]          :: list(),
-          authenticated = false            :: boolean(),
-        db_enabled = true                :: boolean(),
-          try_auth = true                  :: boolean(),
-          myname = <<"">>                  :: binary(),
-          server = <<"">>                  :: binary(),
-        queue = queue:new()              :: ?TQUEUE,
-          delay_to_retry = undefined_delay :: undefined_delay | non_neg_integer(),
-          new = false                      :: boolean(),
-        verify = false                   :: false | {pid(), binary(), binary()},
-          bridge                           :: {atom(), atom()},
-          timer = make_ref()               :: reference()}).
- -type state_name() :: open_socket | wait_for_stream |
-                     wait_for_validation | wait_for_features |
-                     wait_for_auth_result | wait_for_starttls_proceed |
-                     relay_to_bridge | reopen_socket | wait_before_retry |
-                     stream_established.
- -type state() :: #state{}.
- -type fsm_stop() :: {stop, normal, state()}.
- -type fsm_next() :: {next_state, state_name(), state(), non_neg_integer()} |
-                   {next_state, state_name(), state()}.
- -type fsm_transition() :: fsm_stop() | fsm_next().
- %%-define(DBGFSM, true).
- -ifdef(DBGFSM).
- -define(FSMOPTS, [{debug, [trace]}]).
- -else.
- -define(FSMOPTS, []).
- -endif.
- -define(FSMTIMEOUT, 30000).
- %% We do not block on send anymore.
- -define(TCP_SEND_TIMEOUT, 15000).
- %% Maximum delay to wait before retrying to connect after a failed attempt.
- %% Specified in miliseconds. Default value is 5 minutes.
- -define(MAX_RETRY_DELAY, 300000).
- -define(SOCKET_DEFAULT_RESULT, {error, badarg}).
+ -type state() :: map().
+ -export_type([state/0]).
  
- %%%----------------------------------------------------------------------
+ %%%===================================================================
  %%% API
- %%%----------------------------------------------------------------------
- start(From, Host, Type) ->
-     supervisor:start_child(ejabberd_s2s_out_sup,
-                          [From, Host, Type]).
- start_link(From, Host, Type) ->
-     p1_fsm:start_link(ejabberd_s2s_out, [From, Host, Type],
-                     fsm_limit_opts() ++ (?FSMOPTS)).
- start_connection(Pid) -> p1_fsm:send_event(Pid, init).
- stop_connection(Pid) -> p1_fsm:send_event(Pid, closed).
- %%%----------------------------------------------------------------------
- %%% Callback functions from p1_fsm
- %%%----------------------------------------------------------------------
- init([From, Server, Type]) ->
-     process_flag(trap_exit, true),
-     ?DEBUG("started: ~p", [{From, Server, Type}]),
-     {TLS, TLSRequired, TLSCertverify} =
-       case ejabberd_config:get_option(
-              s2s_use_starttls,
-              fun(true) -> true;
-                 (false) -> false;
-                 (optional) -> optional;
-                 (required) -> required;
-                 (required_trusted) -> required_trusted
-              end)
-           of
-         UseTls
-             when (UseTls == undefined) or (UseTls == false) ->
-             {false, false, false};
-         UseTls
-             when (UseTls == true) or (UseTls == optional) ->
-             {true, false, false};
-         required ->
-             {true, true, false};
-         required_trusted ->
-             {true, true, true}
-       end,
-     UseV10 = TLS,
-     TLSOpts1 = case
-               ejabberd_config:get_option(
-                   s2s_certfile, fun iolist_to_binary/1)
-                 of
-               undefined -> [connect];
-               CertFile -> [{certfile, CertFile}, connect]
-             end,
-     TLSOpts2 = case ejabberd_config:get_option(
-                       s2s_ciphers, fun iolist_to_binary/1) of
-                    undefined -> TLSOpts1;
-                    Ciphers -> [{ciphers, Ciphers} | TLSOpts1]
-                end,
-     TLSOpts3 = case ejabberd_config:get_option(
-                       s2s_protocol_options,
-                       fun (Options) ->
-                               [_|O] = lists:foldl(
-                                            fun(X, Acc) -> X ++ Acc end, [],
-                                            [["|" | binary_to_list(Opt)] || Opt <- Options, is_binary(Opt)]
-                                           ),
-                               iolist_to_binary(O)
-                       end) of
-                    undefined -> TLSOpts2;
-                    ProtocolOpts -> [{protocol_options, ProtocolOpts} | TLSOpts2]
-                end,
-     TLSOpts4 = case ejabberd_config:get_option(
-                       s2s_dhfile, fun iolist_to_binary/1) of
-                    undefined -> TLSOpts3;
-                    DHFile -> [{dhfile, DHFile} | TLSOpts3]
-                end,
-     TLSOpts = case ejabberd_config:get_option(
-                      {s2s_tls_compression, From},
-                      fun(true) -> true;
-                         (false) -> false
-                      end, false) of
-                   false -> [compression_none | TLSOpts4];
-                   true -> TLSOpts4
-               end,
-     {New, Verify} = case Type of
-                     new -> {true, false};
-                     {verify, Pid, Key, SID} ->
-                         start_connection(self()), {false, {Pid, Key, SID}}
-                   end,
-     Timer = erlang:start_timer(?S2STIMEOUT, self(), []),
-     {ok, open_socket,
-      #state{use_v10 = UseV10, tls = TLS,
-           tls_required = TLSRequired, tls_certverify = TLSCertverify,
-           tls_options = TLSOpts, queue = queue:new(), myname = From,
-           server = Server, new = New, verify = Verify, timer = Timer}}.
- open_socket(init, StateData) ->
-     log_s2s_out(StateData#state.new, StateData#state.myname,
-               StateData#state.server, StateData#state.tls),
-     ?DEBUG("open_socket: ~p",
-          [{StateData#state.myname, StateData#state.server,
-            StateData#state.new, StateData#state.verify}]),
-     AddrList = case
-                ejabberd_idna:domain_utf8_to_ascii(StateData#state.server)
-                  of
-                false -> [];
-                ASCIIAddr -> get_addr_port(ASCIIAddr)
-              end,
-     case lists:foldl(fun ({Addr, Port}, Acc) ->
-                            case Acc of
-                              {ok, Socket} -> {ok, Socket};
-                              _ -> open_socket1(Addr, Port)
-                            end
-                    end,
-                    ?SOCKET_DEFAULT_RESULT, AddrList)
-       of
-       {ok, Socket} ->
-         Version = if StateData#state.use_v10 -> {1,0};
-                      true -> undefined
-                   end,
-         NewStateData = StateData#state{socket = Socket,
-                                        tls_enabled = false,
-                                        streamid = new_id()},
-         send_header(NewStateData, Version),
-         {next_state, wait_for_stream, NewStateData,
-          ?FSMTIMEOUT};
-       {error, Reason} ->
-         ?INFO_MSG("s2s connection: ~s -> ~s (remote server "
-                   "not found: ~p)",
-                   [StateData#state.myname, StateData#state.server, Reason]),
-         case ejabberd_hooks:run_fold(find_s2s_bridge, undefined,
-                                      [StateData#state.myname,
-                                       StateData#state.server])
-             of
-           {Mod, Fun, Type} ->
-               ?INFO_MSG("found a bridge to ~s for: ~s -> ~s",
-                         [Type, StateData#state.myname,
-                          StateData#state.server]),
-               NewStateData = StateData#state{bridge = {Mod, Fun}},
-               {next_state, relay_to_bridge, NewStateData};
-           _ -> wait_before_reconnect(StateData)
-         end
-     end;
- open_socket(Event, StateData) ->
-     handle_unexpected_event(Event, open_socket, StateData).
- open_socket1({_, _, _, _} = Addr, Port) ->
-     open_socket2(inet, Addr, Port);
- %% IPv6
- open_socket1({_, _, _, _, _, _, _, _} = Addr, Port) ->
-     open_socket2(inet6, Addr, Port);
- %% Hostname
- open_socket1(Host, Port) ->
-     lists:foldl(fun (_Family, {ok, _Socket} = R) -> R;
-                   (Family, _) ->
-                       Addrs = get_addrs(Host, Family),
-                       lists:foldl(fun (_Addr, {ok, _Socket} = R) -> R;
-                                       (Addr, _) -> open_socket1(Addr, Port)
-                                   end,
-                                   ?SOCKET_DEFAULT_RESULT, Addrs)
-               end,
-               ?SOCKET_DEFAULT_RESULT, outgoing_s2s_families()).
- open_socket2(Type, Addr, Port) ->
-     ?DEBUG("s2s_out: connecting to ~p:~p~n", [Addr, Port]),
-     Timeout = outgoing_s2s_timeout(),
-     case catch ejabberd_socket:connect(Addr, Port,
-                                      [binary, {packet, 0},
-                                       {send_timeout, ?TCP_SEND_TIMEOUT},
-                                       {send_timeout_close, true},
-                                       {active, false}, Type],
-                                      Timeout)
-       of
-       {ok, _Socket} = R -> R;
-       {error, Reason} = R ->
-         ?DEBUG("s2s_out: connect return ~p~n", [Reason]), R;
-       {'EXIT', Reason} ->
-         ?DEBUG("s2s_out: connect crashed ~p~n", [Reason]),
-         {error, Reason}
-     end.
- %%----------------------------------------------------------------------
- wait_for_stream({xmlstreamstart, Name, Attrs}, StateData0) ->
-     {CertCheckRes, CertCheckMsg, StateData} =
-       if StateData0#state.tls_certverify, StateData0#state.tls_enabled ->
-               {Res, Msg} =
-                   ejabberd_s2s:check_peer_certificate(ejabberd_socket,
-                                                       StateData0#state.socket,
-                                                       StateData0#state.server),
-               ?DEBUG("Certificate verification result for ~s: ~s",
-                      [StateData0#state.server, Msg]),
-               {Res, Msg, StateData0#state{tls_certverify = false}};
+ %%%===================================================================
+ start(From, To, Opts) ->
+     case proplists:get_value(supervisor, Opts, true) of
 -      true ->
 +         true ->
-               {no_verify, <<"Not verified">>, StateData0}
-       end,
-     try xmpp:decode(#xmlel{name = Name, attrs = Attrs}) of
-       _ when CertCheckRes == error ->
-           send_element(StateData,
-                        xmpp:serr_policy_violation(CertCheckMsg, ?MYLANG)),
-           ?INFO_MSG("Closing s2s connection: ~s -> ~s (~s)",
-                     [StateData#state.myname, StateData#state.server,
-                      CertCheckMsg]),
-           {stop, normal, StateData};
-       #stream_start{xmlns = NS_SERVER, stream_xmlns = NS_STREAM}
-         when NS_SERVER /= ?NS_SERVER; NS_STREAM /= ?NS_STREAM ->
-           send_element(StateData, xmpp:serr_invalid_namespace()),
-           {stop, normal, StateData};
-       #stream_start{db_xmlns = ?NS_SERVER_DIALBACK, id = ID,
-                     version = V} when V /= {1,0} ->
-           send_db_request(StateData#state{remote_streamid = ID});
-       #stream_start{db_xmlns = ?NS_SERVER_DIALBACK, id = ID}
-         when StateData#state.use_v10 ->
-           {next_state, wait_for_features,
-            StateData#state{remote_streamid = ID}, ?FSMTIMEOUT};
-       #stream_start{db_xmlns = ?NS_SERVER_DIALBACK, id = ID}
-         when not StateData#state.use_v10 ->
-           %% Handle Tigase's workaround for an old ejabberd bug:
-           send_db_request(StateData#state{remote_streamid = ID});
-       #stream_start{id = ID} when StateData#state.use_v10 ->
-           {next_state, wait_for_features,
-            StateData#state{db_enabled = false, remote_streamid = ID},
-            ?FSMTIMEOUT};
-       #stream_start{} ->
-           send_element(StateData, xmpp:serr_invalid_namespace()),
-           {stop, normal, StateData};
-       _ ->
-           send_element(StateData, xmpp:serr_invalid_xml()),
-             {stop, normal, StateData}
-     catch _:{xmpp_codec, Why} ->
-           Txt = xmpp:format_error(Why),
-           send_element(StateData, xmpp:serr_invalid_xml(Txt, ?MYLANG)),
-           {stop, normal, StateData}
-     end;
- wait_for_stream(Event, StateData) ->
-     handle_unexpected_event(Event, wait_for_stream, StateData).
- wait_for_validation({xmlstreamelement, El}, StateData) ->
-     decode_element(El, wait_for_validation, StateData);
- wait_for_validation(#db_result{to = To, from = From, type = Type}, StateData) ->
-     ?DEBUG("recv result: ~p", [{From, To, Type}]),
-     case {Type, StateData#state.tls_enabled, StateData#state.tls_required} of
-       {valid, Enabled, Required} when (Enabled == true) or (Required == false) ->
-           send_queue(StateData, StateData#state.queue),
-           ?INFO_MSG("Connection established: ~s -> ~s with "
-                     "TLS=~p",
-                     [StateData#state.myname, StateData#state.server,
-                      StateData#state.tls_enabled]),
-           ejabberd_hooks:run(s2s_connect_hook,
-                              [StateData#state.myname,
-                               StateData#state.server]),
-           {next_state, stream_established, StateData#state{queue = queue:new()}};
-       {valid, Enabled, Required} when (Enabled == false) and (Required == true) ->
-           ?INFO_MSG("Closing s2s connection: ~s -> ~s (TLS "
-                     "is required but unavailable)",
-                     [StateData#state.myname, StateData#state.server]),
-           {stop, normal, StateData};
-       _ ->
-           ?INFO_MSG("Closing s2s connection: ~s -> ~s (invalid "
-                     "dialback key result)",
-                     [StateData#state.myname, StateData#state.server]),
-           {stop, normal, StateData}
-     end;
- wait_for_validation(#db_verify{to = To, from = From, id = Id, type = Type},
-                   StateData) ->
-     ?DEBUG("recv verify: ~p", [{From, To, Id, Type}]),
-     case StateData#state.verify of
-       false ->
-           NextState = wait_for_validation,
-           {next_state, NextState, StateData, get_timeout_interval(NextState)};
-       {Pid, _Key, _SID} ->
-           case Type of
-               valid ->
-                   p1_fsm:send_event(Pid,
-                                     {valid, StateData#state.server,
-                                      StateData#state.myname});
+           supervisor:start_child(ejabberd_s2s_out_sup,
+                                  [From, To, Opts]);
 -      _ ->
 +              _ ->
-                   p1_fsm:send_event(Pid,
-                                     {invalid, StateData#state.server,
-                                      StateData#state.myname})
-           end,
-           if StateData#state.verify == false ->
-                   {stop, normal, StateData};
-              true ->
-                   NextState = wait_for_validation,
-                   {next_state, NextState, StateData, get_timeout_interval(NextState)}
-           end
-     end;
- wait_for_validation(timeout,
-                   #state{verify = {VPid, VKey, SID}} = StateData)
-   when is_pid(VPid) and is_binary(VKey) and is_binary(SID) ->
-     ?DEBUG("wait_for_validation: ~s -> ~s (timeout in verify connection)",
-          [StateData#state.myname, StateData#state.server]),
-     {stop, normal, StateData};
- wait_for_validation(Event, StateData) ->
-     handle_unexpected_event(Event, wait_for_validation, StateData).
- wait_for_features({xmlstreamelement, El}, StateData) ->
-     decode_element(El, wait_for_features, StateData);
- wait_for_features(#stream_features{sub_els = Els}, StateData) ->
-     {SASLEXT, StartTLS, StartTLSRequired} =
-       lists:foldl(
-         fun(#sasl_mechanisms{list = Mechs}, {_, STLS, STLSReq}) ->
-                 {lists:member(<<"EXTERNAL">>, Mechs), STLS, STLSReq};
-            (#starttls{required = Required}, {SEXT, _, _}) ->
-                 {SEXT, true, Required};
-            (_, Acc) ->
-                 Acc
-         end, {false, false, false}, Els),
-     if not SASLEXT and not StartTLS and StateData#state.authenticated ->
-           send_queue(StateData, StateData#state.queue),
-           ?INFO_MSG("Connection established: ~s -> ~s with "
-                     "SASL EXTERNAL and TLS=~p",
-                     [StateData#state.myname, StateData#state.server,
-                      StateData#state.tls_enabled]),
-           ejabberd_hooks:run(s2s_connect_hook,
-                              [StateData#state.myname,
-                               StateData#state.server]),
-           {next_state, stream_established,
-            StateData#state{queue = queue:new()}};
-        SASLEXT and StateData#state.try_auth and
-        (StateData#state.new /= false) and
-        (StateData#state.tls_enabled or
-       not StateData#state.tls_required) ->
-           send_element(StateData,
-                        #sasl_auth{mechanism = <<"EXTERNAL">>,
-                                   text = StateData#state.myname}),
-           {next_state, wait_for_auth_result,
-            StateData#state{try_auth = false}, ?FSMTIMEOUT};
-        StartTLS and StateData#state.tls and
-        not StateData#state.tls_enabled ->
-           send_element(StateData, #starttls{}),
-           {next_state, wait_for_starttls_proceed, StateData, ?FSMTIMEOUT};
-        StartTLSRequired and not StateData#state.tls ->
-           ?DEBUG("restarted: ~p",
-                  [{StateData#state.myname, StateData#state.server}]),
-           ejabberd_socket:close(StateData#state.socket),
-           {next_state, reopen_socket,
-            StateData#state{socket = undefined, use_v10 = false},
-            ?FSMTIMEOUT};
-        StateData#state.db_enabled ->
-           send_db_request(StateData);
-        true ->
-           ?DEBUG("restarted: ~p",
-                  [{StateData#state.myname, StateData#state.server}]),
-           ejabberd_socket:close(StateData#state.socket),
-           {next_state, reopen_socket,
-            StateData#state{socket = undefined, use_v10 = false}, ?FSMTIMEOUT}
-     end;
- wait_for_features(Event, StateData) ->
-     handle_unexpected_event(Event, wait_for_features, StateData).
- wait_for_auth_result({xmlstreamelement, El}, StateData) ->
-     decode_element(El, wait_for_auth_result, StateData);
- wait_for_auth_result(#sasl_success{}, StateData) ->
-     ?DEBUG("auth: ~p", [{StateData#state.myname, StateData#state.server}]),
-     ejabberd_socket:reset_stream(StateData#state.socket),
-     send_header(StateData, {1,0}),
-     {next_state, wait_for_stream,
-      StateData#state{streamid = new_id(), authenticated = true},
-      ?FSMTIMEOUT};
- wait_for_auth_result(#sasl_failure{}, StateData) ->
-     ?DEBUG("restarted: ~p", [{StateData#state.myname, StateData#state.server}]),
-     ejabberd_socket:close(StateData#state.socket),
-     {next_state, reopen_socket,
-      StateData#state{socket = undefined}, ?FSMTIMEOUT};
- wait_for_auth_result(Event, StateData) ->
-     handle_unexpected_event(Event, wait_for_auth_result, StateData).
- wait_for_starttls_proceed({xmlstreamelement, El}, StateData) ->
-     decode_element(El, wait_for_starttls_proceed, StateData);
- wait_for_starttls_proceed(#starttls_proceed{}, StateData) ->
-     ?DEBUG("starttls: ~p", [{StateData#state.myname, StateData#state.server}]),
-     Socket = StateData#state.socket,
-     TLSOpts = case ejabberd_config:get_option(
-                    {domain_certfile, StateData#state.myname},
-                    fun iolist_to_binary/1) of
-                 undefined -> StateData#state.tls_options;
-                 CertFile ->
-                     [{certfile, CertFile}
-                      | lists:keydelete(certfile, 1,
-                                        StateData#state.tls_options)]
-             end,
-     TLSSocket = ejabberd_socket:starttls(Socket, TLSOpts),
-     NewStateData = StateData#state{socket = TLSSocket,
-                                  streamid = new_id(),
-                                  tls_enabled = true,
-                                  tls_options = TLSOpts},
-     send_header(NewStateData, {1,0}),
-     {next_state, wait_for_stream, NewStateData, ?FSMTIMEOUT};
- wait_for_starttls_proceed(Event, StateData) ->
-     handle_unexpected_event(Event, wait_for_starttls_proceed, StateData).
+           xmpp_stream_out:start(?MODULE, [ejabberd_socket, From, To, Opts],
+                                 ejabberd_config:fsm_limit_opts([]))
+     end.
  
- reopen_socket({xmlstreamelement, _El}, StateData) ->
-     {next_state, reopen_socket, StateData, ?FSMTIMEOUT};
- reopen_socket({xmlstreamend, _Name}, StateData) ->
-     {next_state, reopen_socket, StateData, ?FSMTIMEOUT};
- reopen_socket({xmlstreamerror, _}, StateData) ->
-     {next_state, reopen_socket, StateData, ?FSMTIMEOUT};
- reopen_socket(timeout, StateData) ->
-     ?INFO_MSG("reopen socket: timeout", []),
-     {stop, normal, StateData};
- reopen_socket(closed, StateData) ->
-     p1_fsm:send_event(self(), init),
-     {next_state, open_socket, StateData, ?FSMTIMEOUT}.
+ start_link(From, To, Opts) ->
+     xmpp_stream_out:start_link(?MODULE, [ejabberd_socket, From, To, Opts],
+                              ejabberd_config:fsm_limit_opts([])).
+ connect(Ref) ->
+     xmpp_stream_out:connect(Ref).
+ close(Ref) ->
+     xmpp_stream_out:close(Ref).
+ stop(Ref) ->
+     xmpp_stream_out:stop(Ref).
+ -spec send(pid(), xmpp_element()) -> ok;
+         (state(), xmpp_element()) -> state().
+ send(Stream, Pkt) ->
+     xmpp_stream_out:send(Stream, Pkt).
+ -spec route(pid(), xmpp_element()) -> ok.
+ route(Ref, Pkt) ->
+     Ref ! {route, Pkt}.
+ -spec establish(state()) -> state().
+ establish(State) ->
+     xmpp_stream_out:establish(State).
+ -spec update_state(pid(), fun((state()) -> state()) |
+                  {module(), atom(), list()}) -> ok.
+ update_state(Ref, Callback) ->
+     xmpp_stream_out:cast(Ref, {update_state, Callback}).
+ -spec add_hooks() -> ok.
+ add_hooks() ->
+     lists:foreach(
+       fun(Host) ->
+             ejabberd_hooks:add(s2s_out_auth_result, Host, ?MODULE,
+                                process_auth_result, 100),
+             ejabberd_hooks:add(s2s_out_closed, Host, ?MODULE,
+                                process_closed, 100),
+             ejabberd_hooks:add(s2s_out_handle_info, Host, ?MODULE,
+                                handle_unexpected_info, 100),
+             ejabberd_hooks:add(s2s_out_handle_cast, Host, ?MODULE,
+                                handle_unexpected_cast, 100),
+             ejabberd_hooks:add(s2s_out_downgraded, Host, ?MODULE,
+                                process_downgraded, 100)
+       end, ?MYHOSTS).
+ %%%===================================================================
+ %%% Hooks
+ %%%===================================================================
+ process_auth_result(#{server := LServer, remote_server := RServer} = State,
+                   {false, Reason}) ->
+     Delay = get_delay(),
+     ?INFO_MSG("Failed to establish outbound s2s connection ~s -> ~s: "
+             "authentication failed; bouncing for ~p seconds",
+             [LServer, RServer, Delay]),
+     State1 = State#{on_route => bounce, stop_reason => Reason},
+     State2 = close(State1),
+     State3 = bounce_queue(State2),
+     xmpp_stream_out:set_timeout(State3, timer:seconds(Delay));
+ process_auth_result(State, true) ->
+     State.
+ process_closed(#{server := LServer, remote_server := RServer,
+                on_route := send} = State,
+              Reason) ->
+     ?INFO_MSG("Closing outbound s2s connection ~s -> ~s: ~s",
+             [LServer, RServer, xmpp_stream_out:format_error(Reason)]),
+     stop(State);
+ process_closed(#{server := LServer, remote_server := RServer} = State,
+              Reason) ->
+     Delay = get_delay(),
+     ?INFO_MSG("Failed to establish outbound s2s connection ~s -> ~s: ~s; "
+             "bouncing for ~p seconds",
+             [LServer, RServer, xmpp_stream_out:format_error(Reason), Delay]),
+     State1 = State#{on_route => bounce},
+     State2 = bounce_queue(State1),
+     xmpp_stream_out:set_timeout(State2, timer:seconds(Delay)).
+ handle_unexpected_info(State, Info) ->
+     ?WARNING_MSG("got unexpected info: ~p", [Info]),
+     State.
+ handle_unexpected_cast(State, Msg) ->
+     ?WARNING_MSG("got unexpected cast: ~p", [Msg]),
+     State.
+ process_downgraded(State, _StreamStart) ->
+     send(State, xmpp:serr_unsupported_version()).
+ %%%===================================================================
+ %%% gen_server callbacks
+ %%%===================================================================
+ tls_options(#{server := LServer}) ->
+     ejabberd_s2s:tls_options(LServer, []).
+ tls_required(#{server := LServer}) ->
+     ejabberd_s2s:tls_required(LServer).
+ tls_verify(#{server := LServer}) ->
+     ejabberd_s2s:tls_verify(LServer).
+ tls_enabled(#{server := LServer}) ->
+     ejabberd_s2s:tls_enabled(LServer).
+ connect_timeout(#{server := LServer}) ->
+     ejabberd_config:get_option(
+       {outgoing_s2s_timeout, LServer},
+       fun(TimeOut) when is_integer(TimeOut), TimeOut > 0 ->
+               timer:seconds(TimeOut);
+          (infinity) ->
+               infinity
+       end, timer:seconds(10)).
  
- %% This state is use to avoid reconnecting to often to bad sockets
- wait_before_retry(_Event, StateData) ->
-     {next_state, wait_before_retry, StateData, ?FSMTIMEOUT}.
+ default_port(#{server := LServer}) ->
+     ejabberd_config:get_option(
+       {outgoing_s2s_port, LServer},
+       fun(I) when is_integer(I), I > 0, I =< 65536 -> I end,
+       5269).
  
- relay_to_bridge(stop, StateData) ->
-     wait_before_reconnect(StateData);
- relay_to_bridge(closed, StateData) ->
-     ?INFO_MSG("relay to bridge: ~s -> ~s (closed)",
-             [StateData#state.myname, StateData#state.server]),
-     {stop, normal, StateData};
- relay_to_bridge(_Event, StateData) ->
-     {next_state, relay_to_bridge, StateData}.
+ address_families(#{server := LServer}) ->
+     ejabberd_config:get_option(
+       {outgoing_s2s_families, LServer},
+       fun(Families) ->
+             lists:map(
+               fun(ipv4) -> inet;
+                  (ipv6) -> inet6
+               end, Families)
+       end, [inet, inet6]).
  
- stream_established({xmlstreamelement, El}, StateData) ->
-     decode_element(El, stream_established, StateData);
- stream_established(#db_verify{to = VTo, from = VFrom, id = VId, type = VType},
-                  StateData) ->
-     ?DEBUG("recv verify: ~p", [{VFrom, VTo, VId, VType}]),
-     case StateData#state.verify of
-       {VPid, _VKey, _SID} ->
-           case VType of
-               valid ->
-                   p1_fsm:send_event(VPid,
-                                     {valid, StateData#state.server,
-                                      StateData#state.myname});
-               _ ->
-                   p1_fsm:send_event(VPid,
-                                     {invalid, StateData#state.server,
-                                      StateData#state.myname})
-           end;
-       _ -> ok
-     end,
-     {next_state, stream_established, StateData};
- stream_established(Event, StateData) ->
-     handle_unexpected_event(Event, stream_established, StateData).
+ dns_retries(#{server := LServer}) ->
+     ejabberd_config:get_option(
+       {s2s_dns_retries, LServer},
+       fun(I) when is_integer(I), I>=0 -> I end,
+       2).
  
- -spec handle_unexpected_event(term(), state_name(), state()) -> fsm_transition().
- handle_unexpected_event(Event, StateName, StateData) ->
-     case Event of
-       {xmlstreamerror, _} ->
-           send_element(StateData, xmpp:serr_not_well_formed()),
-           ?INFO_MSG("Closing s2s connection ~s -> ~s in state ~s: "
-                     "got invalid XML from peer",
-                     [StateData#state.myname, StateData#state.server,
-                      StateName]),
-           {stop, normal, StateData};
-       {xmlstreamend, _} ->
-           ?INFO_MSG("Closing s2s connection ~s -> ~s in state ~s: "
-                     "XML stream closed by peer",
-                     [StateData#state.myname, StateData#state.server,
-                      StateName]),
-           {stop, normal, StateData};
-       timeout ->
-           send_element(StateData, xmpp:serr_connection_timeout()),
-           ?INFO_MSG("Closing s2s connection ~s -> ~s in state ~s: "
-                     "timed out during establishing an XML stream",
-                     [StateData#state.myname, StateData#state.server,
-                      StateName]),
-           {stop, normal, StateData};
-       closed ->
-           ?INFO_MSG("Closing s2s connection ~s -> ~s in state ~s: "
-                     "connection socket closed",
-                     [StateData#state.myname, StateData#state.server,
-                      StateName]),
-           {stop, normal, StateData};
-       Pkt when StateName == wait_for_stream;
-                StateName == wait_for_features;
-                StateName == wait_for_auth_result;
-                StateName == wait_for_starttls_proceed ->
-           send_element(StateData, xmpp:serr_bad_format()),
-           ?INFO_MSG("Closing s2s connection ~s -> ~s in state ~s: "
-                     "got unexpected event ~p",
-                     [StateData#state.myname, StateData#state.server,
-                      StateName, Pkt]),
-           {stop, normal, StateData};
-       _ ->
-           {next_state, StateName, StateData, get_timeout_interval(StateName)}
+ dns_timeout(#{server := LServer}) ->
+     ejabberd_config:get_option(
+       {s2s_dns_timeout, LServer},
+       fun(I) when is_integer(I), I>=0 ->
+             timer:seconds(I);
+        (infinity) ->
+             infinity
+       end, timer:seconds(10)).
+ handle_auth_success(Mech, #{sockmod := SockMod,
+                           socket := Socket, ip := IP,
+                           remote_server := RServer,
+                           server := LServer} = State) ->
+     ?INFO_MSG("(~s) Accepted outbound s2s ~s authentication ~s -> ~s (~s)",
+             [SockMod:pp(Socket), Mech, LServer, RServer,
+              ejabberd_config:may_hide_data(jlib:ip_to_list(IP))]),
+     ejabberd_hooks:run_fold(s2s_out_auth_result, LServer, State, [true]).
+ handle_auth_failure(Mech, Reason,
+                   #{sockmod := SockMod,
+                     socket := Socket, ip := IP,
+                     remote_server := RServer,
+                     server := LServer} = State) ->
+     ?INFO_MSG("(~s) Failed outbound s2s ~s authentication ~s -> ~s (~s): ~s",
+             [SockMod:pp(Socket), Mech, LServer, RServer,
+              ejabberd_config:may_hide_data(jlib:ip_to_list(IP)),
+              xmpp_stream_out:format_error(Reason)]),
+     ejabberd_hooks:run_fold(s2s_out_auth_result, LServer, State, [{false, Reason}]).
+ handle_packet(Pkt, #{server := LServer} = State) ->
+     ejabberd_hooks:run_fold(s2s_out_packet, LServer, State, [Pkt]).
+ handle_stream_end(Reason, #{server := LServer} = State) ->
+     State1 = State#{stop_reason => Reason},
+     ejabberd_hooks:run_fold(s2s_out_closed, LServer, State1, [Reason]).
+ handle_stream_downgraded(StreamStart, #{server := LServer} = State) ->
+     ejabberd_hooks:run_fold(s2s_out_downgraded, LServer, State, [StreamStart]).
+ handle_stream_established(State) ->
+     State1 = State#{on_route => send},
+     State2 = resend_queue(State1),
+     set_idle_timeout(State2).
+ handle_cdata(Data, #{server := LServer} = State) ->
+     ejabberd_hooks:run_fold(s2s_out_handle_cdata, LServer, State, [Data]).
+ handle_recv(El, Pkt, #{server := LServer} = State) ->
+     ejabberd_hooks:run_fold(s2s_out_handle_recv, LServer, State, [El, Pkt]).
+ handle_send(El, Pkt, #{server := LServer} = State) ->
+     ejabberd_hooks:run_fold(s2s_out_handle_send, LServer, State, [El, Pkt]).
+ handle_timeout(#{on_route := Action} = State) ->
+     case Action of
+       bounce -> stop(State);
+       _ -> send(State, xmpp:serr_connection_timeout())
      end.
  
- %%----------------------------------------------------------------------
- %% Func: StateName/3
- %% Returns: {next_state, NextStateName, NextStateData}            |
- %%          {next_state, NextStateName, NextStateData, Timeout}   |
- %%          {reply, Reply, NextStateName, NextStateData}          |
- %%          {reply, Reply, NextStateName, NextStateData, Timeout} |
- %%          {stop, Reason, NewStateData}                          |
- %%          {stop, Reason, Reply, NewStateData}
- %%----------------------------------------------------------------------
- %%state_name(Event, From, StateData) ->
- %%    Reply = ok,
- %%    {reply, Reply, state_name, StateData}.
- handle_event(_Event, StateName, StateData) ->
-     {next_state, StateName, StateData,
-      get_timeout_interval(StateName)}.
- handle_sync_event(get_state_infos, _From, StateName,
-                 StateData) ->
-     {Addr, Port} = try
-                    ejabberd_socket:peername(StateData#state.socket)
-                  of
-                    {ok, {A, P}} -> {A, P};
-                    {error, _} -> {unknown, unknown}
-                  catch
-                    _:_ -> {unknown, unknown}
-                  end,
-     Infos = [{direction, out}, {statename, StateName},
-            {addr, Addr}, {port, Port},
-            {streamid, StateData#state.streamid},
-            {use_v10, StateData#state.use_v10},
-            {tls, StateData#state.tls},
-            {tls_required, StateData#state.tls_required},
-            {tls_enabled, StateData#state.tls_enabled},
-            {tls_options, StateData#state.tls_options},
-            {authenticated, StateData#state.authenticated},
-            {db_enabled, StateData#state.db_enabled},
-            {try_auth, StateData#state.try_auth},
-            {myname, StateData#state.myname},
-            {server, StateData#state.server},
-            {delay_to_retry, StateData#state.delay_to_retry},
-            {verify, StateData#state.verify}],
-     Reply = {state_infos, Infos},
-     {reply, Reply, StateName, StateData};
- %%----------------------------------------------------------------------
- %% Func: handle_sync_event/4
- %% Returns: {next_state, NextStateName, NextStateData}            |
- %%          {next_state, NextStateName, NextStateData, Timeout}   |
- %%          {reply, Reply, NextStateName, NextStateData}          |
- %%          {reply, Reply, NextStateName, NextStateData, Timeout} |
- %%          {stop, Reason, NewStateData}                          |
- %%          {stop, Reason, Reply, NewStateData}
- %%----------------------------------------------------------------------
- handle_sync_event(_Event, _From, StateName,
-                 StateData) ->
-     Reply = ok,
-     {reply, Reply, StateName, StateData,
-      get_timeout_interval(StateName)}.
- code_change(_OldVsn, StateName, StateData, _Extra) ->
-     {ok, StateName, StateData}.
- handle_info({send_text, Text}, StateName, StateData) ->
-     send_text(StateData, Text),
-     cancel_timer(StateData#state.timer),
-     Timer = erlang:start_timer(?S2STIMEOUT, self(), []),
-     {next_state, StateName, StateData#state{timer = Timer},
-      get_timeout_interval(StateName)};
- handle_info({send_element, El}, StateName, StateData) ->
-     case StateName of
-       stream_established ->
-         cancel_timer(StateData#state.timer),
-         Timer = erlang:start_timer(?S2STIMEOUT, self(), []),
-         send_element(StateData, El),
-         {next_state, StateName, StateData#state{timer = Timer}};
-       %% In this state we bounce all message: We are waiting before
-       %% trying to reconnect
-       wait_before_retry ->
-         bounce_element(El, xmpp:err_remote_server_not_found()),
-         {next_state, StateName, StateData};
-       relay_to_bridge ->
-         {Mod, Fun} = StateData#state.bridge,
-         ?DEBUG("relaying stanza via ~p:~p/1", [Mod, Fun]),
-         case catch Mod:Fun(El) of
-           {'EXIT', Reason} ->
-               ?ERROR_MSG("Error while relaying to bridge: ~p",
-                          [Reason]),
-               bounce_element(El, xmpp:err_internal_server_error()),
-               wait_before_reconnect(StateData);
-           _ -> {next_state, StateName, StateData}
+ init([#{server := LServer, remote_server := RServer} = State, Opts]) ->
+     State1 = State#{on_route => queue,
+                   queue => queue:new(),
+                   xmlns => ?NS_SERVER,
+                   lang => ?MYLANG,
+                   shaper => none},
+     ?INFO_MSG("Outbound s2s connection started: ~s -> ~s",
+             [LServer, RServer]),
+     ejabberd_hooks:run_fold(s2s_out_init, LServer, {ok, State1}, [Opts]).
+ handle_call(Request, From, #{server := LServer} = State) ->
+     ejabberd_hooks:run_fold(s2s_out_handle_call, LServer, State, [Request, From]).
+ handle_cast({update_state, Fun}, State) ->
+     case Fun of
+       {M, F, A} -> erlang:apply(M, F, [State|A]);
+       _ when is_function(Fun) -> Fun(State)
 -    end;
 +        end;
-       _ ->
-         Q = queue:in(El, StateData#state.queue),
-         {next_state, StateName, StateData#state{queue = Q},
-          get_timeout_interval(StateName)}
+ handle_cast(Msg, #{server := LServer} = State) ->
+     ejabberd_hooks:run_fold(s2s_out_handle_cast, LServer, State, [Msg]).
+ handle_info({route, Pkt}, #{queue := Q, on_route := Action} = State) ->
+     case Action of
+       queue -> State#{queue => queue:in(Pkt, Q)};
+       bounce -> bounce_packet(Pkt, State);
+       send -> set_idle_timeout(send(State, Pkt))
      end;
- handle_info({timeout, Timer, _}, wait_before_retry,
-           #state{timer = Timer} = StateData) ->
-     ?INFO_MSG("Reconnect delay expired: Will now retry "
-             "to connect to ~s when needed.",
-             [StateData#state.server]),
-     {stop, normal, StateData};
- handle_info({timeout, Timer, _}, _StateName,
-           #state{timer = Timer} = StateData) ->
-     ?INFO_MSG("Closing connection with ~s: timeout",
-             [StateData#state.server]),
-     {stop, normal, StateData};
- handle_info(terminate_if_waiting_before_retry,
-           wait_before_retry, StateData) ->
-     {stop, normal, StateData};
- handle_info(terminate_if_waiting_before_retry,
-           StateName, StateData) ->
-     {next_state, StateName, StateData,
-      get_timeout_interval(StateName)};
- handle_info(_, StateName, StateData) ->
-     {next_state, StateName, StateData,
-      get_timeout_interval(StateName)}.
- terminate(Reason, StateName, StateData) ->
-     ?DEBUG("terminated: ~p", [{Reason, StateName}]),
-     case StateData#state.new of
-       false -> ok;
-       true ->
-         ejabberd_s2s:remove_connection({StateData#state.myname,
-                                         StateData#state.server},
-                                        self())
+ handle_info(Info, #{server := LServer} = State) ->
+     ejabberd_hooks:run_fold(s2s_out_handle_info, LServer, State, [Info]).
+ terminate(Reason, #{server := LServer,
+                   remote_server := RServer} = State) ->
+     ejabberd_s2s:remove_connection({LServer, RServer}, self()),
+     State1 = case Reason of
+                normal -> State;
+                _ -> State#{stop_reason => internal_failure}
 -           end,
 +    end,
-     bounce_queue(StateData#state.queue, xmpp:err_remote_server_not_found()),
-     bounce_messages(xmpp:err_remote_server_not_found()),
-     case StateData#state.socket of
-       undefined -> ok;
-       _Socket ->
-           catch send_trailer(StateData),
-           ejabberd_socket:close(StateData#state.socket)
-     end,
-     ok.
+     bounce_queue(State1),
+     bounce_message_queue(State1).
  
- print_state(State) -> State.
+ code_change(_OldVsn, State, _Extra) ->
+     {ok, State}.
  
- %%%----------------------------------------------------------------------
+ %%%===================================================================
  %%% Internal functions
- %%%----------------------------------------------------------------------
- -spec send_text(state(), iodata()) -> ok.
- send_text(StateData, Text) ->
-     ?DEBUG("Send Text on stream = ~s", [Text]),
-     ejabberd_socket:send(StateData#state.socket, Text).
- -spec send_element(state(), xmpp_element()) -> ok.
- send_element(StateData, El) ->
-     El1 = xmpp:encode(El, ?NS_SERVER),
-     send_text(StateData, fxml:element_to_binary(El1)).
- -spec send_header(state(), undefined | {integer(), integer()}) -> ok.
- send_header(StateData, Version) ->
-     Header = xmpp:encode(
-              #stream_start{xmlns = ?NS_SERVER,
-                            stream_xmlns = ?NS_STREAM,
-                            db_xmlns = ?NS_SERVER_DIALBACK,
-                            from = jid:make(StateData#state.myname),
-                            to = jid:make(StateData#state.server),
-                            version = Version}),
-     send_text(StateData, fxml:element_to_header(Header)).
- -spec send_trailer(state()) -> ok.
- send_trailer(StateData) ->
-     send_text(StateData, <<"</stream:stream>">>).
- -spec send_queue(state(), queue:queue()) -> ok.
- send_queue(StateData, Q) ->
-     case queue:out(Q) of
-       {{value, El}, Q1} ->
-         send_element(StateData, El), send_queue(StateData, Q1);
-       {empty, _Q1} -> ok
+ %%%===================================================================
+ -spec resend_queue(state()) -> state().
+ resend_queue(#{queue := Q} = State) ->
+     State1 = State#{queue => queue:new()},
+     jlib:queue_foldl(
+       fun(Pkt, AccState) ->
+             send(AccState, Pkt)
+       end, State1, Q).
+ -spec bounce_queue(state()) -> state().
+ bounce_queue(#{queue := Q} = State) ->
+     State1 = State#{queue => queue:new()},
+     jlib:queue_foldl(
+       fun(Pkt, AccState) ->
+             bounce_packet(Pkt, AccState)
+       end, State1, Q).
+ -spec bounce_message_queue(state()) -> state().
+ bounce_message_queue(State) ->
+     receive {route, Pkt} ->
+           State1 = bounce_packet(Pkt, State),
+           bounce_message_queue(State1)
+     after 0 ->
+           State
      end.
  
- %% Bounce a single message (xmlelement)
- -spec bounce_element(stanza(), stanza_error()) -> ok.
- bounce_element(El, Error) ->
-     From = xmpp:get_from(El),
-     To = xmpp:get_to(El),
-     ejabberd_router:route_error(To, From, El, Error).
- -spec bounce_queue(queue:queue(), stanza_error()) -> ok.
- bounce_queue(Q, Error) ->
-     case queue:out(Q) of
-       {{value, El}, Q1} ->
-         bounce_element(El, Error), bounce_queue(Q1, Error);
-       {empty, _} -> ok
-     end.
- -spec new_id() -> binary().
- new_id() -> randoms:get_string().
- -spec cancel_timer(reference()) -> ok.
- cancel_timer(Timer) ->
-     erlang:cancel_timer(Timer),
-     receive {timeout, Timer, _} -> ok after 0 -> ok end.
- -spec bounce_messages(stanza_error()) -> ok.
- bounce_messages(Error) ->
-     receive
-       {send_element, El} ->
-         bounce_element(El, Error), bounce_messages(Error)
-       after 0 -> ok
-     end.
- -spec send_db_request(state()) -> fsm_transition().
- send_db_request(StateData) ->
-     Server = StateData#state.server,
-     New = case StateData#state.new of
-             false ->
-                 ejabberd_s2s:try_register({StateData#state.myname, Server});
-             true ->
-                 true
-         end,
-     NewStateData = StateData#state{new = New},
-     try case New of
-           false -> ok;
-           true ->
-             Key1 = ejabberd_s2s:make_key(
-                      {StateData#state.myname, Server},
-                      StateData#state.remote_streamid),
-             send_element(StateData,
-                          #db_result{from = StateData#state.myname,
-                                     to = Server,
-                                     key = Key1})
-       end,
-       case StateData#state.verify of
-         false -> ok;
-         {_Pid, Key2, SID} ->
-             send_element(StateData,
-                          #db_verify{from = StateData#state.myname,
-                                     to = StateData#state.server,
-                                     id = SID,
-                                     key = Key2})
-       end,
-       {next_state, wait_for_validation, NewStateData,
-        (?FSMTIMEOUT) * 6}
-     catch
-       _:_ -> {stop, normal, NewStateData}
-     end.
- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- %% SRV support
- -include_lib("kernel/include/inet.hrl").
- -spec get_addr_port(binary()) -> [{binary(), inet:port_number()}].
- get_addr_port(Server) ->
-     Res = srv_lookup(Server),
-     case Res of
-       {error, Reason} ->
-         ?DEBUG("srv lookup of '~s' failed: ~p~n",
-                [Server, Reason]),
-         [{Server, outgoing_s2s_port()}];
-       {ok, HEnt} ->
-         ?DEBUG("srv lookup of '~s': ~p~n",
-                [Server, HEnt#hostent.h_addr_list]),
-         AddrList = HEnt#hostent.h_addr_list,
-         case catch lists:map(fun ({Priority, Weight, Port,
-                                    Host}) ->
-                                      N = case Weight of
-                                            0 -> 0;
+ -spec bounce_packet(xmpp_element(), state()) -> state().
+ bounce_packet(Pkt, State) when ?is_stanza(Pkt) ->
+     From = xmpp:get_from(Pkt),
+     To = xmpp:get_to(Pkt),
+     Lang = xmpp:get_lang(Pkt),
+     Err = mk_bounce_error(Lang, State),
+     ejabberd_router:route_error(To, From, Pkt, Err),
+     State;
+ bounce_packet(_, State) ->
+     State.
+ -spec mk_bounce_error(binary(), state()) -> stanza_error().
+ mk_bounce_error(Lang, #{stop_reason := Why}) ->
+     Reason = xmpp_stream_out:format_error(Why),
+     case Why of
+       internal_failure ->
+           xmpp:err_internal_server_error();
+       {dns, _} ->
+           xmpp:err_remote_server_not_found(Reason, Lang);
 -      _ ->
 +                                           _ ->
-                                                (Weight + 1) * randoms:uniform()
-                                          end,
-                                      {Priority * 65536 - N, Host, Port}
-                              end,
-                              AddrList)
-             of
-           SortedList = [_ | _] ->
-               List = lists:map(fun ({_, Host, Port}) ->
-                                          {list_to_binary(Host), Port}
-                                end,
-                                lists:keysort(1, SortedList)),
-               ?DEBUG("srv lookup of '~s': ~p~n", [Server, List]),
-               List;
-           _ -> [{Server, outgoing_s2s_port()}]
-         end
-     end.
- srv_lookup(Server) ->
-     TimeoutMs = timer:seconds(
-                   ejabberd_config:get_option(
-                     s2s_dns_timeout,
-                     fun(I) when is_integer(I), I>=0 -> I end,
-                     10)),
-     Retries = ejabberd_config:get_option(
-                 s2s_dns_retries,
-                 fun(I) when is_integer(I), I>=0 -> I end,
-                 2),
-     srv_lookup(binary_to_list(Server), TimeoutMs, Retries).
- %% XXX - this behaviour is suboptimal in the case that the domain
- %% has a "_xmpp-server._tcp." but not a "_jabber._tcp." record and
- %% we don't get a DNS reply for the "_xmpp-server._tcp." lookup. In this
- %% case we'll give up when we get the "_jabber._tcp." nxdomain reply.
- srv_lookup(_Server, _Timeout, Retries)
-     when Retries < 1 ->
-     {error, timeout};
- srv_lookup(Server, Timeout, Retries) ->
-     case inet_res:getbyname("_xmpp-server._tcp." ++ Server,
-                           srv, Timeout)
-       of
-       {error, _Reason} ->
-         case inet_res:getbyname("_jabber._tcp." ++ Server, srv,
-                                 Timeout)
-             of
-           {error, timeout} ->
-               ?ERROR_MSG("The DNS servers~n  ~p~ntimed out on "
-                          "request for ~p IN SRV. You should check "
-                          "your DNS configuration.",
-                          [inet_db:res_option(nameserver), Server]),
-               srv_lookup(Server, Timeout, Retries - 1);
-           R -> R
+           xmpp:err_remote_server_timeout(Reason, Lang)
 -    end;
 +        end;
-       {ok, _HEnt} = R -> R
-     end.
- test_get_addr_port(Server) ->
-     lists:foldl(fun (_, Acc) ->
-                       [HostPort | _] = get_addr_port(Server),
-                       case lists:keysearch(HostPort, 1, Acc) of
-                         false -> [{HostPort, 1} | Acc];
-                         {value, {_, Num}} ->
-                             lists:keyreplace(HostPort, 1, Acc,
-                                              {HostPort, Num + 1})
-                       end
-               end,
-               [], lists:seq(1, 100000)).
- get_addrs(Host, Family) ->
-     Type = case Family of
-            inet4 -> inet;
-            ipv4 -> inet;
-            inet6 -> inet6;
-            ipv6 -> inet6
-          end,
-     case inet:gethostbyname(binary_to_list(Host), Type) of
-       {ok, #hostent{h_addr_list = Addrs}} ->
-         ?DEBUG("~s of ~s resolved to: ~p~n",
-                [Type, Host, Addrs]),
-         Addrs;
-       {error, Reason} ->
-         ?DEBUG("~s lookup of '~s' failed: ~p~n",
-                [Type, Host, Reason]),
-         []
-     end.
- -spec outgoing_s2s_port() -> pos_integer().
- outgoing_s2s_port() ->
-     ejabberd_config:get_option(
-       outgoing_s2s_port,
-       fun(I) when is_integer(I), I > 0, I =< 65536 -> I end,
-       5269).
- -spec outgoing_s2s_families() -> [ipv4 | ipv6].
- outgoing_s2s_families() ->
-     ejabberd_config:get_option(
-       outgoing_s2s_families,
-       fun(Families) ->
-               true = lists:all(
-                        fun(ipv4) -> true;
-                           (ipv6) -> true
-                        end, Families),
-               Families
-       end, [ipv4, ipv6]).
- -spec outgoing_s2s_timeout() -> pos_integer().
- outgoing_s2s_timeout() ->
-     ejabberd_config:get_option(
-       outgoing_s2s_timeout,
-       fun(TimeOut) when is_integer(TimeOut), TimeOut > 0 ->
-               TimeOut;
-          (infinity) ->
-               infinity
-       end, 10000).
+ mk_bounce_error(_Lang, _State) ->
+     %% We should not be here. Probably :)
+     xmpp:err_remote_server_not_found().
+ -spec get_delay() -> non_neg_integer().
+ get_delay() ->
+     MaxDelay = ejabberd_config:get_option(
+                s2s_max_retry_delay,
+                fun(I) when is_integer(I), I > 0 -> I end,
+                300),
+     crypto:rand_uniform(1, MaxDelay).
+ -spec set_idle_timeout(state()) -> state().
+ set_idle_timeout(#{on_route := send, server := LServer} = State) ->
+     Timeout = ejabberd_s2s:get_idle_timeout(LServer),
+     xmpp_stream_out:set_timeout(State, Timeout);
+ set_idle_timeout(State) ->
+     State.
  
  transform_options(Opts) ->
      lists:foldl(fun transform_options/2, [], Opts).
index 5003ff6ab5620dd62494249e7be2639f9550bd3c,d84de3db419b85e1cfe9889f60c6ecda2beea9c1..dd949f2f951f34d570048c3cbf061eae4e6b9088
@@@ -1,11 -1,8 +1,8 @@@
- %%%----------------------------------------------------------------------
- %%% File    : ejabberd_service.erl
- %%% Author  : Alexey Shchepin <alexey@process-one.net>
- %%% Purpose : External component management (XEP-0114)
- %%% Created :  6 Dec 2002 by Alexey Shchepin <alexey@process-one.net>
+ %%%-------------------------------------------------------------------
+ %%% Created : 11 Dec 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
  %%%
  %%%
 -%%% ejabberd, Copyright (C) 2002-2016   ProcessOne
 +%%% ejabberd, Copyright (C) 2002-2017   ProcessOne
  %%%
  %%% This program is free software; you can redistribute it and/or
  %%% modify it under the terms of the GNU General Public License as
@@@ -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, <<"</stream:stream>">>).
- -spec decode_element(xmlel(), state_name(), state()) -> fsm_transition().
- decode_element(#xmlel{} = El, StateName, StateData) ->
-     try xmpp:decode(El, ?NS_COMPONENT, [ignore_els]) of
-       Pkt -> ?MODULE:StateName(Pkt, StateData)
-     catch error:{xmpp_codec, Why} ->
-             case xmpp:is_stanza(El) of
-                 true ->
-                     Lang = xmpp:get_lang(El),
-                     Txt = xmpp:format_error(Why),
-                     send_error(StateData, El, xmpp:err_bad_request(Txt, Lang));
-                 false ->
+                     ejabberd_hooks:run(component_disconnected, [H, Reason])
+             end, dict:fetch_keys(HostOpts));
+       _ ->
 -          ok
 +                    ok
-             end,
-             {next_state, StateName, StateData}
      end.
  
+ code_change(_OldVsn, State, _Extra) ->
+     {ok, State}.
+ %%%===================================================================
+ %%% Internal functions
+ %%%===================================================================
  -spec check_from(jid(), state()) -> boolean().
- check_from(_From, #state{check_from = false}) ->
+ check_from(_From, #{check_from := false}) ->
      %% If the admin does not want to check the from field
      %% when accept packets from any address.
      %% In this case, the component can send packet of
index 5abcaa57201c64025147c532c74f3ababe411d16,5eb67114953cd17dcf61aff477a2dc5e68b7c262..98aaed573a7e6cebf8a3ad68819f543a1411cec9
@@@ -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()].
index 4cf36a81c25823eb07ae35f68d4e898a1d936657,83b7ae9b90f17d0da7001149d4c50f262b7a5189..c7b57a6a1bf047ad88986458be32255fdc92e894
         sockname/1, peername/1]).
  
  -include("ejabberd.hrl").
+ -include("xmpp.hrl").
  -include("logger.hrl").
  
 --type sockmod() :: ejabberd_http_bind |
 -                 ejabberd_bosh |
 +-type sockmod() :: ejabberd_bosh |
                     ejabberd_http_ws |
                     gen_tcp | fast_tls | ezlib.
  -type receiver() :: pid () | atom().
  %%====================================================================
  %% API
  %%====================================================================
- -spec start(atom(), sockmod(), socket(), [{atom(), any()}]) -> any().
+ -spec start(atom(), sockmod(), socket(), [proplists:propery()])
+       -> {ok, pid() | independent} | {error, inet:posix() | any()}.
  start(Module, SockMod, Socket, Opts) ->
      case Module:socket_type() of
 -      xml_stream ->
+       independent -> {ok, independent};
-         MaxStanzaSize = case lists:keysearch(max_stanza_size, 1,
-                                              Opts)
-                             of
-                           {value, {_, Size}} -> Size;
-                           _ -> infinity
-                         end,
-         {ReceiverMod, Receiver, RecRef} = case catch
-                                                  SockMod:custom_receiver(Socket)
-                                               of
 +      xml_stream ->
 -                  {receiver, RecMod, RecPid} ->
+           MaxStanzaSize = proplists:get_value(max_stanza_size, Opts, infinity),
+           {ReceiverMod, Receiver, RecRef} =
+               try SockMod:custom_receiver(Socket) of
-                                                 {RecMod, RecPid, RecMod};
-                                             _ ->
-                                                 RecPid =
-                                                     ejabberd_receiver:start(Socket,
-                                                                             SockMod,
-                                                                             none,
-                                                                             MaxStanzaSize),
-                                                 {ejabberd_receiver, RecPid,
-                                                  RecPid}
 +                                            {receiver, RecMod, RecPid} ->
 -              end,
 -          SocketData = #socket_state{sockmod = SockMod,
 -                                     socket = Socket, receiver = RecRef},
 -          case Module:start({?MODULE, SocketData}, Opts) of
 -              {ok, Pid} ->
 -                  case SockMod:controlling_process(Socket, Receiver) of
+                       {RecMod, RecPid, RecMod}
+               catch _:_ ->
+                       RecPid = ejabberd_receiver:start(
+                                  Socket, SockMod, none, MaxStanzaSize),
+                       {ejabberd_receiver, RecPid, RecPid}
-                 ok -> ok;
-                 {error, _Reason} -> SockMod:close(Socket)
-               end,
-               ReceiverMod:become_controller(Receiver, Pid);
-           {error, _Reason} ->
 +                                          end,
 +        SocketData = #socket_state{sockmod = SockMod,
 +                                   socket = Socket, receiver = RecRef},
 +        case Module:start({?MODULE, SocketData}, Opts) of
 +          {ok, Pid} ->
 +              case SockMod:controlling_process(Socket, Receiver) of
 -                  SockMod:close(Socket),
 -                  case ReceiverMod of
 -                      ejabberd_receiver -> ReceiverMod:close(Receiver);
 -                      _ -> ok
+                       ok ->
+                           ReceiverMod:become_controller(Receiver, Pid),
+                           {ok, Receiver};
+                       Err ->
+                           SockMod:close(Socket),
+                           Err
+                   end;
+               Err ->
-               end
 +              SockMod:close(Socket),
 +              case ReceiverMod of
 +                ejabberd_receiver -> ReceiverMod:close(Receiver);
 +                _ -> ok
 -          end;
 -      raw ->
 -          case Module:start({SockMod, Socket}, Opts) of
 -              {ok, Pid} ->
 -                  case SockMod:controlling_process(Socket, Pid) of
+                   end,
+                   Err
-       independent -> ok;
 +        end;
-                 ok -> ok;
-                 {error, _Reason} -> SockMod:close(Socket)
 +      raw ->
 +        case Module:start({SockMod, Socket}, Opts) of
 +          {ok, Pid} ->
 +              case SockMod:controlling_process(Socket, Pid) of
 -                  end;
+                       ok ->
+                           {ok, Pid};
+                       {error, _} = Err ->
+                           SockMod:close(Socket),
+                           Err
-           {error, _Reason} -> SockMod:close(Socket)
 +              end;
 -          end
+               Err ->
+                   SockMod:close(Socket),
+                   Err
 +        end
      end.
  
  connect(Addr, Port, Opts) ->
Simple merge
Simple merge
diff --cc src/gen_mod.erl
Simple merge
diff --cc src/jlib.erl
Simple merge
Simple merge
Simple merge
index d4192447a3283d0d52597bc85c3898f8b64d9029,5195bfb733a1cc12d4bb7259b0773a560ccf39ec..3f9e902561a17b62fd0cbf5b6c570352b3d2113e
@@@ -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,
             end,
      Mod = db_mod(LServer),
      case Mod:unblock_by_filter(LUser, LServer, Filter) of
-       {atomic, ok} -> {result, undefined};
+       {atomic, ok} ->
+           xmpp:make_iq_result(IQ);
 -      {atomic, {ok, Default, List}} ->
 -          UserList = make_userlist(Default, List),
 +      {atomic, {ok, Default, List}} ->
 +        UserList = make_userlist(Default, List),
-         broadcast_list_update(LUser, LServer, Default,
-                               UserList),
-         broadcast_blocklist_event(LUser, LServer, unblock_all),
-         {result, undefined, UserList};
+           broadcast_list_update(LUser, LServer, UserList, Default),
+           broadcast_event(LUser, LServer, #unblock{}),
+           xmpp:make_iq_result(IQ);
 -      _Err ->
 +      _Err ->
            ?ERROR_MSG("Error processing ~p: ~p", [{LUser, LServer}, _Err]),
-           {error, xmpp:err_internal_server_error(<<"Database failure">>, Lang)}
+           Err = xmpp:err_internal_server_error(<<"Database failure">>, Lang),
+           xmpp:make_error(IQ, Err)
      end.
  
- -spec process_blocklist_unblock(binary(), binary(), [ljid()], binary()) ->
-                                      {error, stanza_error()} |
-                                      {result, undefined} |
-                                      {result, undefined, userlist()}.
- process_blocklist_unblock(LUser, LServer, JIDs, Lang) ->
+ -spec process_unblock(iq(), [ljid()]) -> iq().
+ process_unblock(#iq{from = #jid{luser = LUser, lserver = LServer},
+                   lang = Lang} = IQ, JIDs) ->
      Filter = fun (List) ->
                     lists:filter(fun (#listitem{action = deny, type = jid,
                                                 value = JID}) ->
             end,
      Mod = db_mod(LServer),
      case Mod:unblock_by_filter(LUser, LServer, Filter) of
-       {atomic, ok} -> {result, undefined};
+       {atomic, ok} ->
+           xmpp:make_iq_result(IQ);
 -      {atomic, {ok, Default, List}} ->
 -          UserList = make_userlist(Default, List),
 +      {atomic, {ok, Default, List}} ->
 +        UserList = make_userlist(Default, List),
-         broadcast_list_update(LUser, LServer, Default,
-                               UserList),
-         broadcast_blocklist_event(LUser, LServer,
-                                   {unblock, [jid:make(J) || J <- JIDs]}),
-         {result, undefined, UserList};
+           broadcast_list_update(LUser, LServer, UserList, Default),
+           broadcast_event(LUser, LServer,
+                           #unblock{items = [jid:make(J) || J <- JIDs]}),
+           xmpp:make_iq_result(IQ);
 -      _Err ->
 +      _Err ->
            ?ERROR_MSG("Error processing ~p: ~p", [{LUser, LServer, JIDs}, _Err]),
-           {error, xmpp:err_internal_server_error(<<"Database failure">>, Lang)}
+           Err = xmpp:err_internal_server_error(<<"Database failure">>, Lang),
+           xmpp:make_error(IQ, Err)
      end.
  
  -spec make_userlist(binary(), [listitem()]) -> userlist().
@@@ -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().
index cebf4e6ba87f65fdcb77ec2bec6ff2961cc687a7,43a00edce58a1f477527142689edd2b6d3800ae7..62dc31ac80502f8b569b5ac32f5aa7a1b700331f
@@@ -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,iVBORw0KGgoAAAANSUhEUgAAASwAAABACAYAAACgPErgAAAVtklEQVR42uydeXhU1d2ADzuyCcii4FZBZdEqVYtaUGqlikVwaRWsrWBRKIh8KgXcEAX9VECtoljcURAJICJWQLaKLAEjIHtBSNhC9oQkk2SW+36/52P+iIE5d5kzk4Fn3ud5H/7gIbnD3PPOveeeuVedygD9xLlQMh9yJ0D+GeokJAg1xcEWzOWYj0DBaQm0fXdaMCu8bROgpJVKksQj0+F84OXw/jRd7KVOdbbCFI6jdDNktlAnESVQNwSzOI6yhZBaV1UzAXiB4wj8CBtbqiRJXDIILtgL+zmOb8aoU5Xh0MMiEr5x6iSiAIZYROLo/aoayYMeocjbNlklSeKC9VAjBDM5IcEQfNZFnYq8AM8SkcJV6iSiCGYRkT1LVDWSA08Tkfy9MLaeOsUA2ot9xJHiW2KK+LW4Mvxnijg1/Pd9xQvFGiqJLUOhcT5kE5Etg0/VYI3TBOvbkyxYKURk2dpqDZb2gyEjC1SjUyBQdcQe4mtimujDHWXiD+Lr4g1iXZXkhISgBZBLZIaoU5GR0I2IVDxxkgVrNhH5z+pqDZb2g+FAJjQ4aYMFtBAfEzdils3iP8TkhYkqlMEZFuQQmcHqVGUBTOI40lLhz02TwUoGKxJAI/FxMYPYckB8Smyikvw/vlM/WHqAP4ifiHPF0fBsYyUkg5UM1okAbhV/JL5sE29TSZLBOhHJYCWDVRXgNPFVqpfXxQbJYCWDlQxWMlgRAc4VV5EYrBZ/kQxWMljJYFVLsBomdLCAS8RdJBZ7xMuSwTIcLKCO2EG8VrxdvEfsLV4F1vkJstq9BlidwN8hVleSLLgUuFzsEv6zs9g4EYIFNBAvFi87tm3WZWI7oHZ8glWnkWbb6ortCG/bsT+tDsBpcYrVpeIhzBHAHJni5XGOd5PwvnKJif0DaCh2/Pn7y/linbgFKx/qrjkWpffEHZo3qVhMBWuC2EVVA4ehay6sAYJgBeDocuh9iYqCL+F04DbxLTFNPMzxWGI6WCuBJ8Rfxi5YK34WLKC5BX3FVzj22n8S/fwcX/i9+1wcLraLVbAqr8OqgNOA31nwPLBc3HWCNU1+cbe4WBwjXhbD08A9eOcn8SNxmHij+EuxQ3iwdxP/Jr4pbsU7+2J5ehiCNuI9FkwN78sZol+0xDSYd6OH75R2BcZasALYKwZO0IWd4fd3LIS6g1XHeLBWQV1gsLgJ9wTBWgSBm1ScuBYuAvI4jrx9cOEZyiXzoUUIxhbDPm+fvFlL4PPe5oO14FslvAjnrYeXgYO4p0T8UrzJbLB2HlbCZMqbfwOjgrDD41HLCrE/LK1pKFYNxFS8sVDsK1Y5ctQeRf42HLcy3JMmNjIc6+ss+ETMRUuRH9Zd7SBS9QMwCFiDNzZBYAT4G5dTUcuC7KiClQ3dj8B6osYSAzOgoq2KMatgChF5e5RyQQXcVwL7MMNXsKOTuWAd2FBG9uhyyMMI6XPBd7GZYOUd8pP+UCn8hBEOr4KCbgYG7Ju4Z43YU0VB+LRoLu6ZpgyQAhflw2e4Y7lYS0XADzdZ8ANGsLaX88PgACHvwfLBY4Afo4T2Q9lNMZ68W0lE/J8oB5RDkxDMwDhF+XC4v6tgxZdcKLov+mBhYR4/FD8VRTRu93CE97RYTxkCuFfMxR13qSjIhoE+Tx9qPh9Mb6WqcBRqBuBli5hguQ4WUGsPvGsRKyw/hAaoGJEPy4lI4Uxlw3XHzu/XEVNKHk3QYAmWWDEecmraBqtaCE6HrCYeJpX34Jwc8eYYXp3cjHPSxWbKA9/Da3im+CeY1kBV4gNoUQILiB/6YD0JNYFP4jMoGFINwZqhNHwKLbfB5ji9/kdMBss8vk9MBcs8pasht7GLSIzHOQfFX6kYApyFuw/FFz38jveJitxBqhI3QMsS2IiQMMFaDFOJHyF45fZECdYOqA0sIX5YMPkOk8Eyz96xpoJlnvIZDgduazEfZ+SJVyoNhqO1DWcUim1dfJf2DTxTVgxFY1Ql+kOtivDYSJhgTYX7cIx1EJgmDhSvEbuK12XAiHyYCxThCKsQHmiXCMHa5+5T+BBY74qVX39vcUIhbLBwSkUO7DonBsEqBCtFfBjoHt6+34mjLfg34HO4feL8mw0HqwKsr8XRwA2E9x3xYY693jzHvWfZCAdheAZnWG6+1wecKd4oDhFHicPFvm6WI4SXRBTijOcdzi3+BefkifPAehLoI14Pqcdt/1x3YyNb/BisYUC38PvbU3wivOQhGHWw+sK5ASjAltBB8D8MQe05NXCBBZOBCmxJXwbTa1RnsDbCL4Pgx5ajeeJjYDVXEZgDNSugVwhW44j1sw0GKwjrX4El2kGzHDqVwzvO1x3d2dhMsPbMgjnaRZGroW0OjHcY1VIYeZHNMoZ0nPFP5YDwIJwp5miWinwt3u7w5z3g4lS1sc2c1Tl+Z0eTRXBkLKy1PWp7Ga4EAthSXAhHn4BQa5vX2zUIX1rRBKvU0fluYDaUt3F5xa5rNmzFlsfurs5grYP52JK7AVZcpBxSCrU3wvPOPtgzuxsI1gG3l9/90Cfg6Ihm/ONRBssn3udyceNVftiBLXNnaAZHbxcLNZs6uPXMFJdHCF84OeICluIIfQTzHI3jrI0Q7Kwc4myaxEqDtR2VC9bCCMDvOliZ0NGCCv1pwb7XlEdmQCtgOVp2bYU69aojWB/AVUEIoWcpfNlMeQD4q/0bs/GL6IK1JR3Wd/T4/lxZDJloKTgMrzXzFqyDJfBNT+WB2dD2kO1Eb9AP/zrhAAQ+xhl/d7A6fm0U977qbvPzr3YYwlkqArnQ2f4swb8SZjZXDtkG3SxsWSZ6HRt9RJ+rYP0H/omWdVNUlGyDxpm2b/i4P1VHsPbYnhrl7IQ/NlNRAPzDfqnH0o4eg1UEEy+N8mkl3e1P378e4CFYFnzRR0VBVzgP26BumqyqANQX92JPhm5FOXC2uJXoKBR7KA3AQuzZLzaMcJbwqv2pfXFL5YKV8BFaCvfCH1uqKDgKA0NOgzUAGuRp7664eaUyxD3Qpkz7u0oWxDtY/aBhFhzUD7hBN6goeRVq7IRVNkEY4y1YRx5VBiiHV9ByaK77YOW+pwxQCn9DS/4W2Fy7SgCuEEPYM1ETkXriSsxwRLxQe7RhjyV2VVW4E+ofhj1oSe2rXDAaGhTBAbQM72vo6VDzHQXrX9r7nxeXwqrLYWcNoFa0hj8F7rQiTuAV5cHIFvEM1jvQ3UJH+QJliN9Dj5B2APkWuw/Wrm3QrJ6hZyC20q/C9qfDsgbOg1WYDxe2NbRtdfTfZbXKIbW9x8ns30QISA1xEmbZLDbRzJEdwp6hqgqvwVWWdt9avVy5ZDL8Wj9VstbYwUwpdALKbIP1HxhJRAJlwPfhHWWzATeJafpz9bd/H89grYBRaHn+RmWQn2CdZtsyYWhjd8HaPVQZZKl2HV7AgvEXOw9W1pvKIDNhGFrevbVKACY6vPLWSHN0NUp8ThxnyJfECzRHWSlermaugfvRcuQu5ZLv4F60pN+lDLIZUmyDtRRmklAUPBzPYK2GT3Vf4IX+jZRBPoYniUioHA62dx6sQAVkt1MGSYXeFjoC3ZwFKyRm/14Z5Bu4OKidZwsOqjL4P8eeZSqBAMZgz0JVhQ3wOhEpLoLxZyqXHIAJRKS0CF48UxnkWehnG6wj8BUJxfK3VZQUuAiW/ntRwa+UYR6BP+gH+f6eLoK1CQprKoMchIv82itNGfc5DFY+FLUyvG0NS2E/Ecl8qcrg/w573kuwYP0Re1JVFd7RfvAWbYIU1/vJ2/AhETm6HlJqKIOshA4hKNMGK5RwwZrygYFHqC9zGqygNlgrP4vBjQW7hcCKHKy9tzh/kGrqd8ow+6CVX3tPovShKkw2PKuJx2G4tKEyyF6o4YPvicjhN1UlHD5TcGKCBetG7Nkq/ixA+doPNv9i5YFp2iuE1mJlmBxoEbR7kKqVcMGaOU1FyTpYq7tbg/NgbZ+tDLMLrrcJVi9ViTLtKfsq48E6CK392p0m/e8qTDE8RUQOHobmRoN1AGqWQZomWFMq33FE/C/2vJBgwboee/aKdVzMfXk6U3hLGyyMBysXWga1i5jTBqmchAvWY31VlGRqbyrmn64qka8NVsj4/MZg6IPgNFj7tV9iLfsRVtdSBsmATgEIaoJ/mwqzEwYTkWABbDhLGSQdGpdp12OVPF7lmQO7HU1gJxDAI9izs+oRVlAfrH8rDyzQXoAp3gxv1zJ8UaVTSDtHec9f1bqEmXQPiuUfwjdR/Se8AG0sOEpEUidVmax8n4iU58OKFoaPsJ5xE6x/wwT9pPuTRifdv4K79Nv3QZdK8yb90PLazcogH8HlIQgQkfl3ewjWpwn2yLH92JOmhFgHqwSGE5EyH7x/vjJICtyjX362s6f6Cf6hX9tSOB8Q+SJGfimmQPogZYCXoD9avh1WZYAORcvwfsogpbDeTbBmwW1oKR6mDJIGs4iIvxAeOLPSpGxn/dGYb6rhJRdPod2jn7nGQ7DWaQJSQzxLbC22iqFtxDvE7ThjRTyCNQ1uRkvqYGWQQzBH/yV3+R7zFOhhab8u8smt6iThN1AjB1brd+rnuqhKvAFX6hdz/rgBLqqpDJAHPS2w3ARrIpzth2IiEtgBM+spA2RBB/33unZ9qyrxDNTLg52a11MAS842dDrYRH/XhfxDMLyhh2AViK0jBKum+K5YKObE0CLc8WE8gvUStC7XbtuWrXBeHWWA7tDJp933dq+F8TXVHmgS1M4LFByE/q3VScB826OrwG7IqqsqcQvU3Q+70OJ7SEVJO6iVARsQHAcrTBosR8vrI5UBNsBctBQ+raowD6ag5ev3lQFmw3i0FM9SgiZYOiJ+KHMs4iUkFk+aC5aeLbAULYERygBB+BIt+8ZV3lHfQ8uRRaDqqASmF7Sxf8xV6SR1AjbBi+gphPIuUcb0aQQvwfoWBqDFKoEXuqooWAID0RLyw56OJ7j319WAhZZp/aOMVTegDC0ZfaII1sdKA9CPxOLWeAVrMgy0Hxu9ukS5NvEh+33v6Q6VT4uusCCElpnz4Ie6KgFZAWctgTS0lPjg2osiLDRtFwAfWoKHIfRrj/NCI0oAr8EqgAZ+2IOew7DkWuWBAPQvBT9ayuZo5uUWo8cH6z1FKwTXFkEOWio2Q1ndKIJVILa1XYGeGBSJbeIVrObQKBPS0VJ6ABZ7itY2eLAIO1JmqKqUwzzsWQyB9soFwGliQxUj/HCNs/tiH5hqM2hfxxarCPwPughBI80dEBwHK3wUOMDCjtKjbp5GlAn1vodxgIWeoO6hDGvhmqDtzygXA0/BN3VcPF/yAYfzO3crQRcsE+uxgIfEUqqXVUowGyw9I2EY9uRCxV0uls803AwTsacC3uisqjIEOgMV2GLlAeMgdK7NbW47Aq+Ke8UD4jsw4yyDj9ruIE6yoAxbyo/AjjOVhnehVQFk4ojQIgjeDNSNsGO3FP9mwVYEE8FqAbUOwwoccWAh+HuCVS/C9jUNwZ+BNByx9A0Hd4+ciiOOpELx3WBFumNB/RD0smARjti5FPrVNBCsQvECB9G6SvwaZ/xX/F/xf8S5YojoGRXvYM2C+j7nT8uZC9ZvI42NhdA8AAOALTgi5TkVic/gSRxjFQDzxKFiH7G7eJv4WAhWBqCc49i/Gya2US7hWPzuFf8iTrBgGVCOY4r/5HCupF8AV2wDazrwiDhEfEFcKGYRxkiwwlRAeyAb5+wQPxFHioPEseLn4gEcU7gdLm3q4PubTVw+ay9DTBGfCm/bKPFTcSfOyYe+FyrBQLAQPnfzyHdxSvg1F4hlYrGYHn5d94hNTnC/qxK8Uya2Mx8se0rhCsCHc7aCVXnfG2fB/Ao4hGPSV8O59VQkfgs1N8IiYkrBK8oFW+E5wIdndkx2eVXkeYxgPljh7bs1CCHigr8Ull7u4r26pBgKiBtb7lCCqWCFeVi5AKgtthE7iL8QGykNwGi8s0AJcQ9WmOnwIHEjlAFp5yg7noNmQCoxo2yNizmWoUG8Yon73oOzayiXAG8mZLDC/BkGlIBFTMkvg1v6eFhPc31mzKNVIf59hBJiECy/2FvFCOCcKObBesQjWNV/8aFiPxR0cbNRrcVVxITgauWAR6H+IdiNZxZPinJdyKSEDFaY5dAXyCY2HIJPeyqPvAlXANuJDT74/n4leA6WPUfFXjEKVjMxD/css/m5c7TBMsgmGBGI2VG+9T0UtvdyI7cme+EzjPPhROWACmiJp0/q8lzY/RdlgFUwDCjGGFZYXbDSb3Fx4aGzH77DKBmrIPdCFSUcWyU9B6Pk7ICMbkowFiz9fNFAZRjgGjGIOwLi1UrDBv0R1hJlmCLoFYIMs2Mj8D74m6poAIZbkGtmg/gIbmumHLID5uOK0GzIb294B7skCAssoiYLsh6FdWIk/D7Ycp7LZ7vV2Qejifo9CmYBo+Hb2oY/jQcC+4iKUAnwCmxvrgTjwdLzttjS0L7U3uOjwqYoG3ZqF19WvKVigA/aBGEaUEFUWFsh+CeTa50uCMAUC3LwRioEb1MueRw62a/DsXLE9yH4GxVD/HCjBfPEYlxxdDsUjgPrbCXMprTF/oiPnip9QnnEgnND8KwFu3DHdigfC4vaqhgBNA3CwxZsxB37xFdhUSelQRssM+wVh4iNPb7+RhwLyhHcs8PJM/+OHLv1ziaOw5cNmReoGAL8KgRvBWC/5e4IZh2EBomnqVgQgrYWDBUXApkRlhaUillgLRVfFm8AaiqPAN3ENWJ+2ExxJVjTIHQXBM9UcSQI51twLzBNXMSxQOSIeeEdchNYC469dq6HH+qd4DYpvwA+FPeIeeF/86Ch9+g0C34HTBC/FLeK2eHfkyWmiZ+LT4rXhddqxYUQ1LLgGmCMOF/8QcwKb1tOeFsXhNcu3QyWLhDmg2XPLvFZ8UpRO8iAVuL14nPiTrxRLnZVDjkC54TveJAfdjEcuUzFia/h9FLoDbwifhV+3XmVxsZ6cQ5Yj0LoChVPkI0TO4q9wLoDuFW8TrxAbGr4d9WwoAVwhni6SiCA+mIzsXn4/6S2i3/bIPzvasdw++pW2r6mYi2VIAC1wtvUPLyNdZUGz8EyTyj8s78KnzK+LL4kviPOE9eJeUTP/R5f+xlgnZEIY8OC5pXGRg2VJEkSTbBOXsaoJEmSJIN1EvC0SvJ/7dSxigEAHIDxf5FisAilbNbbWGSi5OZ7BgZPcS/hAZTNwFtYSVltit2iU/c9gxv8u75ffa/wSQ4ruR+ahySHldyZJiHJYSW3pnZIcliJHekrJDmsxMM60JQqIUkJh3WhJX1SKSTpzcN60J1udKQNfdOIaiFJLwxrTwua0Zj61PtjXfqgDtWpEJL04rCetKKBM5GUeVgnGoYkJR1Wka60o0ZIUuJhVWlLzZCk5MMqUyv0r/wCSDD/4sxS1q8AAAAASUVORK5CYII=">>.
index 132f1ee729037d67abb554dad75b69166d7814e4,d5a623669970258362496204b6ba209fe8c5237e..391a3ba74f41d56ba791375777113a163876aee9
@@@ -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 make_my_disco_hash(MyHost) of
+     case gen_mod:is_loaded(MyHost, ?MODULE) of
+       true ->
-       <<"">> -> Acc;
 +    case make_my_disco_hash(MyHost) of
 -              Hash ->
+               <<"">> ->
+                   Acc;
-         [#caps{hash = <<"sha-1">>, node = ?EJABBERD_URI, version = Hash}|Acc]
 +      Hash ->
+                   [#caps{hash = <<"sha-1">>, node = ?EJABBERD_URI,
+                          version = Hash}|Acc]
+           end;
+       false ->
+           Acc
      end.
  
  -spec disco_features({error, stanza_error()} | {result, [binary()]} | empty,
@@@ -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) ->
      [].
Simple merge
index aab89f0d6f5843f880334349cede104ced2d1d2c,175929a570ffd1ed2536923152390b482d68d3a3..d38de68321ad4e5475c322705972a99bbcc83c4f
@@@ -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;
Simple merge
index 2b5e0bfc55777e9a0ab7d0ee75d3fe6240e4c9c2,e8cc298166e3ca87311b05865b134b553681d2b3..2c6ff618c4cb7860ca2b70877e5734d11fc51ac6
@@@ -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
Simple merge
Simple merge
Simple merge
diff --cc src/mod_mam.erl
index 721b06f03ecf4fdaa7c07c60b22b2c2f47be5cd8,ca5930218bbde606cd961a66f991e261b27eca94..f55c1ccf2345a157706d0d7df49a4420eb859675
@@@ -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().
Simple merge
diff --cc src/mod_muc.erl
index dadc3e0f73e9fb41b5dddb03da1f3f69f896bcc5,e75fb3893d23d3e70032c314c8e923862aa1392d..a91fcc8102710faadea9ba7c1967e74c7914bc41
@@@ -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(
Simple merge
index f1089212eebe321faa904715aba07a2d9e314b59,2f5b319922c12d882e9b1cc2e6cafa4298b03bb3..700f7284e9e9aaa9b0ff380969a0636654546c5f
@@@ -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().
index 9c6ebf924801df92ed61e99c29c7855c3268c3bd,e73c31bcd24386ba6d4e5d442d9812c66ecd535f..9fdd1dce2be4f7f686f01b9e4350a09cf8291ec3
@@@ -161,6 -268,64 +284,62 @@@ import(_LServer, <<"muc_registered">>
        #muc_registered{us_host = {{U, S}, RoomHost},
                        nick = Nick}).
  
 -                                   record_info(fields, muc_registered)}]),
 -          update_tables(MyHost),
 -          mnesia:add_table_index(muc_registered, nick);
+ %%%===================================================================
+ %%% gen_server callbacks
+ %%%===================================================================
+ init([Host, Opts]) ->
+     MyHost = proplists:get_value(host, Opts),
+     case gen_mod:db_mod(Host, Opts, mod_muc) of
+       ?MODULE ->
+           ejabberd_mnesia:create(?MODULE, muc_room,
+                                  [{disc_copies, [node()]},
+                                   {attributes,
+                                    record_info(fields, muc_room)}]),
+           ejabberd_mnesia:create(?MODULE, muc_registered,
+                                  [{disc_copies, [node()]},
+                                   {attributes,
 -                                  {attributes,
 -                                   record_info(fields, muc_online_room)}]),
++                                   record_info(fields, muc_registered)},
++                                  {index, [nick]}]),
++          update_tables(MyHost);
+       _ ->
+           ok
+     end,
+     case gen_mod:ram_db_mod(Host, Opts, mod_muc) of
+       ?MODULE ->
+           update_muc_online_table(),
+           ejabberd_mnesia:create(?MODULE, muc_online_room,
+                                  [{ram_copies, [node()]},
+                                   {type, ordered_set},
 -          catch ets:new(muc_online_users,
 -                        [bag, named_table, public, {keypos, 2}]),
++                                  {attributes, record_info(fields, muc_online_room)}]),
+           mnesia:add_table_copy(muc_online_room, node(), ram_copies),
++          catch ets:new(muc_online_users, [bag, named_table, public, {keypos, 2}]),
+           clean_table_from_bad_node(node(), MyHost),
+           mnesia:subscribe(system);
+       _ ->
+           ok
+     end,
+     {ok, #state{}}.
+ handle_call(_Request, _From, State) ->
+     Reply = ok,
+     {reply, Reply, State}.
+ handle_cast(_Msg, State) ->
+     {noreply, State}.
+ handle_info({mnesia_system_event, {mnesia_down, Node}}, State) ->
+     clean_table_from_bad_node(Node),
+     {noreply, State};
+ handle_info(Info, State) ->
+     ?ERROR_MSG("unexpected info: ~p", [Info]),
+     {noreply, State}.
+ terminate(_Reason, _State) ->
+     ok.
+ code_change(_OldVsn, State, _Extra) ->
+     {ok, State}.
  %%%===================================================================
  %%% Internal functions
  %%%===================================================================
Simple merge
Simple merge
Simple merge
index 432214f2ed8609de6efaa91b09c10affc3de96b4,8443fde0cd75b8bf77a165737cf2509c9bf9c9c1..b34572ba84fb6fe1f649bf96b4d687825aac2e79
@@@ -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).
Simple merge
Simple merge
index 97de32eee842434ebeafaafa1e6a2cd740022948,6eb939c3c126d8fe643e297340766a4bdf923167..cfced6d06831630a97fed94dd9d659bd96f0b5c0
@@@ -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 <list/> element is allowed in this query">>,
-     {error, xmpp:err_bad_request(Txt, Lang)};
- process_iq_get(_, #iq{from = From, lang = Lang,
-                     sub_els = [#privacy_query{lists = Lists}]},
-              #userlist{name = Active}) ->
-     #jid{luser = LUser, lserver = LServer} = From,
+     xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang));
+ process_iq_get(#iq{lang = Lang,
+                  sub_els = [#privacy_query{lists = Lists}]} = IQ) ->
      case Lists of
        [] ->
-           process_lists_get(LUser, LServer, Active, Lang);
+           process_lists_get(IQ);
        [#privacy_list{name = ListName}] ->
-           process_list_get(LUser, LServer, ListName, Lang);
+           process_list_get(IQ, ListName);
        _ ->
            Txt = <<"Too many <list/> elements">>,
-           {error, xmpp:err_bad_request(Txt, Lang)}
+           xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang))
      end;
- process_iq_get(Acc, _, _) ->
-     Acc.
- -spec process_lists_get(binary(), binary(), binary(), binary()) ->
-                              {error, stanza_error()} | {result, privacy_query()}.
- process_lists_get(LUser, LServer, Active, Lang) ->
+ process_iq_get(#iq{lang = Lang} = IQ) ->
+     Txt = <<"No module is handling this query">>,
+     xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang)).
+ -spec process_lists_get(iq()) -> iq().
+ process_lists_get(#iq{from = #jid{luser = LUser, lserver = LServer},
+                     lang = Lang,
+                     meta = #{privacy_active_list := Active}} = IQ) ->
      Mod = gen_mod:db_mod(LServer, ?MODULE),
      case Mod:process_lists_get(LUser, LServer) of
        error ->
            Txt = <<"Database failure">>,
-           {error, xmpp:err_internal_server_error(Txt, Lang)};
+           xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang));
        {_Default, []} ->
-           {result, #privacy_query{}};
+           xmpp:make_iq_result(IQ, #privacy_query{});
        {Default, ListNames} ->
-           {result,
+           xmpp:make_iq_result(
+             IQ,
 -            #privacy_query{active = Active,
 -                           default = Default,
 -                           lists = [#privacy_list{name = ListName}
 +           #privacy_query{active = Active,
 +                          default = Default,
 +                          lists = [#privacy_list{name = ListName}
-                                    || ListName <- ListNames]}}
+                                     || ListName <- ListNames]})
      end.
  
- -spec process_list_get(binary(), binary(), binary(), binary()) ->
-                             {error, stanza_error()} | {result, privacy_query()}.
process_list_get(LUser, LServer, Name, Lang) ->
+ -spec process_list_get(iq(), binary()) -> iq().
+ process_list_get(#iq{from = #jid{luser = LUser, lserver = LServer},
                   lang = Lang} = IQ, Name) ->
      Mod = gen_mod:db_mod(LServer, ?MODULE),
      case Mod:process_list_get(LUser, LServer, Name) of
        error ->
            Txt = <<"Database failure">>,
-           {error, xmpp:err_internal_server_error(Txt, Lang)};
+           xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang));
        not_found ->
            Txt = <<"No privacy list with this name found">>,
-           {error, xmpp:err_item_not_found(Txt, Lang)};
+           xmpp:make_error(IQ, xmpp:err_item_not_found(Txt, Lang));
        Items ->
            LItems = lists:map(fun encode_list_item/1, Items),
-           {result,
+           xmpp:make_iq_result(
+             IQ,
 -            #privacy_query{
 +           #privacy_query{
-               lists = [#privacy_list{name = Name, items = LItems}]}}
+                lists = [#privacy_list{name = Name, items = LItems}]})
      end.
  
  -spec item_to_xml(listitem()) -> xmlel().
@@@ -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,
-         {Subscription, Groups} = case NeedDb of
 +        LJID = case Dir of
 +                 in -> jid:tolower(From);
 +                 out -> jid:tolower(To)
 +               end,
 -                  true ->
 -                      ejabberd_hooks:run_fold(roster_get_jid_info,
+           {Subscription, Groups} =
+               case NeedDb of
-                                                                jid:nameprep(Server),
 +                                   true ->
 +                                       ejabberd_hooks:run_fold(roster_get_jid_info,
 -                                              {none, []},
+                                               LServer,
-                                                                [User, Server,
-                                                                 LJID]);
-                                    false -> {[], []}
 +                                                               {none, []},
 -              end,
+                                               [LUser, LServer, LJID]);
+                   false ->
+                       {[], []}
-         check_packet_aux(List, PType2, LJID, Subscription,
-                          Groups)
-     end.
 +                                 end,
+           check_packet_aux(List, PType2, LJID, Subscription, Groups)
+     end;
+ check_packet(Acc, #jid{luser = LUser, lserver = LServer} = JID, Packet, Dir) ->
+     List = get_user_list(LUser, LServer),
+     check_packet(Acc, #{jid => JID, privacy_list => List}, Packet, Dir).
  
  -spec check_packet_aux([listitem()],
                       message | iq | presence_in | presence_out | other,
Simple merge
Simple merge
Simple merge
Simple merge
index 108c0b59360d85895a3ab4efe0c6b3ff57fbcae1,527c010a0ba0b2bee051d6eebb9d268dd8fb3f65..eba2cab291e35666543362140d6216379cfabebc
@@@ -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, <<Node/binary, "+notify">>},
+           C2SPid ! {send_filtered, {pep_message, <<Node/binary, "+notify">>},
 -                    service_jid(From), jid:make(To),
 +              service_jid(From), jid:make(To),
-               Stanza)
+                     Stanza}
      end;
  dispatch_items(From, To, _Node, Stanza) ->
      ejabberd_router:route(service_jid(From), jid:make(To), Stanza).
@@@ -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).
  
Simple merge
index 792bc1d4337c08e90e0ae36c220630ede259fde8,c22d02bb6f1793d5db146faf26c1e0557128bd04..44649631afce18704c95088242edbbe20679bfda
@@@ -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 roster_versioning_enabled(Host) of
 -              true ->
 -                  [#rosterver_feature{}|Acc];
+     case gen_mod:is_loaded(Host, ?MODULE) of
+       true ->
-       false -> []
 +    case roster_versioning_enabled(Host) of
 +      true ->
 +        [#rosterver_feature{}|Acc];
+               false ->
+                   Acc
+           end;
+       false ->
+           Acc
      end.
  
  roster_version(LServer, LUser) ->
@@@ -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.
  
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  
Simple merge
index 8400823f4366a227a8f816a1f1d3e06214decb64,7839543178fb9d297289a1eaa415fe4ef026cd2b..077f9bfab37dbdbbc12e75f09fa73db8ee6deaa1
@@@ -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()]}.
index 7ebceb9b39a1ee89532046cc21e19828f3ff87de,777854b8ef23aef99806ca34429103d283b6b734..e79bcc5c0d6a895e3527ee8053ece140fd92275e
@@@ -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()]}.
Simple merge
diff --cc src/scram.erl
Simple merge