]> granicus.if.org Git - ejabberd/commitdiff
Add support for XEP-0368: SRV records for XMPP over TLS
authorEvgeniy Khramtsov <ekhramtsov@process-one.net>
Wed, 27 Sep 2017 09:03:05 +0000 (12:03 +0300)
committerEvgeniy Khramtsov <ekhramtsov@process-one.net>
Wed, 27 Sep 2017 09:03:05 +0000 (12:03 +0300)
Currently this is only supported for outgoing s2s connections.
For such connections ejabberd is now able to resolve SRV records
of type "_xmpps-server._tcp". Also, SNI and ALPN fields are set
during TLS handshake. No additional configuration is required.

rebar.config
src/xmpp_stream_out.erl

index 0744a61c47024a56e30afab01895cd9e2803037b..d9aaae5975838ed6a9b596874d433bf03710e31f 100644 (file)
@@ -22,7 +22,7 @@
                        {tag, {if_version_above, "17", "3.4.2", "3.2.1"}}}},
         {p1_utils, ".*", {git, "https://github.com/processone/p1_utils", {tag, "1.0.9"}}},
         {cache_tab, ".*", {git, "https://github.com/processone/cache_tab", {tag, "1.0.10"}}},
-        {fast_tls, ".*", {git, "https://github.com/processone/fast_tls", {tag, "1.0.15"}}},
+        {fast_tls, ".*", {git, "https://github.com/processone/fast_tls", "fc3ef32"}},
         {stringprep, ".*", {git, "https://github.com/processone/stringprep", {tag, "1.0.9"}}},
         {fast_xml, ".*", {git, "https://github.com/processone/fast_xml", {tag, "1.1.23"}}},
         {xmpp, ".*", {git, "https://github.com/processone/xmpp", "d98be4a3159"}},
index af5c67c6608bdb7f8d4f62842a8f604837dc071c..024fdf63bc24f9172242769372d9fe1e6b44a4bc 100644 (file)
 -type host_port() :: {inet:hostname(), inet:port_number()}.
 -type ip_port() :: {inet:ip_address(), inet:port_number()}.
 -type network_error() :: {error, inet:posix() | inet_res:res_error()}.
+-type tls_error_reason() :: inet:posix() | atom() | binary().
+-type socket_error_reason() :: inet:posix() | atom().
 -type stop_reason() :: {idna, bad_string} |
                       {dns, inet:posix() | inet_res:res_error()} |
                       {stream, reset | {in | out, stream_error()}} |
-                      {tls, inet:posix() | atom() | binary()} |
+                      {tls, tls_error_reason()} |
                       {pkix, binary()} |
                       {auth, atom() | binary() | string()} |
-                      {socket, inet:posix() | atom()} |
+                      {socket, socket_error_reason()} |
                       internal_failure.
 -export_type([state/0, stop_reason/0]).
 -callback init(list()) -> {ok, state()} | {error, term()} | ignore.
@@ -276,19 +278,20 @@ handle_cast(connect, #{remote_server := RemoteServer,
              process_stream_end({idna, bad_string}, State);
          ASCIIName ->
              case resolve(binary_to_list(ASCIIName), State) of
-                 {ok, AddrPorts} ->
-                     case connect(AddrPorts, State) of
+                 {{ok, AddrPorts}, Encrypted} ->
+                     case connect(AddrPorts, State, Encrypted) of
                          {ok, Socket, AddrPort} ->
                              SocketMonitor = SockMod:monitor(Socket),
                              State1 = State#{ip => AddrPort,
                                              socket => Socket,
+                                             stream_encrypted => Encrypted,
                                              socket_monitor => SocketMonitor},
                              State2 = State1#{stream_state => wait_for_stream},
                              send_header(State2);
-                         {error, Why} ->
-                             process_stream_end({socket, Why}, State)
+                         {error, {Class, Why}} ->
+                             process_stream_end({Class, Why}, State)
                      end;
-                 {error, Why} ->
+                 {{error, Why}, _} ->
                      process_stream_end({dns, Why}, State)
              end
       end);
@@ -578,11 +581,8 @@ process_sasl_mechanisms(Mechs, #{user := User, server := Server} = State) ->
     end.
 
 -spec process_starttls(state()) -> state().
-process_starttls(#{sockmod := SockMod, socket := Socket, mod := Mod} = State) ->
-    TLSOpts = try Mod:tls_options(State)
-             catch _:undef -> []
-             end,
-    case SockMod:starttls(Socket, [connect|TLSOpts]) of
+process_starttls(#{socket := Socket} = State) ->
+    case starttls(Socket, State) of
        {ok, TLSSocket} ->
            State1 = State#{socket => TLSSocket,
                            stream_id => new_id(),
@@ -770,6 +770,19 @@ close_socket(State) ->
     State#{stream_timeout => infinity,
           stream_state => disconnected}.
 
+-spec starttls(term(), state()) -> {ok, term()} | {error, tls_error_reason()}.
+starttls(Socket, #{sockmod := SockMod, mod := Mod,
+                  xmlns := NS, remote_server := RemoteServer} = State) ->
+    TLSOpts = try Mod:tls_options(State)
+             catch _:undef -> []
+             end,
+    SNI = idna_to_ascii(RemoteServer),
+    ALPN = case NS of
+              ?NS_SERVER -> <<"xmpp-server">>;
+              ?NS_CLIENT -> <<"xmpp-client">>
+          end,
+    SockMod:starttls(Socket, [connect, {sni, SNI}, {alpn, [ALPN]}|TLSOpts]).
+
 -spec select_lang(binary(), binary()) -> binary().
 select_lang(Lang, <<"">>) -> Lang;
 select_lang(_, Lang) -> Lang.
@@ -841,17 +854,17 @@ idna_to_ascii(Host) ->
        {error, _} -> ejabberd_idna:domain_utf8_to_ascii(Host)
     end.
 
--spec resolve(string(), state()) -> {ok, [ip_port()]} | network_error().
+-spec resolve(string(), state()) -> {{ok, [ip_port()]} | network_error(), boolean()}.
 resolve(Host, State) ->
     case srv_lookup(Host, State) of
-       {error, _Reason} ->
+       {{error, _Reason}, _} ->
            DefaultPort = get_default_port(State),
-           a_lookup([{Host, DefaultPort}], State);
-       {ok, HostPorts} ->
-           a_lookup(HostPorts, State)
+           {a_lookup([{Host, DefaultPort}], State), false};
+       {{ok, HostPorts}, TLS} ->
+           {a_lookup(HostPorts, State), TLS}
     end.
 
--spec srv_lookup(string(), state()) -> {ok, [host_port()]} | network_error().
+-spec srv_lookup(string(), state()) -> {{ok, [host_port()]} | network_error(), boolean()}.
 srv_lookup(_Host, #{xmlns := ?NS_COMPONENT}) ->
     %% Do not attempt to lookup SRV for component connections
     {error, nxdomain};
@@ -867,21 +880,35 @@ srv_lookup(Host, State) ->
                {error, _} ->
                    Timeout = get_dns_timeout(State),
                    Retries = get_dns_retries(State),
-                   srv_lookup(Host, Timeout, Retries)
+                   case is_starttls_available(State) of
+                       true ->
+                           case srv_lookup("_xmpps-server._tcp." ++ Host,
+                                           Timeout, Retries) of
+                               {error, _} ->
+                                   {srv_lookup("_xmpp-server._tcp." ++ Host,
+                                               Timeout, Retries),
+                                    false};
+                               {ok, Res} ->
+                                   {{ok, Res}, true}
+                           end;
+                       false ->
+                           {srv_lookup("_xmpp-server._tcp." ++ Host,
+                                       Timeout, Retries),
+                            false}
+                   end
            end
     end.
 
 -spec srv_lookup(string(), timeout(), integer()) ->
                        {ok, [host_port()]} | network_error().
-srv_lookup(_Host, _Timeout, Retries) when Retries < 1 ->
+srv_lookup(_SRVName, _Timeout, Retries) when Retries < 1 ->
     {error, timeout};
-srv_lookup(Host, Timeout, Retries) ->
-    SRVName = "_xmpp-server._tcp." ++ Host,
+srv_lookup(SRVName, Timeout, Retries) ->
     case inet_res:getbyname(SRVName, srv, Timeout) of
        {ok, HostEntry} ->
            host_entry_to_host_ports(HostEntry);
        {error, timeout} ->
-           srv_lookup(Host, Timeout, Retries - 1);
+           srv_lookup(SRVName, Timeout, Retries - 1);
        {error, _} = Err ->
            Err
     end.
@@ -971,10 +998,22 @@ host_entry_to_addr_ports(#hostent{h_addr_list = AddrList}, Port) ->
        _ -> {ok, AddrPorts}
     end.
 
--spec connect([ip_port()], state()) -> {ok, term(), ip_port()} | network_error().
-connect(AddrPorts, #{sockmod := SockMod} = State) ->
+-spec connect([ip_port()], state(), boolean()) -> {ok, term(), ip_port()} |
+                                                 {error, {socket, socket_error_reason()}} |
+                                                 {error, {tls, tls_error_reason()}}.
+connect(AddrPorts, #{sockmod := SockMod} = State, TLS) ->
     Timeout = get_connect_timeout(State),
-    connect(AddrPorts, SockMod, Timeout, {error, nxdomain}).
+    case connect(AddrPorts, SockMod, Timeout, {error, nxdomain}) of
+       {ok, Socket, AddrPort} when TLS ->
+           case starttls(Socket, State) of
+               {ok, TLSSocket} -> {ok, TLSSocket, AddrPort};
+               {error, Why} -> {error, {tls, Why}}
+           end;
+       {ok, _Socket, _AddrPort} = OK ->
+           OK;
+       {error, Why} ->
+           {error, {socket, Why}}
+    end.
 
 -spec connect([ip_port()], module(), timeout(), network_error()) ->
                     {ok, term(), ip_port()} | network_error().