|
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.
|