File diff 000000000000 → 000000000000
net-im/ejabberd/files/ejabberd-2.1.x-remote-roster.patch
Show inline comments
 
new file 100644
 
diff --git a/doc/guide.tex b/doc/guide.tex
 
index 044fab8..25598ca 100644
 
--- a/doc/guide.tex
 
+++ b/doc/guide.tex
 
@@ -3864,6 +3864,15 @@ It also supports Roster Versioning (\xepref{0237}).
 
 Options:
 
 \begin{description}
 
 \iqdiscitem{Roster Management (\ns{jabber:iq:roster})}
 
+  \titem{\{managers, [Domainname]\}} \ind{options!managers}
 
+  List of components (or servers) that can manage users rosters using
 
+  \footahref{http://jkaluza.fedorapeople.org/remote-roster.html}{XEP-xxxx: Remote Roster Management}.
 
+  The protocol sections implemented are:
 
+  \term{2.3. Component requests user's roster},
 
+  \term{2.4. Client sends roster update},
 
+  \term{2.5. Component sends roster update}.
 
+  A component only gets or can modify roster items that have the same domain as the component.
 
+  Default value is: \term{[]}.
 
   \titem{\{versioning, false|true\}} \ind{options!versioning}Enables
 
   Roster Versioning.
 
   This option is disabled by default.
 
@@ -3877,12 +3886,13 @@ Options:
 
   Important: if you use \modsharedroster, you must disable this option.
 
 \end{description}
 
 
 
-This example configuration enables Roster Versioning with storage of current id:
 
+This example configuration enables Roster Versioning with storage of current id.
 
+The ICQ and MSN transports can get ICQ and MSN contacts, add them, or remove them for any local account:
 
 \begin{verbatim}
 
 {modules,
 
  [
 
   ...
 
-  {mod_roster, [{versioning, true}, {store_current_id, true}]},
 
+  {mod_roster, [{versioning, true}, {store_current_id, true}, {["icq.example.org", "msn.example.org"]} ]},
 
   ...
 
  ]}.
 
 \end{verbatim}
 
diff --git a/src/mod_roster.erl b/src/mod_roster.erl
 
index 90c4601..f837ba4 100644
 
--- a/src/mod_roster.erl
 
+++ b/src/mod_roster.erl
 
@@ -123,6 +123,8 @@ stop(Host) ->
 
 			  ?MODULE, webadmin_user, 50),
 
     gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_ROSTER).
 
 
 
+process_iq(From, To, IQ) when ((From#jid.luser == "") andalso (From#jid.lresource == "")) ->
 
+    process_iq_manager(From, To, IQ);
 
 
 
 process_iq(From, To, IQ) ->
 
     #iq{sub_el = SubEl} = IQ,
 
@@ -281,12 +283,13 @@ item_to_xml(Item) ->
 
     {xmlelement, "item", Attrs4, SubEls}.
 
 
 
 
 
-process_iq_set(From, To, #iq{sub_el = SubEl} = IQ) ->
 
+process_iq_set(From, To, #iq{sub_el = SubEl, id = Id} = IQ) ->
 
     {xmlelement, _Name, _Attrs, Els} = SubEl,
 
-    lists:foreach(fun(El) -> process_item_set(From, To, El) end, Els),
 
+    Managed = is_managed_from_id(Id),
 
+    lists:foreach(fun(El) -> process_item_set(From, To, El, Managed) end, Els),
 
     IQ#iq{type = result, sub_el = []}.
 
 
 
-process_item_set(From, To, {xmlelement, _Name, Attrs, Els}) ->
 
+process_item_set(From, To, {xmlelement, _Name, Attrs, Els}, Managed) ->
 
     JID1 = jlib:string_to_jid(xml:get_attr_s("jid", Attrs)),
 
     #jid{user = User, luser = LUser, lserver = LServer} = From,
 
     case JID1 of
 
@@ -308,7 +311,8 @@ process_item_set(From, To, {xmlelement, _Name, Attrs, Els}) ->
 
 						groups = [],
 
 						xs = []}
 
 			       end,
 
-			Item1 = process_item_attrs(Item, Attrs),
 
+%			Item11 = process_item_attrs(Item, Attrs),
 
+			Item1 = process_item_attrs_managed(Item, Attrs, Managed),
 
 			Item2 = process_item_els(Item1, Els),
 
 			case Item2#roster.subscription of
 
 			    remove ->
 
@@ -316,6 +320,7 @@ process_item_set(From, To, {xmlelement, _Name, Attrs, Els}) ->
 
 			    _ ->
 
 				mnesia:write(Item2)
 
 			end,
 
+			send_itemset_to_managers(From, Item2, Managed),
 
 			%% If the item exist in shared roster, take the
 
 			%% subscription information from there:
 
 			Item3 = ejabberd_hooks:run_fold(roster_process_item,
 
@@ -341,7 +346,7 @@ process_item_set(From, To, {xmlelement, _Name, Attrs, Els}) ->
 
 		    ok
 
 	    end
 
     end;
 
-process_item_set(_From, _To, _) ->
 
+process_item_set(_From, _To, _, _Managed) ->
 
     ok.
 
 
 
 process_item_attrs(Item, [{Attr, Val} | Attrs]) ->
 
@@ -1098,3 +1103,89 @@ us_to_list({User, Server}) ->
 
 webadmin_user(Acc, _User, _Server, Lang) ->
 
     Acc ++ [?XE("h3", [?ACT("roster/", "Roster")])].
 
 
 
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 
+
 
+%% Implement http://jkaluza.fedorapeople.org/remote-roster.html#sect-id188012
 
+
 
+%% Handle 2.3 and 2.5
 
+
 
+process_iq_manager(From, To, IQ) ->
 
+    %% Check what access is allowed for From to To
 
+    MatchDomain = From#jid.lserver,
 
+    case is_domain_managed(MatchDomain, To#jid.lserver) of
 
+	true ->
 
+	    process_iq_manager2(MatchDomain, To, IQ);
 
+	false ->
 
+	    #iq{sub_el = SubEl} = IQ,
 
+	    IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]}
 
+    end.
 
+
 
+process_iq_manager2(MatchDomain, To, IQ) ->
 
+    %% If IQ is SET, filter the input IQ
 
+    IQFiltered = maybe_filter_request(MatchDomain, IQ),
 
+    %% Call the standard function with reversed JIDs
 
+    IdInitial = IQFiltered#iq.id,
 
+    ResIQ = process_iq(To, To, IQFiltered#iq{id = "roster-remotely-managed"}),
 
+    %% Filter the output IQ
 
+    filter_stanza(MatchDomain, ResIQ#iq{id = IdInitial}).
 
+
 
+is_domain_managed(ContactHost, UserHost) ->
 
+    Managers = gen_mod:get_module_opt(UserHost, ?MODULE, managers, []),
 
+    lists:member(ContactHost, Managers).
 
+
 
+maybe_filter_request(MatchDomain, IQ) when IQ#iq.type == set ->
 
+    filter_stanza(MatchDomain, IQ);
 
+maybe_filter_request(_MatchDomain, IQ) ->
 
+    IQ.
 
+
 
+filter_stanza(_MatchDomain, #iq{sub_el = []} = IQ) ->
 
+    IQ;
 
+filter_stanza(MatchDomain, #iq{sub_el = [SubEl | _]} = IQ) ->
 
+    #iq{sub_el = SubElFiltered} = IQRes =
 
+	filter_stanza(MatchDomain, IQ#iq{sub_el = SubEl}),
 
+    IQRes#iq{sub_el = [SubElFiltered]};
 
+filter_stanza(MatchDomain, #iq{sub_el = SubEl} = IQ) ->
 
+    {xmlelement, Type, Attrs, Items} = SubEl,
 
+    ItemsFiltered = lists:filter(
 
+		      fun(Item) ->
 
+			      is_item_of_domain(MatchDomain, Item) end, Items),
 
+    SubElFiltered = {xmlelement, Type, Attrs, ItemsFiltered},
 
+    IQ#iq{sub_el = SubElFiltered}.
 
+
 
+is_item_of_domain(MatchDomain, {xmlelement, _, Attrs, _}) ->
 
+    lists:any(fun(Attr) -> is_jid_of_domain(MatchDomain, Attr) end, Attrs);
 
+is_item_of_domain(_MatchDomain, {xmlcdata, _}) ->
 
+    false.
 
+
 
+is_jid_of_domain(MatchDomain, {"jid", JIDString}) ->
 
+    case jlib:string_to_jid(JIDString) of
 
+	JID when JID#jid.lserver == MatchDomain -> true;
 
+	_ -> false
 
+    end;
 
+is_jid_of_domain(_, _) ->
 
+    false.
 
+
 
+%% Handle 2.5
 
+process_item_attrs_managed(Item, Attrs, true) ->
 
+    process_item_attrs_ws(Item, Attrs);
 
+process_item_attrs_managed(Item, _Attrs, false) ->
 
+    process_item_attrs(Item, _Attrs).
 
+
 
+%% Handle 2.4
 
+send_itemset_to_managers(_From, _Item, true) ->
 
+    ok;
 
+send_itemset_to_managers(From, Item, false) ->
 
+    {_, UserHost} = Item#roster.us,
 
+    {_ContactUser, ContactHost, _ContactResource} = Item#roster.jid,
 
+
 
+    %% Check if the component is an allowed manager
 
+    IsManager = is_domain_managed(ContactHost, UserHost),
 
+    case IsManager of
 
+	true -> push_item("", ContactHost, "", From, Item);
 
+	false -> ok
 
+    end.
 
+
 
+is_managed_from_id("roster-remotely-managed") ->
 
+    true;
 
+is_managed_from_id(_Id) ->
 
+    false.
 
diff --git a/src/mod_roster_odbc.erl b/src/mod_roster_odbc.erl
 
index 4159614..1f5e83f 100644
 
--- a/src/mod_roster_odbc.erl
 
+++ b/src/mod_roster_odbc.erl
 
@@ -113,6 +113,8 @@ stop(Host) ->
 
 			  ?MODULE, webadmin_user, 50),
 
     gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_ROSTER).
 
 
 
+process_iq(From, To, IQ) when ((From#jid.luser == "") andalso (From#jid.lresource == "")) ->
 
+    process_iq_manager(From, To, IQ);
 
 
 
 process_iq(From, To, IQ) ->
 
     #iq{sub_el = SubEl} = IQ,
 
@@ -306,12 +308,13 @@ item_to_xml(Item) ->
 
     {xmlelement, "item", Attrs, SubEls}.
 
 
 
 
 
-process_iq_set(From, To, #iq{sub_el = SubEl} = IQ) ->
 
+process_iq_set(From, To, #iq{sub_el = SubEl, id = Id} = IQ) ->
 
     {xmlelement, _Name, _Attrs, Els} = SubEl,
 
-    lists:foreach(fun(El) -> process_item_set(From, To, El) end, Els),
 
+    Managed = is_managed_from_id(Id),
 
+    lists:foreach(fun(El) -> process_item_set(From, To, El, Managed) end, Els),
 
     IQ#iq{type = result, sub_el = []}.
 
 
 
-process_item_set(From, To, {xmlelement, _Name, Attrs, Els}) ->
 
+process_item_set(From, To, {xmlelement, _Name, Attrs, Els}, Managed) ->
 
     JID1 = jlib:string_to_jid(xml:get_attr_s("jid", Attrs)),
 
     #jid{user = User, luser = LUser, lserver = LServer} = From,
 
     case JID1 of
 
@@ -347,7 +350,8 @@ process_item_set(From, To, {xmlelement, _Name, Attrs, Els}) ->
 
 						 name = ""}
 
 				       end
 
 			       end,
 
-			Item1 = process_item_attrs(Item, Attrs),
 
+%			Item11 = process_item_attrs(Item, Attrs),
 
+			Item1 = process_item_attrs_managed(Item, Attrs, Managed),
 
 			Item2 = process_item_els(Item1, Els),
 
 			case Item2#roster.subscription of
 
 			    remove ->
 
@@ -357,6 +361,7 @@ process_item_set(From, To, {xmlelement, _Name, Attrs, Els}) ->
 
 				ItemGroups = groups_to_string(Item2),
 
 				odbc_queries:update_roster(LServer, Username, SJID, ItemVals, ItemGroups)
 
 			end,
 
+			send_itemset_to_managers(From, Item2, Managed),
 
 			%% If the item exist in shared roster, take the
 
 			%% subscription information from there:
 
 			Item3 = ejabberd_hooks:run_fold(roster_process_item,
 
@@ -382,7 +387,7 @@ process_item_set(From, To, {xmlelement, _Name, Attrs, Els}) ->
 
 		    ok
 
 	    end
 
     end;
 
-process_item_set(_From, _To, _) ->
 
+process_item_set(_From, _To, _, _Managed) ->
 
     ok.
 
 
 
 process_item_attrs(Item, [{Attr, Val} | Attrs]) ->
 
@@ -1197,3 +1202,90 @@ us_to_list({User, Server}) ->
 
 
 
 webadmin_user(Acc, _User, _Server, Lang) ->
 
     Acc ++ [?XE("h3", [?ACT("roster/", "Roster")])].
 
+
 
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 
+
 
+%% Implement http://jkaluza.fedorapeople.org/remote-roster.html#sect-id188012
 
+
 
+%% Handle 2.3 and 2.5
 
+
 
+process_iq_manager(From, To, IQ) ->
 
+    %% Check what access is allowed for From to To
 
+    MatchDomain = From#jid.lserver,
 
+    case is_domain_managed(MatchDomain, To#jid.lserver) of
 
+	true ->
 
+	    process_iq_manager2(MatchDomain, To, IQ);
 
+	false ->
 
+	    #iq{sub_el = SubEl} = IQ,
 
+	    IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]}
 
+    end.
 
+
 
+process_iq_manager2(MatchDomain, To, IQ) ->
 
+    %% If IQ is SET, filter the input IQ
 
+    IQFiltered = maybe_filter_request(MatchDomain, IQ),
 
+    %% Call the standard function with reversed JIDs
 
+    IdInitial = IQFiltered#iq.id,
 
+    ResIQ = process_iq(To, To, IQFiltered#iq{id = "roster-remotely-managed"}),
 
+    %% Filter the output IQ
 
+    filter_stanza(MatchDomain, ResIQ#iq{id = IdInitial}).
 
+
 
+is_domain_managed(ContactHost, UserHost) ->
 
+    Managers = gen_mod:get_module_opt(UserHost, ?MODULE, managers, []),
 
+    lists:member(ContactHost, Managers).
 
+
 
+maybe_filter_request(MatchDomain, IQ) when IQ#iq.type == set ->
 
+    filter_stanza(MatchDomain, IQ);
 
+maybe_filter_request(_MatchDomain, IQ) ->
 
+    IQ.
 
+
 
+filter_stanza(_MatchDomain, #iq{sub_el = []} = IQ) ->
 
+    IQ;
 
+filter_stanza(MatchDomain, #iq{sub_el = [SubEl | _]} = IQ) ->
 
+    #iq{sub_el = SubElFiltered} = IQRes =
 
+	filter_stanza(MatchDomain, IQ#iq{sub_el = SubEl}),
 
+    IQRes#iq{sub_el = [SubElFiltered]};
 
+filter_stanza(MatchDomain, #iq{sub_el = SubEl} = IQ) ->
 
+    {xmlelement, Type, Attrs, Items} = SubEl,
 
+    ItemsFiltered = lists:filter(
 
+		      fun(Item) ->
 
+			      is_item_of_domain(MatchDomain, Item) end, Items),
 
+    SubElFiltered = {xmlelement, Type, Attrs, ItemsFiltered},
 
+    IQ#iq{sub_el = SubElFiltered}.
 
+
 
+is_item_of_domain(MatchDomain, {xmlelement, _, Attrs, _}) ->
 
+    lists:any(fun(Attr) -> is_jid_of_domain(MatchDomain, Attr) end, Attrs);
 
+is_item_of_domain(_MatchDomain, {xmlcdata, _}) ->
 
+    false.
 
+
 
+is_jid_of_domain(MatchDomain, {"jid", JIDString}) ->
 
+    case jlib:string_to_jid(JIDString) of
 
+	JID when JID#jid.lserver == MatchDomain -> true;
 
+	_ -> false
 
+    end;
 
+is_jid_of_domain(_, _) ->
 
+    false.
 
+
 
+%% Handle 2.5
 
+process_item_attrs_managed(Item, Attrs, true) ->
 
+    process_item_attrs_ws(Item, Attrs);
 
+process_item_attrs_managed(Item, _Attrs, false) ->
 
+    process_item_attrs(Item, _Attrs).
 
+
 
+%% Handle 2.4
 
+send_itemset_to_managers(_From, _Item, true) ->
 
+    ok;
 
+send_itemset_to_managers(From, Item, false) ->
 
+    {_, UserHost} = Item#roster.us,
 
+    {_ContactUser, ContactHost, _ContactResource} = Item#roster.jid,
 
+
 
+    %% Check if the component is an allowed manager
 
+    IsManager = is_domain_managed(ContactHost, UserHost),
 
+    case IsManager of
 
+	true -> push_item("", ContactHost, "", From, Item);
 
+	false -> ok
 
+    end.
 
+
 
+is_managed_from_id("roster-remotely-managed") ->
 
+    true;
 
+is_managed_from_id(_Id) ->
 
+    false.