diff --git a/mix.exs b/mix.exs index fb505e8f8..8f996b678 100644 --- a/mix.exs +++ b/mix.exs @@ -114,7 +114,7 @@ defmodule Ejabberd.MixProject do {:p1_utils, "~> 1.0"}, {:pkix, "~> 1.0"}, {:stringprep, ">= 1.0.26"}, - {:xmpp, ">= 1.7.0"}, + {:xmpp, git: "https://github.com/processone/xmpp.git", ref: "b6c0d6310b03939aab4db7aebc0f5fe323dadc51", override: true}, {:yconf, "~> 1.0"}] ++ cond_deps() end diff --git a/mix.lock b/mix.lock index 05423248c..6f1517e69 100644 --- a/mix.lock +++ b/mix.lock @@ -30,6 +30,6 @@ "stringprep": {:hex, :stringprep, "1.0.29", "02f23e8c3a219a3dfe40a22e908bece3a2f68af0ff599ea8a7b714ecb21e62ee", [:rebar3], [{:p1_utils, "1.0.25", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "928eba304c3006eb1512110ebd7b87db163b00859a09375a1e4466152c6c462a"}, "stun": {:hex, :stun, "1.2.10", "53f8be69e14f9476dcaf1dfb626b9dad2380f3fba8faf2c30bdf74311cfdc008", [:rebar3], [{:fast_tls, "1.1.16", [hex: :fast_tls, repo: "hexpm", optional: false]}, {:p1_utils, "1.0.25", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "19d3eecbfcc6935f0880f8ef7e77ff373900c604092937a1acda166ae3fb40e9"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm", "1d1848c40487cdb0b30e8ed975e34e025860c02e419cb615d255849f3427439d"}, - "xmpp": {:hex, :xmpp, "1.7.0", "2c3034ed501c9ccb7a5e73a462a74e082b4cf9f485bb551732e56fb15106dac9", [:rebar3], [{:ezlib, "1.0.12", [hex: :ezlib, repo: "hexpm", optional: false]}, {:fast_tls, "1.1.16", [hex: :fast_tls, repo: "hexpm", optional: false]}, {:fast_xml, "1.1.49", [hex: :fast_xml, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:p1_utils, "1.0.25", [hex: :p1_utils, repo: "hexpm", optional: false]}, {:stringprep, "1.0.29", [hex: :stringprep, repo: "hexpm", optional: false]}], "hexpm", "821bc8e2c4b288c031aa95aca17f3b215ed9bd634b7448d0f273baf8f6a46243"}, + "xmpp": {:git, "https://github.com/processone/xmpp.git", "b6c0d6310b03939aab4db7aebc0f5fe323dadc51", [ref: "b6c0d6310b03939aab4db7aebc0f5fe323dadc51"]}, "yconf": {:hex, :yconf, "1.0.15", "e22998b3d7728270bdd06162a9515bd142b14fae8927cbdbd3ef639c32aa6f7a", [:rebar3], [{:fast_yaml, "1.0.36", [hex: :fast_yaml, repo: "hexpm", optional: false]}], "hexpm", "7ff2ab24d3c9833842716b9aaaa01a8f96641a7695cbb701b03445c4def01117"}, } diff --git a/rebar.config b/rebar.config index d296fa87f..8fd6b49bd 100644 --- a/rebar.config +++ b/rebar.config @@ -77,7 +77,7 @@ {stringprep, ".*", {git, "https://github.com/processone/stringprep", {tag, "1.0.29"}}}, {if_var_true, stun, {stun, ".*", {git, "https://github.com/processone/stun", {tag, "1.2.10"}}}}, - {xmpp, ".*", {git, "https://github.com/processone/xmpp", "307deb6065476bf102d9a8eeb594524eaed36c13"}}, + {xmpp, ".*", {git, "https://github.com/processone/xmpp", "b6c0d6310b03939aab4db7aebc0f5fe323dadc51"}}, {yconf, ".*", {git, "https://github.com/processone/yconf", {tag, "1.0.15"}}} ]}. diff --git a/src/ejabberd_c2s.erl b/src/ejabberd_c2s.erl index d7b3a3340..54c30659a 100644 --- a/src/ejabberd_c2s.erl +++ b/src/ejabberd_c2s.erl @@ -40,7 +40,9 @@ handle_stream_start/2, handle_stream_end/2, handle_unauthenticated_packet/2, handle_authenticated_packet/2, handle_auth_success/4, handle_auth_failure/4, handle_send/3, - handle_recv/3, handle_cdata/2, handle_unbinded_packet/2]). + handle_recv/3, handle_cdata/2, handle_unbinded_packet/2, + inline_stream_features/1, handle_sasl2_inline/2, + handle_sasl2_inline_post/3, handle_bind2_inline/2]). %% Hooks -export([handle_unexpected_cast/2, handle_unexpected_call/3, process_auth_result/3, reject_unauthenticated_packet/2, @@ -381,6 +383,9 @@ unauthenticated_stream_features(#{lserver := LServer}) -> authenticated_stream_features(#{lserver := LServer}) -> ejabberd_hooks:run_fold(c2s_post_auth_features, LServer, [], [LServer]). +inline_stream_features(#{lserver := LServer}) -> + ejabberd_hooks:run_fold(c2s_inline_features, LServer, {[], []}, [LServer]). + sasl_mechanisms(Mechs, #{lserver := LServer, stream_encrypted := Encrypted} = State) -> Type = ejabberd_auth:store_type(LServer), Mechs1 = ejabberd_option:disable_sasl_mechanisms(LServer), @@ -533,6 +538,18 @@ handle_cdata(Data, #{lserver := LServer} = State) -> ejabberd_hooks:run_fold(c2s_handle_cdata, LServer, State, [Data]). +handle_sasl2_inline(Els, #{lserver := LServer} = State) -> + ejabberd_hooks:run_fold(c2s_handle_sasl2_inline, LServer, + {State, Els, []}, []). + +handle_sasl2_inline_post(Els, Results, #{lserver := LServer} = State) -> + ejabberd_hooks:run_fold(c2s_handle_sasl2_inline_post, LServer, + State, [Els, Results]). + +handle_bind2_inline(Els, #{lserver := LServer} = State) -> + ejabberd_hooks:run_fold(c2s_handle_bind2_inline, LServer, + State, [Els]). + handle_recv(El, Pkt, #{lserver := LServer} = State) -> ejabberd_hooks:run_fold(c2s_handle_recv, LServer, State, [El, Pkt]). diff --git a/src/mod_offline.erl b/src/mod_offline.erl index d7de2ccd4..0725e9a85 100644 --- a/src/mod_offline.erl +++ b/src/mod_offline.erl @@ -61,7 +61,8 @@ c2s_copy_session/2, webadmin_page/3, webadmin_user/4, - webadmin_user_parse_query/5]). + webadmin_user_parse_query/5, + c2s_handle_bind2_inline/2]). -export([mod_opt_type/1, mod_options/1, mod_doc/0, depends/2]). @@ -130,6 +131,7 @@ start(Host, Opts) -> {hook, disco_info, get_info, 50}, {hook, c2s_handle_info, c2s_handle_info, 50}, {hook, c2s_copy_session, c2s_copy_session, 50}, + {hook, c2s_handle_bind2_inline, c2s_handle_bind2_inline, 50}, {hook, webadmin_page_host, webadmin_page, 50}, {hook, webadmin_user, webadmin_user, 50}, {hook, webadmin_user_parse_query, webadmin_user_parse_query, 50}, @@ -297,6 +299,10 @@ c2s_copy_session(State, #{resend_offline := Flag}) -> c2s_copy_session(State, _) -> State. +c2s_handle_bind2_inline(#{jid := #jid{luser = LUser, lserver = LServer}} = State, _Els) -> + delete_all_msgs(LUser, LServer), + State. + -spec handle_offline_query(iq()) -> iq(). handle_offline_query(#iq{from = #jid{luser = U1, lserver = S1}, to = #jid{luser = U2, lserver = S2}, diff --git a/src/mod_stream_mgmt.erl b/src/mod_stream_mgmt.erl index e87109a5f..370a1bb87 100644 --- a/src/mod_stream_mgmt.erl +++ b/src/mod_stream_mgmt.erl @@ -33,10 +33,15 @@ c2s_authenticated_packet/2, c2s_unauthenticated_packet/2, c2s_unbinded_packet/2, c2s_closed/2, c2s_terminated/2, c2s_handle_send/3, c2s_handle_info/2, c2s_handle_call/3, - c2s_handle_recv/3]). + c2s_handle_recv/3, c2s_inline_features/2, + c2s_handle_sasl2_inline/1, c2s_handle_sasl2_inline_post/3, + c2s_handle_bind2_inline/2]). %% adjust pending session timeout / access queue -export([get_resume_timeout/1, set_resume_timeout/2, queue_find/2]). +%% for sasl2 inline resume +-export([has_resume_data/2, post_resume_tasks/1]). + -include_lib("xmpp/include/xmpp.hrl"). -include("logger.hrl"). -include_lib("p1_utils/include/p1_queue.hrl"). @@ -65,6 +70,7 @@ start(_Host, Opts) -> init_cache(Opts), {ok, [{hook, c2s_stream_started, c2s_stream_started, 50}, {hook, c2s_post_auth_features, c2s_stream_features, 50}, + {hook, c2s_inline_features, c2s_inline_features, 50}, {hook, c2s_unauthenticated_packet, c2s_unauthenticated_packet, 50}, {hook, c2s_unbinded_packet, c2s_unbinded_packet, 50}, {hook, c2s_authenticated_packet, c2s_authenticated_packet, 50}, @@ -72,6 +78,9 @@ start(_Host, Opts) -> {hook, c2s_handle_recv, c2s_handle_recv, 50}, {hook, c2s_handle_info, c2s_handle_info, 50}, {hook, c2s_handle_call, c2s_handle_call, 50}, + {hook, c2s_handle_sasl2_inline, c2s_handle_sasl2_inline, 50}, + {hook, c2s_handle_sasl2_inline_post, c2s_handle_sasl2_inline_post, 50}, + {hook, c2s_handle_bind2_inline, c2s_handle_bind2_inline, 50}, {hook, c2s_closed, c2s_closed, 50}, {hook, c2s_terminated, c2s_terminated, 50}]}. @@ -112,6 +121,45 @@ c2s_stream_features(Acc, Host) -> Acc end. +c2s_inline_features({Sasl, Bind} = Acc, Host) -> + case gen_mod:is_loaded(Host, ?MODULE) of + true -> + {[#feature_sm{xmlns = ?NS_STREAM_MGMT_3} | Sasl], + [#feature_sm{xmlns = ?NS_STREAM_MGMT_3} | Bind]}; + false -> + Acc + end. + +c2s_handle_sasl2_inline({State, Els, Results} = Acc) -> + case lists:keytake(sm_resume, 1, Els) of + {value, Resume, Rest} -> + case has_resume_data(State, Resume) of + {ok, NewState, Resumed} -> + Rest2 = lists:keydelete(bind2_bind, 1, Rest), + {NewState, Rest2, [Resumed | Results]}; + {error, ResumeError} -> + {State, Els, [ResumeError | Results]} + end; + _ -> + Acc + end. + +c2s_handle_sasl2_inline_post(State, _Els, Results) -> + case lists:keyfind(sm_resumed, 1, Results) of + false -> + State; + _ -> + post_resume_tasks(State) + end. + +c2s_handle_bind2_inline(State, Els) -> + case lists:keyfind(sm_enable, 1, Els) of + #sm_enable{} = Pkt -> + negotiate_stream_mgmt(Pkt, State); + _ -> + State + end. + c2s_unauthenticated_packet(#{lang := Lang} = State, Pkt) when ?is_sm_packet(Pkt) -> %% XEP-0198 says: "For client-to-server connections, the client MUST NOT %% attempt to enable stream management until after it has completed Resource @@ -394,39 +442,48 @@ handle_a(State, #sm_a{h = H}) -> resend_rack(State1). -spec handle_resume(state(), sm_resume()) -> {ok, state()} | {error, state()}. -handle_resume(#{user := User, lserver := LServer, - lang := Lang, socket := Socket} = State, - #sm_resume{h = H, previd = PrevID, xmlns = Xmlns}) -> - R = case inherit_session_state(State, PrevID) of - {ok, InheritedState} -> - {ok, InheritedState, H}; - {error, Err, InH} -> - {error, #sm_failed{reason = 'item-not-found', - text = xmpp:mk_text(format_error(Err), Lang), - h = InH, xmlns = Xmlns}, Err}; - {error, Err} -> - {error, #sm_failed{reason = 'item-not-found', - text = xmpp:mk_text(format_error(Err), Lang), - xmlns = Xmlns}, Err} - end, - case R of - {ok, #{jid := JID} = ResumedState, NumHandled} -> - State1 = check_h_attribute(ResumedState, NumHandled), - #{mgmt_xmlns := AttrXmlns, mgmt_stanzas_in := AttrH} = State1, - State2 = send(State1, #sm_resumed{xmlns = AttrXmlns, - h = AttrH, - previd = PrevID}), - State3 = resend_unacked_stanzas(State2), - State4 = send(State3, #sm_r{xmlns = AttrXmlns}), - State5 = ejabberd_hooks:run_fold(c2s_session_resumed, LServer, State4, []), - ?INFO_MSG("(~ts) Resumed session for ~ts", - [xmpp_socket:pp(Socket), jid:encode(JID)]), - {ok, State5}; +handle_resume(#{user := User, lserver := LServer, lang := Lang} = State, + #sm_resume{} = Resume) -> + case has_resume_data(State, Resume) of + {ok, ResumedState, ResumedEl} -> + State2 = send(ResumedState, ResumedEl), + {ok, post_resume_tasks(State2)}; {error, El, Reason} -> log_resumption_error(User, LServer, Reason), {error, send(State, El)} end. +-spec has_resume_data(state(), sm_resume()) -> + {ok, state(), sm_resumed()} | {error, sm_failed(), error_reason()}. +has_resume_data(#{lang := Lang} = State, + #sm_resume{h = H, previd = PrevID, xmlns = Xmlns}) -> + case inherit_session_state(State, PrevID) of + {ok, InheritedState} -> + State1 = check_h_attribute(InheritedState, H), + #{mgmt_xmlns := AttrXmlns, mgmt_stanzas_in := AttrH} = State1, + {ok, InheritedState, #sm_resumed{xmlns = AttrXmlns, + h = AttrH, + previd = PrevID}}; + {error, Err, InH} -> + {error, #sm_failed{reason = 'item-not-found', + text = xmpp:mk_text(format_error(Err), Lang), + h = InH, xmlns = Xmlns}, Err}; + {error, Err} -> + {error, #sm_failed{reason = 'item-not-found', + text = xmpp:mk_text(format_error(Err), Lang), + xmlns = Xmlns}, Err} + end. + +-spec post_resume_tasks(state()) -> state(). +post_resume_tasks(#{lserver := LServer, socket := Socket, jid := JID, + mgmt_xmlns := AttrXmlns} = State) -> + State3 = resend_unacked_stanzas(State), + State4 = send(State3, #sm_r{xmlns = AttrXmlns}), + State5 = ejabberd_hooks:run_fold(c2s_session_resumed, LServer, State4, []), + ?INFO_MSG("(~ts) Resumed session for ~ts", + [xmpp_socket:pp(Socket), jid:encode(JID)]), + State5. + -spec transition_to_pending(state(), _) -> state(). transition_to_pending(#{mgmt_state := active, mod := Mod, mgmt_timeout := 0} = State, _Reason) ->