From 63bb3f29fb35bbc2a100a3515f43eac2027b95e6 Mon Sep 17 00:00:00 2001 From: Nelson Vides Date: Mon, 18 Sep 2023 13:07:33 +0200 Subject: [PATCH] Set from field from c2s stream-start --- big_tests/tests/mim_c2s_SUITE.erl | 45 ++++++++++++++++++++++++++++++- src/c2s/mongoose_c2s.erl | 43 ++++++++++++++++++++++------- src/c2s/mongoose_c2s_stanzas.erl | 4 ++- 3 files changed, 80 insertions(+), 12 deletions(-) diff --git a/big_tests/tests/mim_c2s_SUITE.erl b/big_tests/tests/mim_c2s_SUITE.erl index 104c1e47ebb..2f846477002 100644 --- a/big_tests/tests/mim_c2s_SUITE.erl +++ b/big_tests/tests/mim_c2s_SUITE.erl @@ -4,7 +4,7 @@ -include_lib("common_test/include/ct.hrl"). -include_lib("eunit/include/eunit.hrl"). --include_lib("exml/include/exml.hrl"). +-include_lib("exml/include/exml_stream.hrl"). -include_lib("escalus/include/escalus.hrl"). -include_lib("escalus/include/escalus_xmlns.hrl"). -define(BAD_RESOURCE, <<"\x{EFBB}"/utf8>>). @@ -25,6 +25,8 @@ groups() -> [ {basic, [parallel], [ + client_sets_stream_from_server_answers_with_to, + stream_from_does_not_match_sasl_jid_results_in_stream_error, two_users_can_log_and_chat, too_big_stanza_rejected, message_sent_to_malformed_jid_results_in_error, @@ -80,6 +82,33 @@ end_per_testcase(Name, Config) -> %%-------------------------------------------------------------------- %% tests %%-------------------------------------------------------------------- +client_sets_stream_from_server_answers_with_to(Config) -> + AliceSpec = escalus_fresh:create_fresh_user(Config, alice), + Alice = escalus_connection:connect(AliceSpec), + escalus_client:send(Alice, stream_start(Alice)), + [StreamStartAnswer, _StreamFeatures] = escalus_client:wait_for_stanzas(Alice, 2, 500), + #xmlstreamstart{name = <<"stream:stream">>, attrs = Attrs} = StreamStartAnswer, + FromClient = jid:from_binary(escalus_utils:get_jid(Alice)), + {_, FromServerBin} = lists:keyfind(<<"to">>, 1, Attrs), + FromServer = jid:from_binary(FromServerBin), + ?assert(jid:are_equal(FromClient, FromServer)), + escalus_connection:stop(Alice). + +stream_from_does_not_match_sasl_jid_results_in_stream_error(Config) -> + AliceSpec = escalus_fresh:create_fresh_user(Config, alice), + Alice = escalus_connection:connect(AliceSpec), + Server = escalus_utils:get_server(Alice), + escalus_client:send(Alice, stream_start(Server, <<"not_alice@", Server/binary>>)), + [_StreamStartAnswer, _StreamFeatures] = escalus_client:wait_for_stanzas(Alice, 2, 500), + try escalus_auth:auth_plain(Alice, AliceSpec) of + _ -> error(authentication_with_inconsistent_jid_succeeded) + catch + throw:{auth_failed, _User, AuthReply} -> + escalus:assert(is_stream_error, [<<"invalid-from">>, <<>>], AuthReply), + escalus:assert(is_stream_end, escalus_client:wait_for_stanza(Alice)), + true = escalus_connection:wait_for_close(Alice, timer:seconds(1)) + end. + two_users_can_log_and_chat(Config) -> AliceHost = escalus_users:get_server(Config, alice), HostType = domain_helper:domain_to_host_type(mim(), AliceHost), @@ -154,6 +183,20 @@ start_connection_maybe_get_session_feature(Config) -> %% helpers %%-------------------------------------------------------------------- +stream_start(Client) -> + Server = escalus_utils:get_server(Client), + From = escalus_utils:get_jid(Client), + stream_start(Server, From). + +stream_start(Server, From) -> + #xmlstreamstart{name = <<"stream:stream">>, + attrs = [{<<"to">>, Server}, + {<<"from">>, From}, + {<<"version">>, <<"1.0">>}, + {<<"xml:lang">>, <<"en">>}, + {<<"xmlns">>, <<"jabber:client">>}, + {<<"xmlns:stream">>, <<"http://etherx.jabber.org/streams">>}]}. + save_c2s_listener(Config) -> C2SPort = ct:get_config({hosts, mim, c2s_port}), [C2SListener] = mongoose_helper:get_listeners(mim(), #{port => C2SPort, module => mongoose_c2s_listener}), diff --git a/src/c2s/mongoose_c2s.erl b/src/c2s/mongoose_c2s.erl index 18263dacffa..953b957cf42 100644 --- a/src/c2s/mongoose_c2s.erl +++ b/src/c2s/mongoose_c2s.erl @@ -329,20 +329,21 @@ stop_if_unhandled(_, _, {ok, _Acc}, Reason) -> -spec handle_stream_start(data(), exml:element(), stream_state()) -> fsm_res(). handle_stream_start(S0, StreamStart, StreamState) -> Lang = get_xml_lang(StreamStart), + From = maybe_capture_from_jid_in_stream_start(StreamStart), LServer = jid:nameprep(exml_query:attr(StreamStart, <<"to">>, <<>>)), case {StreamState, exml_query:attr(StreamStart, <<"xmlns:stream">>, <<>>), exml_query:attr(StreamStart, <<"version">>, <<>>), mongoose_domain_api:get_domain_host_type(LServer)} of {stream_start, ?NS_STREAM, ?XMPP_VERSION, {ok, HostType}} -> - S = S0#c2s_data{host_type = HostType, lserver = LServer, lang = Lang}, + S = S0#c2s_data{host_type = HostType, lserver = LServer, jid = From, lang = Lang}, stream_start_features_before_auth(S); {authenticated, ?NS_STREAM, ?XMPP_VERSION, {ok, HostType}} -> S = S0#c2s_data{host_type = HostType, lserver = LServer, lang = Lang}, stream_start_features_after_auth(S); {_, ?NS_STREAM, _Pre1_0, {ok, HostType}} -> %% (http://xmpp.org/rfcs/rfc6120.html#streams-negotiation-features) - S = S0#c2s_data{host_type = HostType, lserver = LServer, lang = Lang}, + S = S0#c2s_data{host_type = HostType, lserver = LServer, jid = From, lang = Lang}, stream_start_error(S, mongoose_xmpp_errors:unsupported_version()); {_, ?NS_STREAM, _, {error, not_found}} -> stream_start_error(S0, mongoose_xmpp_errors:host_unknown()); @@ -350,6 +351,16 @@ handle_stream_start(S0, StreamStart, StreamState) -> stream_start_error(S0, mongoose_xmpp_errors:invalid_namespace()) end. +%% We conditionally set the jid field before authentication if it was provided by the client in the +%% stream-start stanza, as extensions might use this (carefully, as it hasn't been proved this is +%% the real identity of the client!). See RFC6120#section-4.7.1 +-spec maybe_capture_from_jid_in_stream_start(exml:element()) -> error | jid:jid(). +maybe_capture_from_jid_in_stream_start(StreamStart) -> + case jid:from_binary(exml_query:attr(StreamStart, <<"from">>)) of + error -> undefined; + Jid -> Jid + end. + %% As stated in BCP47, 4.4.1: %% Protocols or specifications that specify limited buffer sizes for %% language tags MUST allow for language tags of at least 35 characters. @@ -413,14 +424,26 @@ handle_sasl_step(StateData, {error, NewSaslAcc, Result}, Retries) -> handle_sasl_error(StateData, NewSaslAcc, Result, Retries). -spec handle_sasl_success(data(), mongoose_acc:t(), mongoose_c2s_sasl:success()) -> fsm_res(). -handle_sasl_success(StateData = #c2s_data{info = Info}, SaslAcc, - #{server_out := MaybeServerOut, jid := Jid, auth_module := AuthMod}) -> - StateData1 = StateData#c2s_data{streamid = new_stream_id(), jid = Jid, - info = maps:merge(Info, #{auth_module => AuthMod})}, - El = mongoose_c2s_stanzas:sasl_success_stanza(MaybeServerOut), - send_acc_from_server_jid(StateData1, SaslAcc, El), - ?LOG_INFO(#{what => auth_success, text => <<"Accepted SASL authentication">>, c2s_state => StateData1}), - {next_state, {wait_for_stream, authenticated}, StateData1, state_timeout(StateData1)}. +handle_sasl_success(StateData = #c2s_data{jid = MaybeInitialJid, info = Info}, SaslAcc, + #{server_out := MaybeServerOut, jid := SaslJid, auth_module := AuthMod}) -> + case verify_initial_jid(MaybeInitialJid, SaslJid) of + true -> + StateData1 = StateData#c2s_data{streamid = new_stream_id(), jid = SaslJid, + info = maps:merge(Info, #{auth_module => AuthMod})}, + El = mongoose_c2s_stanzas:sasl_success_stanza(MaybeServerOut), + send_acc_from_server_jid(StateData1, SaslAcc, El), + ?LOG_INFO(#{what => auth_success, text => <<"Accepted SASL authentication">>, c2s_state => StateData1}), + {next_state, {wait_for_stream, authenticated}, StateData1, state_timeout(StateData1)}; + false -> + c2s_stream_error(StateData, mongoose_xmpp_errors:invalid_from()) + end. + +%% 6.4.6. SASL Success: https://www.rfc-editor.org/rfc/rfc6120.html#section-6.4.6 +-spec verify_initial_jid(undefined | jid:jid(), jid:jid()) -> boolean(). +verify_initial_jid(undefined, _Jid) -> + true; +verify_initial_jid(InitialJid, SaslJid) -> + jid:are_bare_equal(InitialJid, SaslJid). -spec handle_sasl_continue(data(), mongoose_acc:t(), mongoose_c2s_sasl:continue(), retries()) -> fsm_res(). handle_sasl_continue(StateData, SaslAcc, #{server_out := ServerOut}, Retries) -> diff --git a/src/c2s/mongoose_c2s_stanzas.erl b/src/c2s/mongoose_c2s_stanzas.erl index df3e3fe78fd..12ffe63b8e2 100644 --- a/src/c2s/mongoose_c2s_stanzas.erl +++ b/src/c2s/mongoose_c2s_stanzas.erl @@ -21,12 +21,14 @@ stream_header(StateData) -> Lang = mongoose_c2s:get_lang(StateData), LServer = mongoose_c2s:get_lserver(StateData), StreamId = mongoose_c2s:get_stream_id(StateData), + MaybeFrom = [ {<<"to">>, jid:to_binary(Jid)} + || Jid <- [mongoose_c2s:get_jid(StateData)], Jid =/= undefined], Attrs = [{<<"xmlns">>, ?NS_CLIENT}, {<<"xmlns:stream">>, <<"http://etherx.jabber.org/streams">>}, {<<"id">>, StreamId}, {<<"from">>, LServer}, {<<"version">>, ?XMPP_VERSION}, - {<<"xml:lang">>, Lang}], + {<<"xml:lang">>, Lang} | MaybeFrom ], #xmlstreamstart{name = <<"stream:stream">>, attrs = Attrs}. -spec stream_features([exml:element() | exml:cdata()]) -> exml:element().