diff --git a/src/ejabberd_c2s.erl b/src/ejabberd_c2s.erl index b09d41433..6238cdae5 100644 --- a/src/ejabberd_c2s.erl +++ b/src/ejabberd_c2s.erl @@ -317,10 +317,10 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) -> [{xmlelement, "mechanisms", [{"xmlns", ?NS_SASL}], Mechs}] ++ - ejabberd_hooks:run_fold( - c2s_stream_features, - Server, - [], [])}), + ejabberd_hooks:run_fold( + c2s_stream_features, + Server, + [], [Server])}), fsm_next_state(wait_for_feature_request, StateData#state{ server = Server, @@ -329,11 +329,20 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) -> _ -> case StateData#state.resource of "" -> - RosterVersioningFeature = ejabberd_hooks:run_fold(roster_get_versioning_feature, Server, [], [Server]), - StreamFeatures = [{xmlelement, "bind", - [{"xmlns", ?NS_BIND}], []}, - {xmlelement, "session", - [{"xmlns", ?NS_SESSION}], []} | RosterVersioningFeature], + RosterVersioningFeature = + ejabberd_hooks:run_fold( + roster_get_versioning_feature, + Server, [], [Server]), + StreamFeatures = + [{xmlelement, "bind", + [{"xmlns", ?NS_BIND}], []}, + {xmlelement, "session", + [{"xmlns", ?NS_SESSION}], []}] + ++ RosterVersioningFeature + ++ ejabberd_hooks:run_fold( + c2s_stream_features, + Server, + [], [Server]), send_element( StateData, {xmlelement, "stream:features", [], diff --git a/src/ejabberd_s2s_in.erl b/src/ejabberd_s2s_in.erl index f2213020f..9b378338c 100644 --- a/src/ejabberd_s2s_in.erl +++ b/src/ejabberd_s2s_in.erl @@ -177,8 +177,9 @@ init([{SockMod, Socket}, Opts]) -> wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) -> case {xml:get_attr_s("xmlns", Attrs), xml:get_attr_s("xmlns:db", Attrs), + xml:get_attr_s("to", Attrs), xml:get_attr_s("version", Attrs) == "1.0"} of - {"jabber:server", _, true} when + {"jabber:server", _, Server, true} when StateData#state.tls and (not StateData#state.authenticated) -> send_text(StateData, ?STREAM_HEADER(" version='1.0'")), SASL = @@ -212,15 +213,23 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) -> end, send_element(StateData, {xmlelement, "stream:features", [], - SASL ++ StartTLS}), + SASL ++ StartTLS ++ + ejabberd_hooks:run_fold( + s2s_stream_features, + Server, + [], [Server])}), {next_state, wait_for_feature_request, StateData}; - {"jabber:server", _, true} when + {"jabber:server", _, Server, true} when StateData#state.authenticated -> send_text(StateData, ?STREAM_HEADER(" version='1.0'")), send_element(StateData, - {xmlelement, "stream:features", [], []}), + {xmlelement, "stream:features", [], + ejabberd_hooks:run_fold( + s2s_stream_features, + Server, + [], [Server])}), {next_state, stream_established, StateData}; - {"jabber:server", "jabber:server:dialback", _} -> + {"jabber:server", "jabber:server:dialback", _Server, _} -> send_text(StateData, ?STREAM_HEADER("")), {next_state, stream_established, StateData}; _ -> diff --git a/src/mod_caps.erl b/src/mod_caps.erl index 9250c5c03..dc20a9ae3 100644 --- a/src/mod_caps.erl +++ b/src/mod_caps.erl @@ -33,6 +33,10 @@ -behaviour(gen_mod). -export([read_caps/1, + caps_stream_features/2, + disco_features/5, + disco_identity/5, + disco_info/5, get_features/1]). %% gen_mod callbacks @@ -56,7 +60,7 @@ -define(PROCNAME, ejabberd_mod_caps). --record(caps, {node, version, exts}). +-record(caps, {node, version, hash, exts}). -record(caps_features, {node_pair, features = []}). -record(state, {host}). @@ -115,8 +119,10 @@ read_caps([{xmlelement, "c", Attrs, _Els} | Tail], Result) -> ?NS_CAPS -> Node = xml:get_attr_s("node", Attrs), Version = xml:get_attr_s("ver", Attrs), + Hash = xml:get_attr_s("hash", Attrs), Exts = string:tokens(xml:get_attr_s("ext", Attrs), " "), - read_caps(Tail, #caps{node = Node, version = Version, exts = Exts}); + read_caps(Tail, #caps{node = Node, hash = Hash, + version = Version, exts = Exts}); _ -> read_caps(Tail, Result) end; @@ -152,6 +158,41 @@ user_send_packet(#jid{luser = User, lserver = Server} = From, user_send_packet(_From, _To, _Packet) -> ok. +caps_stream_features(Acc, MyHost) -> + case make_my_disco_hash(MyHost) of + "" -> + Acc; + Hash -> + [{xmlelement, "c", [{"xmlns", ?NS_CAPS}, + {"hash", "sha-1"}, + {"node", ?EJABBERD_URI}, + {"ver", Hash}], []} | Acc] + end. + +disco_features(_Acc, From, To, ?EJABBERD_URI ++ "#" ++ [_|_], Lang) -> + ejabberd_hooks:run_fold(disco_local_features, + To#jid.lserver, + empty, + [From, To, "", Lang]); +disco_features(Acc, _From, _To, _Node, _Lang) -> + Acc. + +disco_identity(_Acc, From, To, ?EJABBERD_URI ++ "#" ++ [_|_], Lang) -> + ejabberd_hooks:run_fold(disco_local_identity, + To#jid.lserver, + [], + [From, To, "", Lang]); +disco_identity(Acc, _From, _To, _Node, _Lang) -> + Acc. + +disco_info(_Acc, Host, Module, ?EJABBERD_URI ++ "#" ++ [_|_], Lang) -> + ejabberd_hooks:run_fold(disco_info, + Host, + [], + [Host, Module, "", Lang]); +disco_info(Acc, _Host, _Module, _Node, _Lang) -> + Acc. + %%==================================================================== %% gen_server callbacks %%==================================================================== @@ -163,6 +204,16 @@ init([Host, _Opts]) -> mnesia:add_table_copy(caps_features, node(), disc_copies), ejabberd_hooks:add(user_send_packet, Host, ?MODULE, user_send_packet, 75), + ejabberd_hooks:add(c2s_stream_features, Host, + ?MODULE, caps_stream_features, 75), + ejabberd_hooks:add(s2s_stream_features, Host, + ?MODULE, caps_stream_features, 75), + ejabberd_hooks:add(disco_local_features, Host, + ?MODULE, disco_features, 75), + ejabberd_hooks:add(disco_local_identity, Host, + ?MODULE, disco_identity, 75), + ejabberd_hooks:add(disco_info, Host, + ?MODULE, disco_info, 75), {ok, #state{host = Host}}. handle_call(stop, _From, State) -> @@ -180,6 +231,16 @@ terminate(_Reason, State) -> Host = State#state.host, ejabberd_hooks:delete(user_send_packet, Host, ?MODULE, user_send_packet, 75), + ejabberd_hooks:delete(c2s_stream_features, Host, + ?MODULE, caps_stream_features, 75), + ejabberd_hooks:delete(s2s_stream_features, Host, + ?MODULE, caps_stream_features, 75), + ejabberd_hooks:delete(disco_local_features, Host, + ?MODULE, disco_features, 75), + ejabberd_hooks:delete(disco_local_identity, Host, + ?MODULE, disco_identity, 75), + ejabberd_hooks:delete(disco_info, Host, + ?MODULE, disco_info, 75), ok. code_change(_OldVsn, State, _Extra) -> @@ -214,16 +275,28 @@ feature_request(_Host, _From, _Caps, []) -> feature_response(#iq{type = result, sub_el = [{xmlelement, _, _, Els}]}, Host, From, Caps, [SubNode | SubNodes]) -> - Features = lists:flatmap( - fun({xmlelement, "feature", FAttrs, _}) -> - [xml:get_attr_s("var", FAttrs)]; - (_) -> - [] - end, Els), BinaryNode = node_to_binary(Caps#caps.node, SubNode), - mnesia:dirty_write( - #caps_features{node_pair = BinaryNode, - features = features_to_binary(Features)}), + IsValid = case Caps#caps.hash of + "sha-1" -> + Caps#caps.version == make_disco_hash(Els, sha1); + "md5" -> + Caps#caps.version == make_disco_hash(Els, md5); + _ -> + true + end, + if IsValid -> + Features = lists:flatmap( + fun({xmlelement, "feature", FAttrs, _}) -> + [xml:get_attr_s("var", FAttrs)]; + (_) -> + [] + end, Els), + mnesia:dirty_write( + #caps_features{node_pair = BinaryNode, + features = features_to_binary(Features)}); + true -> + mnesia:dirty_write(#caps_features{node_pair = BinaryNode}) + end, feature_request(Host, From, Caps, SubNodes); feature_response(timeout, _Host, _From, _Caps, _SubNodes) -> ok; @@ -239,3 +312,102 @@ node_to_binary(Node, SubNode) -> features_to_binary(L) -> [list_to_binary(I) || I <- L]. binary_to_features(L) -> [binary_to_list(I) || I <- L]. + +make_my_disco_hash(Host) -> + JID = jlib:make_jid("", Host, ""), + case {ejabberd_hooks:run_fold(disco_local_features, + Host, + empty, + [JID, JID, "", ""]), + ejabberd_hooks:run_fold(disco_local_identity, + Host, + [], + [JID, JID, "", ""]), + ejabberd_hooks:run_fold(disco_info, + Host, + [], + [Host, undefined, "", ""])} of + {{result, Features}, Identities, Info} -> + Feats = lists:map( + fun({{Feat, _Host}}) -> + {xmlelement, "feature", [{"var", Feat}], []}; + (Feat) -> + {xmlelement, "feature", [{"var", Feat}], []} + end, Features), + make_disco_hash(Identities ++ Info ++ Feats, sha1); + _Err -> + "" + end. + +make_disco_hash(DiscoEls, Algo) when Algo == sha1; Algo == md5 -> + Concat = [concat_identities(DiscoEls), + concat_features(DiscoEls), + concat_info(DiscoEls)], + base64:encode_to_string( + if Algo == sha1 -> + crypto:sha(Concat); + Algo == md5 -> + crypto:md5(Concat) + end). + +concat_features(Els) -> + lists:usort( + lists:flatmap( + fun({xmlelement, "feature", Attrs, _}) -> + [[xml:get_attr_s("var", Attrs), $<]]; + (_) -> + [] + end, Els)). + +concat_identities(Els) -> + lists:sort( + lists:flatmap( + fun({xmlelement, "identity", Attrs, _}) -> + [[xml:get_attr_s("category", Attrs), $/, + xml:get_attr_s("type", Attrs), $/, + xml:get_attr_s("xml:lang", Attrs), $/, + xml:get_attr_s("name", Attrs), $<]]; + (_) -> + [] + end, Els)). + +concat_info(Els) -> + lists:sort( + lists:flatmap( + fun({xmlelement, "x", Attrs, Fields}) -> + case {xml:get_attr_s("xmlns", Attrs), + xml:get_attr_s("type", Attrs)} of + {?NS_XDATA, "result"} -> + [concat_xdata_fields(Fields)]; + _ -> + [] + end; + (_) -> + [] + end, Els)). + +concat_xdata_fields(Fields) -> + [Form, Res] = + lists:foldl( + fun({xmlelement, "field", Attrs, Els} = El, + [FormType, VarFields] = Acc) -> + case xml:get_attr_s("var", Attrs) of + "" -> + Acc; + "FORM_TYPE" -> + [xml:get_subtag_cdata(El, "value"), VarFields]; + Var -> + [FormType, + [[[Var, $<], + lists:sort( + lists:flatmap( + fun({xmlelement, "value", _, VEls}) -> + [[xml:get_cdata(VEls), $<]]; + (_) -> + [] + end, Els))] | VarFields]] + end; + (_, Acc) -> + Acc + end, ["", []], Fields), + [Form, $<, lists:sort(Res)]. diff --git a/src/mod_disco.erl b/src/mod_disco.erl index 43c3efc42..151bf0d9c 100644 --- a/src/mod_disco.erl +++ b/src/mod_disco.erl @@ -172,7 +172,7 @@ process_local_iq_info(From, To, #iq{type = Type, lang = Lang, [{"xmlns", ?NS_DISCO_INFO} | ANode], Identity ++ Info ++ - lists:map(fun feature_to_xml/1, Features) + features_to_xml(Features) }]}; {error, Error} -> IQ#iq{type = error, sub_el = [SubEl, Error]} @@ -209,10 +209,16 @@ get_local_features(Acc, _From, _To, _Node, _Lang) -> end. -feature_to_xml({{Feature, _Host}}) -> - feature_to_xml(Feature); -feature_to_xml(Feature) when is_list(Feature) -> - {xmlelement, "feature", [{"var", Feature}], []}. +features_to_xml(FeatureList) -> + %% Avoid duplicating features + [{xmlelement, "feature", [{"var", Feat}], []} || + Feat <- lists:usort( + lists:map( + fun({{Feature, _Host}}) -> + Feature; + (Feature) when is_list(Feature) -> + Feature + end, FeatureList))]. domain_to_xml({Domain}) -> {xmlelement, "item", [{"jid", Domain}], []}; @@ -358,7 +364,7 @@ process_sm_iq_info(From, To, #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) sub_el = [{xmlelement, "query", [{"xmlns", ?NS_DISCO_INFO} | ANode], Identity ++ - lists:map(fun feature_to_xml/1, Features) + features_to_xml(Features) }]}; {error, Error} -> IQ#iq{type = error, sub_el = [SubEl, Error]} @@ -403,7 +409,13 @@ get_user_resources(User, Server) -> %%% Support for: XEP-0157 Contact Addresses for XMPP Services -get_info(_A, Host, Module, Node, _Lang) when Node == [] -> +get_info(_A, Host, Mod, Node, _Lang) when Node == [] -> + Module = case Mod of + undefined -> + ?MODULE; + _ -> + Mod + end, Serverinfo_fields = get_fields_xml(Host, Module), [{xmlelement, "x", [{"xmlns", ?NS_XDATA}, {"type", "result"}], @@ -417,8 +429,8 @@ get_info(_A, Host, Module, Node, _Lang) when Node == [] -> ++ Serverinfo_fields }]; -get_info(_, _, _, _Node, _) -> - []. +get_info(Acc, _, _, _Node, _) -> + Acc. get_fields_xml(Host, Module) -> Fields = gen_mod:get_module_opt(Host, ?MODULE, server_info, []), diff --git a/src/mod_register.erl b/src/mod_register.erl index 2a6c7c20b..35fa1ea67 100644 --- a/src/mod_register.erl +++ b/src/mod_register.erl @@ -31,7 +31,7 @@ -export([start/2, stop/1, - stream_feature_register/1, + stream_feature_register/2, unauthenticated_iq_register/4, try_register/5, process_iq/3]). @@ -65,7 +65,7 @@ stop(Host) -> gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_REGISTER). -stream_feature_register(Acc) -> +stream_feature_register(Acc, _Host) -> [{xmlelement, "register", [{"xmlns", ?NS_FEATURE_IQREGISTER}], []} | Acc].