From 92ec42565e94be7ec23c65164716438d5fe14b34 Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Mon, 10 May 2010 16:00:30 +1000 Subject: [PATCH] full support for XEP-0115 v1.5 (EJAB-1223) (EJAB-1189) --- src/ejabberd_c2s.erl | 31 +++++--- src/ejabberd_s2s_in.erl | 19 +++-- src/mod_caps.erl | 161 ++++++++++++++++++++++++++++++++++++++++ src/mod_disco.erl | 43 ++++++----- src/mod_register.erl | 4 +- 5 files changed, 222 insertions(+), 36 deletions(-) diff --git a/src/ejabberd_c2s.erl b/src/ejabberd_c2s.erl index 6474e239c..1bb7dd950 100644 --- a/src/ejabberd_c2s.erl +++ b/src/ejabberd_c2s.erl @@ -323,9 +323,9 @@ wait_for_stream({xmlstreamstart, #xmlel{ns = NS} = Opening}, StateData) -> [] end, Other_Feats = ejabberd_hooks:run_fold( - c2s_stream_features, - ServerB, - [], []), + c2s_stream_features, + ServerB, + [], [ServerB]), send_element(StateData, exmpp_stream:features( TLSFeature ++ @@ -340,17 +340,26 @@ wait_for_stream({xmlstreamstart, #xmlel{ns = NS} = Opening}, StateData) -> _ -> case StateData#state.resource of undefined -> - RosterVersioningFeature = ejabberd_hooks:run_fold(roster_get_versioning_feature, ServerB, [], [ServerB]), + RosterVersioningFeature = + ejabberd_hooks:run_fold( + roster_get_versioning_feature, + ServerB, + [], [ServerB]), + Other_Feats = ejabberd_hooks:run_fold( + c2s_stream_features, + ServerB, + [], [ServerB]), send_element( StateData, - exmpp_stream:features([ - exmpp_server_binding:feature(), - exmpp_server_session:feature() - | RosterVersioningFeature])), + exmpp_stream:features( + [exmpp_server_binding:feature(), + exmpp_server_session:feature()] + ++ RosterVersioningFeature + ++ Other_Feats)), fsm_next_state(wait_for_bind, - StateData#state{ - server = ServerB, - lang = Lang}); + StateData#state{ + server = ServerB, + lang = Lang}); _ -> send_element( StateData, diff --git a/src/ejabberd_s2s_in.erl b/src/ejabberd_s2s_in.erl index 73f40ee5f..8a881b145 100644 --- a/src/ejabberd_s2s_in.erl +++ b/src/ejabberd_s2s_in.erl @@ -155,8 +155,9 @@ init([{SockMod, Socket}, Opts]) -> wait_for_stream({xmlstreamstart, Opening}, StateData) -> case {exmpp_stream:get_default_ns(Opening), exmpp_xml:is_ns_declared_here(Opening, ?NS_DIALBACK), + exmpp_stream:get_receiving_entity(Opening), exmpp_stream:get_version(Opening) == {1, 0}} of - {?NS_JABBER_SERVER, _, true} when + {?NS_JABBER_SERVER, _, Server, true} when StateData#state.tls and (not StateData#state.authenticated) -> Opening_Reply = exmpp_stream:opening_reply(Opening, StateData#state.streamid), @@ -188,17 +189,25 @@ wait_for_stream({xmlstreamstart, Opening}, StateData) -> true -> [exmpp_server_tls:feature()] end, - send_element(StateData, exmpp_stream:features(SASL ++ StartTLS)), + Features = SASL ++ StartTLS ++ ejabberd_hooks:run_fold( + c2s_stream_features, + Server, + [], [Server]), + send_element(StateData, exmpp_stream:features(Features)), {next_state, wait_for_feature_request, StateData}; - {?NS_JABBER_SERVER, _, true} when + {?NS_JABBER_SERVER, _, Server, true} when StateData#state.authenticated -> Opening_Reply = exmpp_stream:opening_reply(Opening, StateData#state.streamid), send_element(StateData, exmpp_stream:set_dialback_support(Opening_Reply)), - send_element(StateData, exmpp_stream:features([])), + Features = ejabberd_hooks:run_fold( + c2s_stream_features, + Server, + [], [Server]), + send_element(StateData, exmpp_stream:features(Features)), {next_state, stream_established, StateData}; - {?NS_JABBER_SERVER, true, _} -> + {?NS_JABBER_SERVER, true, _Server, _} -> Opening_Reply = exmpp_stream:opening_reply(Opening, StateData#state.streamid), send_element(StateData, diff --git a/src/mod_caps.erl b/src/mod_caps.erl index 6af93a6b3..90b51396b 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 @@ -146,6 +150,42 @@ user_send_packet(From, To, #xmlel{name = 'presence', attrs = Attrs, children = E user_send_packet(_From, _To, _Packet) -> ok. +caps_stream_features(Acc, MyHost) -> + case make_my_disco_hash(MyHost) of + "" -> + Acc; + Hash -> + [#xmlel{name = c, + ns = ?NS_CAPS, + attrs = [?XMLATTR(hash, "sha-1"), + ?XMLATTR(node, ?EJABBERD_URI), + ?XMLATTR(ver, Hash)]} | Acc] + end. + +disco_features(_Acc, From, To, <>, Lang) -> + ejabberd_hooks:run_fold(disco_local_features, + exmpp_jid:domain(To), + empty, + [From, To, <<>>, Lang]); +disco_features(Acc, _From, _To, _Node, _Lang) -> + Acc. + +disco_identity(_Acc, From, To, <>, Lang) -> + ejabberd_hooks:run_fold(disco_local_identity, + exmpp_jid:domain(To), + [], + [From, To, <<>>, Lang]); +disco_identity(Acc, _From, _To, _Node, _Lang) -> + Acc. + +disco_info(_Acc, Host, Module, <>, Lang) -> + ejabberd_hooks:run_fold(disco_info, + list_to_binary(Host), + [], + [Host, Module, <<>>, Lang]); +disco_info(Acc, _Host, _Module, _Node, _Lang) -> + Acc. + %%==================================================================== %% gen_server callbacks %%==================================================================== @@ -158,6 +198,16 @@ init([Host, _Opts]) -> mnesia:add_table_copy(caps_features, node(), disc_copies), HostB = list_to_binary(Host), ejabberd_hooks:add(user_send_packet, HostB, ?MODULE, user_send_packet, 75), + ejabberd_hooks:add(c2s_stream_features, HostB, + ?MODULE, caps_stream_features, 75), + ejabberd_hooks:add(s2s_stream_features, HostB, + ?MODULE, caps_stream_features, 75), + ejabberd_hooks:add(disco_local_features, HostB, + ?MODULE, disco_features, 75), + ejabberd_hooks:add(disco_local_identity, HostB, + ?MODULE, disco_identity, 75), + ejabberd_hooks:add(disco_info, HostB, + ?MODULE, disco_info, 75), {ok, #state{host = Host}}. handle_call(stop, _From, State) -> @@ -174,6 +224,16 @@ handle_info(_Info, State) -> terminate(_Reason, State) -> HostB = list_to_binary(State#state.host), ejabberd_hooks:delete(user_send_packet, HostB, ?MODULE, user_send_packet, 75), + ejabberd_hooks:delete(c2s_stream_features, HostB, + ?MODULE, caps_stream_features, 75), + ejabberd_hooks:delete(s2s_stream_features, HostB, + ?MODULE, caps_stream_features, 75), + ejabberd_hooks:delete(disco_local_features, HostB, + ?MODULE, disco_features, 75), + ejabberd_hooks:delete(disco_local_identity, HostB, + ?MODULE, disco_identity, 75), + ejabberd_hooks:delete(disco_info, HostB, + ?MODULE, disco_info, 75), ok. code_change(_OldVsn, State, _Extra) -> @@ -229,3 +289,104 @@ 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 = exmpp_jid:make(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}}) -> + #xmlel{name = feature, + attrs = [?XMLATTR(var, Feat)]}; + (Feat) -> + #xmlel{name = feature, + attrs = [?XMLATTR(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(#xmlel{name = feature} = El) -> + [[exmpp_xml:get_attribute(El, var, <<>>), $<]]; + (_) -> + [] + end, Els)). + +concat_identities(Els) -> + lists:sort( + lists:flatmap( + fun(#xmlel{name = identity} = El) -> + [[exmpp_xml:get_attribute_as_binary(El, category, <<>>), $/, + exmpp_xml:get_attribute_as_binary(El, type, <<>>), $/, + exmpp_xml:get_attribute_as_binary(El, lang, <<>>), $/, + exmpp_xml:get_attribute_as_binary(El, name, <<>>), $<]]; + (_) -> + [] + end, Els)). + +concat_info(Els) -> + lists:sort( + lists:flatmap( + fun(#xmlel{name = x, ns = ?NS_DATA_FORMS, children = Fields} = El) -> + case exmpp_xml:get_attribute_as_list(El, 'type', "") of + "result" -> + [concat_xdata_fields(Fields)]; + _ -> + [] + end; + (_) -> + [] + end, Els)). + +concat_xdata_fields(Fields) -> + [Form, Res] = + lists:foldl( + fun(#xmlel{name = field, children = Els} = El, + [FormType, VarFields] = Acc) -> + case exmpp_xml:get_attribute_as_binary(El, var, <<>>) of + <<>> -> + Acc; + <<"FORM_TYPE">> -> + [exmpp_xml:get_path( + El, [{element, value}, cdata]), VarFields]; + Var -> + [FormType, + [[[Var, $<], + lists:sort( + lists:flatmap( + fun(#xmlel{name = value} = VEl) -> + [[exmpp_xml:get_cdata(VEl), $<]]; + (_) -> + [] + 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 f97ce5dad..01a818866 100644 --- a/src/mod_disco.erl +++ b/src/mod_disco.erl @@ -164,9 +164,8 @@ process_local_iq_info(From, To, #iq{type = get, payload = SubEl, _ -> [?XMLATTR('node', Node)] end, Result = #xmlel{ns = ?NS_DISCO_INFO, name = 'query', - attrs = ANode, - children = Identity ++ Info ++ lists:map(fun feature_to_xml/1, - Features)}, + attrs = ANode, + children = Identity ++ Info ++ features_to_xml(Features)}, exmpp_iq:result(IQ_Rec, Result); {error, Error} -> exmpp_iq:error(IQ_Rec, Error) @@ -204,19 +203,24 @@ get_local_features(Acc, _From, _To, _Node, _Lang) -> {error, 'item-not-found'} end. - -feature_to_xml({{Feature, _Host}}) -> - feature_to_xml(Feature); - -feature_to_xml(Feature) when is_binary(Feature) -> - #xmlel{ns = ?NS_DISCO_INFO, name = 'feature', attrs = [ - ?XMLATTR('var', Feature) - ]}; +features_to_xml(FeatureList) -> + %% Avoid duplicating features + [#xmlel{ns = ?NS_DISCO_INFO, name = 'feature', + attrs = [?XMLATTR('var', Feat)]} || + Feat <- lists:usort( + lists:map( + fun({{Feature, _Host}}) -> + feature_to_xml(Feature); + (Feature) -> + feature_to_xml(Feature) + end, FeatureList))]. feature_to_xml(Feature) when is_list(Feature) -> feature_to_xml(list_to_binary(Feature)); feature_to_xml(Feature) when is_atom(Feature) -> - feature_to_xml(atom_to_list(Feature)). + feature_to_xml(atom_to_list(Feature)); +feature_to_xml(Feature) when is_binary(Feature) -> + Feature. domain_to_xml({Domain}) -> domain_to_xml(Domain); @@ -364,9 +368,8 @@ process_sm_iq_info(From, To, #iq{type = get, payload = SubEl, _ -> [?XMLATTR('node', Node)] end, Result = #xmlel{ns = ?NS_DISCO_INFO, name = 'query', - attrs = ANode, - children = Identity ++ lists:map(fun feature_to_xml/1, - Features)}, + attrs = ANode, + children = Identity ++ features_to_xml(Features)}, exmpp_iq:result(IQ_Rec, Result); {error, Error} -> exmpp_iq:error(IQ_Rec, Error) @@ -421,7 +424,11 @@ get_user_resources(JID) -> %%% Support for: XEP-0157 Contact Addresses for XMPP Services -get_info(Acc, Host, Module, Node, _Lang) when Node == <<>> -> +get_info(Acc, Host, Mod, Node, _Lang) when Node == <<>> -> + Module = case Mod of + undefined -> ?MODULE; + _ -> Mod + end, Serverinfo_fields = get_fields_xml(Host, Module), CData1 = #xmlcdata{cdata = list_to_binary(?NS_SERVERINFO_s)}, Value1 = #xmlel{name = 'value', children = [CData1]}, @@ -437,8 +444,8 @@ get_info(Acc, Host, Module, Node, _Lang) when Node == <<>> -> }, [X | Acc]; -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 324b7081f..d885e0904 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]). @@ -68,7 +68,7 @@ stop(Host) -> gen_iq_handler:remove_iq_handler(ejabberd_sm, HostB, ?NS_INBAND_REGISTER). -stream_feature_register(Acc) -> +stream_feature_register(Acc, _Host) -> [#xmlel{ns = ?NS_INBAND_REGISTER_FEAT, name = 'register'} | Acc]. unauthenticated_iq_register(_Acc,