mirror of
https://github.com/processone/ejabberd.git
synced 2024-09-27 14:30:55 +02:00
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.
This commit is contained in:
parent
368ba3fc55
commit
c17ec50e3a
@ -22,7 +22,7 @@
|
|||||||
{tag, {if_version_above, "17", "3.4.2", "3.2.1"}}}},
|
{tag, {if_version_above, "17", "3.4.2", "3.2.1"}}}},
|
||||||
{p1_utils, ".*", {git, "https://github.com/processone/p1_utils", {tag, "1.0.9"}}},
|
{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"}}},
|
{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"}}},
|
{stringprep, ".*", {git, "https://github.com/processone/stringprep", {tag, "1.0.9"}}},
|
||||||
{fast_xml, ".*", {git, "https://github.com/processone/fast_xml", {tag, "1.1.23"}}},
|
{fast_xml, ".*", {git, "https://github.com/processone/fast_xml", {tag, "1.1.23"}}},
|
||||||
{xmpp, ".*", {git, "https://github.com/processone/xmpp", "d98be4a3159"}},
|
{xmpp, ".*", {git, "https://github.com/processone/xmpp", "d98be4a3159"}},
|
||||||
|
@ -51,13 +51,15 @@
|
|||||||
-type host_port() :: {inet:hostname(), inet:port_number()}.
|
-type host_port() :: {inet:hostname(), inet:port_number()}.
|
||||||
-type ip_port() :: {inet:ip_address(), inet:port_number()}.
|
-type ip_port() :: {inet:ip_address(), inet:port_number()}.
|
||||||
-type network_error() :: {error, inet:posix() | inet_res:res_error()}.
|
-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} |
|
-type stop_reason() :: {idna, bad_string} |
|
||||||
{dns, inet:posix() | inet_res:res_error()} |
|
{dns, inet:posix() | inet_res:res_error()} |
|
||||||
{stream, reset | {in | out, stream_error()}} |
|
{stream, reset | {in | out, stream_error()}} |
|
||||||
{tls, inet:posix() | atom() | binary()} |
|
{tls, tls_error_reason()} |
|
||||||
{pkix, binary()} |
|
{pkix, binary()} |
|
||||||
{auth, atom() | binary() | string()} |
|
{auth, atom() | binary() | string()} |
|
||||||
{socket, inet:posix() | atom()} |
|
{socket, socket_error_reason()} |
|
||||||
internal_failure.
|
internal_failure.
|
||||||
-export_type([state/0, stop_reason/0]).
|
-export_type([state/0, stop_reason/0]).
|
||||||
-callback init(list()) -> {ok, state()} | {error, term()} | ignore.
|
-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);
|
process_stream_end({idna, bad_string}, State);
|
||||||
ASCIIName ->
|
ASCIIName ->
|
||||||
case resolve(binary_to_list(ASCIIName), State) of
|
case resolve(binary_to_list(ASCIIName), State) of
|
||||||
{ok, AddrPorts} ->
|
{{ok, AddrPorts}, Encrypted} ->
|
||||||
case connect(AddrPorts, State) of
|
case connect(AddrPorts, State, Encrypted) of
|
||||||
{ok, Socket, AddrPort} ->
|
{ok, Socket, AddrPort} ->
|
||||||
SocketMonitor = SockMod:monitor(Socket),
|
SocketMonitor = SockMod:monitor(Socket),
|
||||||
State1 = State#{ip => AddrPort,
|
State1 = State#{ip => AddrPort,
|
||||||
socket => Socket,
|
socket => Socket,
|
||||||
|
stream_encrypted => Encrypted,
|
||||||
socket_monitor => SocketMonitor},
|
socket_monitor => SocketMonitor},
|
||||||
State2 = State1#{stream_state => wait_for_stream},
|
State2 = State1#{stream_state => wait_for_stream},
|
||||||
send_header(State2);
|
send_header(State2);
|
||||||
{error, Why} ->
|
{error, {Class, Why}} ->
|
||||||
process_stream_end({socket, Why}, State)
|
process_stream_end({Class, Why}, State)
|
||||||
end;
|
end;
|
||||||
{error, Why} ->
|
{{error, Why}, _} ->
|
||||||
process_stream_end({dns, Why}, State)
|
process_stream_end({dns, Why}, State)
|
||||||
end
|
end
|
||||||
end);
|
end);
|
||||||
@ -578,11 +581,8 @@ process_sasl_mechanisms(Mechs, #{user := User, server := Server} = State) ->
|
|||||||
end.
|
end.
|
||||||
|
|
||||||
-spec process_starttls(state()) -> state().
|
-spec process_starttls(state()) -> state().
|
||||||
process_starttls(#{sockmod := SockMod, socket := Socket, mod := Mod} = State) ->
|
process_starttls(#{socket := Socket} = State) ->
|
||||||
TLSOpts = try Mod:tls_options(State)
|
case starttls(Socket, State) of
|
||||||
catch _:undef -> []
|
|
||||||
end,
|
|
||||||
case SockMod:starttls(Socket, [connect|TLSOpts]) of
|
|
||||||
{ok, TLSSocket} ->
|
{ok, TLSSocket} ->
|
||||||
State1 = State#{socket => TLSSocket,
|
State1 = State#{socket => TLSSocket,
|
||||||
stream_id => new_id(),
|
stream_id => new_id(),
|
||||||
@ -770,6 +770,19 @@ close_socket(State) ->
|
|||||||
State#{stream_timeout => infinity,
|
State#{stream_timeout => infinity,
|
||||||
stream_state => disconnected}.
|
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().
|
-spec select_lang(binary(), binary()) -> binary().
|
||||||
select_lang(Lang, <<"">>) -> Lang;
|
select_lang(Lang, <<"">>) -> Lang;
|
||||||
select_lang(_, Lang) -> Lang.
|
select_lang(_, Lang) -> Lang.
|
||||||
@ -841,17 +854,17 @@ idna_to_ascii(Host) ->
|
|||||||
{error, _} -> ejabberd_idna:domain_utf8_to_ascii(Host)
|
{error, _} -> ejabberd_idna:domain_utf8_to_ascii(Host)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec resolve(string(), state()) -> {ok, [ip_port()]} | network_error().
|
-spec resolve(string(), state()) -> {{ok, [ip_port()]} | network_error(), boolean()}.
|
||||||
resolve(Host, State) ->
|
resolve(Host, State) ->
|
||||||
case srv_lookup(Host, State) of
|
case srv_lookup(Host, State) of
|
||||||
{error, _Reason} ->
|
{{error, _Reason}, _} ->
|
||||||
DefaultPort = get_default_port(State),
|
DefaultPort = get_default_port(State),
|
||||||
a_lookup([{Host, DefaultPort}], State);
|
{a_lookup([{Host, DefaultPort}], State), false};
|
||||||
{ok, HostPorts} ->
|
{{ok, HostPorts}, TLS} ->
|
||||||
a_lookup(HostPorts, State)
|
{a_lookup(HostPorts, State), TLS}
|
||||||
end.
|
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}) ->
|
srv_lookup(_Host, #{xmlns := ?NS_COMPONENT}) ->
|
||||||
%% Do not attempt to lookup SRV for component connections
|
%% Do not attempt to lookup SRV for component connections
|
||||||
{error, nxdomain};
|
{error, nxdomain};
|
||||||
@ -867,21 +880,35 @@ srv_lookup(Host, State) ->
|
|||||||
{error, _} ->
|
{error, _} ->
|
||||||
Timeout = get_dns_timeout(State),
|
Timeout = get_dns_timeout(State),
|
||||||
Retries = get_dns_retries(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
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec srv_lookup(string(), timeout(), integer()) ->
|
-spec srv_lookup(string(), timeout(), integer()) ->
|
||||||
{ok, [host_port()]} | network_error().
|
{ok, [host_port()]} | network_error().
|
||||||
srv_lookup(_Host, _Timeout, Retries) when Retries < 1 ->
|
srv_lookup(_SRVName, _Timeout, Retries) when Retries < 1 ->
|
||||||
{error, timeout};
|
{error, timeout};
|
||||||
srv_lookup(Host, Timeout, Retries) ->
|
srv_lookup(SRVName, Timeout, Retries) ->
|
||||||
SRVName = "_xmpp-server._tcp." ++ Host,
|
|
||||||
case inet_res:getbyname(SRVName, srv, Timeout) of
|
case inet_res:getbyname(SRVName, srv, Timeout) of
|
||||||
{ok, HostEntry} ->
|
{ok, HostEntry} ->
|
||||||
host_entry_to_host_ports(HostEntry);
|
host_entry_to_host_ports(HostEntry);
|
||||||
{error, timeout} ->
|
{error, timeout} ->
|
||||||
srv_lookup(Host, Timeout, Retries - 1);
|
srv_lookup(SRVName, Timeout, Retries - 1);
|
||||||
{error, _} = Err ->
|
{error, _} = Err ->
|
||||||
Err
|
Err
|
||||||
end.
|
end.
|
||||||
@ -971,10 +998,22 @@ host_entry_to_addr_ports(#hostent{h_addr_list = AddrList}, Port) ->
|
|||||||
_ -> {ok, AddrPorts}
|
_ -> {ok, AddrPorts}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec connect([ip_port()], state()) -> {ok, term(), ip_port()} | network_error().
|
-spec connect([ip_port()], state(), boolean()) -> {ok, term(), ip_port()} |
|
||||||
connect(AddrPorts, #{sockmod := SockMod} = State) ->
|
{error, {socket, socket_error_reason()}} |
|
||||||
|
{error, {tls, tls_error_reason()}}.
|
||||||
|
connect(AddrPorts, #{sockmod := SockMod} = State, TLS) ->
|
||||||
Timeout = get_connect_timeout(State),
|
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()) ->
|
-spec connect([ip_port()], module(), timeout(), network_error()) ->
|
||||||
{ok, term(), ip_port()} | network_error().
|
{ok, term(), ip_port()} | network_error().
|
||||||
|
Loading…
Reference in New Issue
Block a user