From 5a1300bc7019c603a777b173bd62cc309d74da8d Mon Sep 17 00:00:00 2001 From: Badlop Date: Wed, 4 Dec 2013 14:55:21 +0100 Subject: [PATCH] Add access rule to mod_roster (EJAB-72) --- doc/guide.tex | 25 +++++++++++++++++++++++++ src/ejabberd_c2s.erl | 26 ++++++++++++++++++-------- src/mod_roster.erl | 14 ++++++++++++-- 3 files changed, 55 insertions(+), 10 deletions(-) diff --git a/doc/guide.tex b/doc/guide.tex index 4d3b2b4ff..468faf009 100644 --- a/doc/guide.tex +++ b/doc/guide.tex @@ -4039,6 +4039,13 @@ Options: This option is disabled by default. Important: if you use \modsharedroster{} or \modsharedrosterldap{}, you must disable this option. + \titem{access} \ind{options!access} + This option can be configured to specify rules to restrict roster management. + If a rule returns `deny' on the requested user name, + that user cannot modify his personal roster: + not add/remove/modify contacts, + or subscribe/unsubscribe presence. + By default there aren't restrictions. \end{description} This example configuration enables Roster Versioning with storage of current id: @@ -4051,6 +4058,24 @@ modules: ... \end{verbatim} +With this example configuration, only admins can manage their rosters; +everybody else cannot modify the roster: +\begin{verbatim} +acl: + admin: + user: + - "sarah": "example.org" +access: + roster: + admin: allow + +modules: + ... + mod_roster: + access: roster + ... +\end{verbatim} + \makesubsection{modservicelog}{\modservicelog{}} \ind{modules!\modservicelog{}}\ind{message auditing}\ind{Bandersnatch} diff --git a/src/ejabberd_c2s.erl b/src/ejabberd_c2s.erl index fa8ec3f5b..e5304044a 100644 --- a/src/ejabberd_c2s.erl +++ b/src/ejabberd_c2s.erl @@ -1879,10 +1879,7 @@ presence_track(From, To, Packet, StateData) -> A = remove_element(LTo, StateData#state.pres_a), StateData#state{pres_a = A}; <<"subscribe">> -> - ejabberd_hooks:run(roster_out_subscription, Server, - [User, Server, To, subscribe]), - check_privacy_route(From, StateData, - jlib:jid_remove_resource(From), To, Packet), + try_roster_subscribe(subscribe, User, Server, From, To, Packet, StateData), StateData; <<"subscribed">> -> ejabberd_hooks:run(roster_out_subscription, Server, @@ -1891,10 +1888,7 @@ presence_track(From, To, Packet, StateData) -> jlib:jid_remove_resource(From), To, Packet), StateData; <<"unsubscribe">> -> - ejabberd_hooks:run(roster_out_subscription, Server, - [User, Server, To, unsubscribe]), - check_privacy_route(From, StateData, - jlib:jid_remove_resource(From), To, Packet), + try_roster_subscribe(unsubscribe, User, Server, From, To, Packet, StateData), StateData; <<"unsubscribed">> -> ejabberd_hooks:run(roster_out_subscription, Server, @@ -1943,6 +1937,22 @@ is_privacy_allow(StateData, From, To, Packet, Dir) -> allow == privacy_check_packet(StateData, From, To, Packet, Dir). +%%% Check ACL before allowing to send a subscription stanza +try_roster_subscribe(Type, User, Server, From, To, Packet, StateData) -> + JID1 = jlib:make_jid(User, Server, <<"">>), + Access = gen_mod:get_module_opt(Server, mod_roster, access, fun(A) when is_atom(A) -> A end, all), + case acl:match_rule(Server, Access, JID1) of + deny -> + %% Silently drop this (un)subscription request + ok; + allow -> + ejabberd_hooks:run(roster_out_subscription, + Server, + [User, Server, To, Type]), + check_privacy_route(From, StateData, jlib:jid_remove_resource(From), + To, Packet) + end. + %% Send presence when disconnecting presence_broadcast(StateData, From, JIDSet, Packet) -> JIDs = ?SETS:to_list(JIDSet), diff --git a/src/mod_roster.erl b/src/mod_roster.erl index dd211b95a..7415aa3de 100644 --- a/src/mod_roster.erl +++ b/src/mod_roster.erl @@ -142,7 +142,7 @@ process_iq(From, To, IQ) -> process_local_iq(From, To, #iq{type = Type} = IQ) -> case Type of - set -> process_iq_set(From, To, IQ); + set -> try_process_iq_set(From, To, IQ); get -> process_iq_get(From, To, IQ) end. @@ -455,6 +455,16 @@ get_roster_by_jid_t(LUser, LServer, LJID, odbc) -> end end. +try_process_iq_set(From, To, #iq{sub_el = SubEl} = IQ) -> + #jid{server = Server} = From, + Access = gen_mod:get_module_opt(Server, ?MODULE, access, fun(A) when is_atom(A) -> A end, all), + case acl:match_rule(Server, Access, From) of + deny -> + IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}; + allow -> + process_iq_set(From, To, IQ) + end. + process_iq_set(From, To, #iq{sub_el = SubEl} = IQ) -> #xmlel{children = Els} = SubEl, lists:foreach(fun (El) -> process_item_set(From, To, El) @@ -1508,7 +1518,7 @@ user_roster_item_parse_query(User, Server, Items, {value, _} -> UJID = jlib:make_jid(User, Server, <<"">>), - process_iq(UJID, UJID, + process_iq_set(UJID, UJID, #iq{type = set, sub_el = #xmlel{name =