aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEvgeniy Khramtsov <ekhramtsov@process-one.net>2010-05-08 02:32:57 +1000
committerEvgeniy Khramtsov <ekhramtsov@process-one.net>2010-05-08 02:32:57 +1000
commit58590cf08d3d448d61328ea3f5d249a1a6b80519 (patch)
tree36ab631de0a534a1dfeb3b5a837ecfb4116179d1
parentChange max inactivity from 30 to 120 seconds (diff)
full support for XEP-0115 v1.5 (EJAB-1223) (EJAB-1189)
-rw-r--r--src/ejabberd_c2s.erl27
-rw-r--r--src/ejabberd_s2s_in.erl19
-rw-r--r--src/mod_caps.erl194
-rw-r--r--src/mod_disco.erl30
-rw-r--r--src/mod_register.erl4
5 files changed, 238 insertions, 36 deletions
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].