From c17ec50e3a989e3d8ceb78f94ee2a7d5eec5f7d3 Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Wed, 27 Sep 2017 12:03:05 +0300 Subject: [PATCH] Add support for XEP-0368: SRV records for XMPP over TLS 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 | 2 +- src/xmpp_stream_out.erl | 91 +++++++++++++++++++++++++++++------------ 2 files changed, 66 insertions(+), 27 deletions(-) diff --git a/rebar.config b/rebar.config index 0744a61c4..d9aaae597 100644 --- a/rebar.config +++ b/rebar.config @@ -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"}}, diff --git a/src/xmpp_stream_out.erl b/src/xmpp_stream_out.erl index af5c67c66..024fdf63b 100644 --- a/src/xmpp_stream_out.erl +++ b/src/xmpp_stream_out.erl @@ -51,13 +51,15 @@ -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().