diff options
author | Badlop <badlop@process-one.net> | 2011-05-27 11:43:52 +0200 |
---|---|---|
committer | Badlop <badlop@process-one.net> | 2011-05-27 11:47:22 +0200 |
commit | bfebcebeb72b7ab4558e26ab594e065e52b370c6 (patch) | |
tree | efd7b08a6c60612d7e602d5ffd53aa2ea495e581 | |
parent | A user can query his own Last activity, even if not subscribed to oneself (diff) |
Support XEP-0191 Simple Communications Blocking (thanks to Stephan Maka)(EJAB-695)
-rw-r--r-- | doc/guide.tex | 6 | ||||
-rw-r--r-- | src/ejabberd.cfg.example | 1 | ||||
-rw-r--r-- | src/ejabberd_c2s.erl | 52 | ||||
-rw-r--r-- | src/jlib.hrl | 1 | ||||
-rw-r--r-- | src/mod_blocking.erl | 333 |
5 files changed, 390 insertions, 3 deletions
diff --git a/doc/guide.tex b/doc/guide.tex index 0fdea48a..463a12f4 100644 --- a/doc/guide.tex +++ b/doc/guide.tex @@ -66,6 +66,7 @@ \newcommand{\module}[1]{\texttt{#1}} \newcommand{\modadhoc}{\module{mod\_adhoc}} \newcommand{\modannounce}{\module{mod\_announce}} +\newcommand{\modblocking}{\module{mod\_blocking}} \newcommand{\modcaps}{\module{mod\_caps}} \newcommand{\modconfigure}{\module{mod\_configure}} \newcommand{\moddisco}{\module{mod\_disco}} @@ -2525,6 +2526,7 @@ The following table lists all modules included in \ejabberd{}. \hline \hline \modadhoc{} & Ad-Hoc Commands (\xepref{0050}) & \\ \hline \ahrefloc{modannounce}{\modannounce{}} & Manage announcements & recommends \modadhoc{} \\ + \hline \modblocking{} & Simple Communications Blocking (\xepref{0191}) & \modprivacy{} \\ \hline \modcaps{} & Entity Capabilities (\xepref{0115}) & \\ \hline \modconfigure{} & Server configuration using Ad-Hoc & \modadhoc{} \\ \hline \ahrefloc{moddisco}{\moddisco{}} & Service Discovery (\xepref{0030}) & \\ @@ -2540,8 +2542,8 @@ The following table lists all modules included in \ejabberd{}. \hline \ahrefloc{modoffline}{\modofflineodbc{}} & Offline message storage (\xepref{0160}) & supported DB (*) \\ \hline \ahrefloc{modping}{\modping{}} & XMPP Ping and periodic keepalives (\xepref{0199}) & \\ \hline \ahrefloc{modprescounter}{\modprivacy{}} & Detect presence subscription flood & \\ - \hline \ahrefloc{modprivacy}{\modprivacy{}} & Blocking Communication (XMPP IM) & \\ - \hline \ahrefloc{modprivacy}{\modprivacyodbc{}} & Blocking Communication (XMPP IM) & supported DB (*) \\ + \hline \ahrefloc{modprivacy}{\modprivacy{}} & Blocking Communication (\xepref{0016}) & \\ + \hline \ahrefloc{modprivacy}{\modprivacyodbc{}} & Blocking Communication (\xepref{0016}) & supported DB (*) \\ \hline \ahrefloc{modprivate}{\modprivate{}} & Private XML Storage (\xepref{0049}) & \\ \hline \ahrefloc{modprivate}{\modprivateodbc{}} & Private XML Storage (\xepref{0049}) & supported DB (*) \\ \hline \ahrefloc{modproxy}{\modproxy{}} & SOCKS5 Bytestreams (\xepref{0065}) & \\ diff --git a/src/ejabberd.cfg.example b/src/ejabberd.cfg.example index c776a8cb..92e78e32 100644 --- a/src/ejabberd.cfg.example +++ b/src/ejabberd.cfg.example @@ -494,6 +494,7 @@ [ {mod_adhoc, []}, {mod_announce, [{access, announce}]}, % recommends mod_adhoc + {mod_blocking,[]}, % requires mod_privacy {mod_caps, []}, {mod_configure,[]}, % requires mod_adhoc {mod_disco, []}, diff --git a/src/ejabberd_c2s.erl b/src/ejabberd_c2s.erl index 7888256f..67fee640 100644 --- a/src/ejabberd_c2s.erl +++ b/src/ejabberd_c2s.erl @@ -1051,7 +1051,9 @@ session_established2(El, StateData) -> end; "iq" -> case jlib:iq_query_info(NewEl) of - #iq{xmlns = ?NS_PRIVACY} = IQ -> + #iq{xmlns = Xmlns} = IQ + when Xmlns == ?NS_PRIVACY; + Xmlns == ?NS_BLOCKING -> process_privacy_iq( FromJID, ToJID, IQ, StateData); _ -> @@ -1283,6 +1285,9 @@ handle_info({route, From, To, Packet}, StateName, StateData) -> send_element(StateData, PrivPushEl), {false, Attrs, StateData#state{privacy_list = NewPL}} end; + [{blocking, What}] -> + route_blocking(What, StateData), + {false, Attrs, StateData}; _ -> {false, Attrs, StateData} end; @@ -2238,6 +2243,51 @@ bounce_messages() -> end. %%%---------------------------------------------------------------------- +%%% XEP-0191 +%%%---------------------------------------------------------------------- + +route_blocking(What, StateData) -> + SubEl = + case What of + {block, JIDs} -> + {xmlelement, "block", + [{"xmlns", ?NS_BLOCKING}], + lists:map( + fun(JID) -> + {xmlelement, "item", + [{"jid", jlib:jid_to_string(JID)}], + []} + end, JIDs)}; + {unblock, JIDs} -> + {xmlelement, "unblock", + [{"xmlns", ?NS_BLOCKING}], + lists:map( + fun(JID) -> + {xmlelement, "item", + [{"jid", jlib:jid_to_string(JID)}], + []} + end, JIDs)}; + unblock_all -> + {xmlelement, "unblock", + [{"xmlns", ?NS_BLOCKING}], []} + end, + PrivPushIQ = + #iq{type = set, xmlns = ?NS_BLOCKING, + id = "push", + sub_el = [SubEl]}, + PrivPushEl = + jlib:replace_from_to( + jlib:jid_remove_resource( + StateData#state.jid), + StateData#state.jid, + jlib:iq_to_xml(PrivPushIQ)), + send_element(StateData, PrivPushEl), + %% No need to replace active privacy list here, + %% blocking pushes are always accompanied by + %% Privacy List pushes + ok. + +%%%---------------------------------------------------------------------- %%% JID Set memory footprint reduction code %%%---------------------------------------------------------------------- diff --git a/src/jlib.hrl b/src/jlib.hrl index 5d554d97..2dc2b53f 100644 --- a/src/jlib.hrl +++ b/src/jlib.hrl @@ -30,6 +30,7 @@ -define(NS_ROSTER, "jabber:iq:roster"). -define(NS_ROSTER_VER, "urn:xmpp:features:rosterver"). -define(NS_PRIVACY, "jabber:iq:privacy"). +-define(NS_BLOCKING, "urn:xmpp:blocking"). -define(NS_PRIVATE, "jabber:iq:private"). -define(NS_VERSION, "jabber:iq:version"). -define(NS_TIME90, "jabber:iq:time"). % TODO: Remove once XEP-0090 is Obsolete diff --git a/src/mod_blocking.erl b/src/mod_blocking.erl new file mode 100644 index 00000000..ea2c19cf --- /dev/null +++ b/src/mod_blocking.erl @@ -0,0 +1,333 @@ +%%%---------------------------------------------------------------------- +%%% File : mod_blocking.erl +%%% Author : Stephan Maka +%%% Purpose : XEP-0191: Simple Communications Blocking +%%% Created : 24 Aug 2008 by Stephan Maka <stephan@spaceboyz.net> +%%% +%%% +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne +%%% +%%% This program is free software; you can redistribute it and/or +%%% modify it under the terms of the GNU General Public License as +%%% published by the Free Software Foundation; either version 2 of the +%%% License, or (at your option) any later version. +%%% +%%% This program is distributed in the hope that it will be useful, +%%% but WITHOUT ANY WARRANTY; without even the implied warranty of +%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%%% General Public License for more details. +%%% +%%% You should have received a copy of the GNU General Public License +%%% along with this program; if not, write to the Free Software +%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +%%% 02111-1307 USA +%%% +%%%---------------------------------------------------------------------- + +-module(mod_blocking). + +-behaviour(gen_mod). + +-export([start/2, stop/1, + process_iq/3, + process_iq_set/4, + process_iq_get/5]). + +-include("ejabberd.hrl"). +-include("jlib.hrl"). +-include("mod_privacy.hrl"). + +start(Host, Opts) -> + IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue), + ejabberd_hooks:add(privacy_iq_get, Host, + ?MODULE, process_iq_get, 40), + ejabberd_hooks:add(privacy_iq_set, Host, + ?MODULE, process_iq_set, 40), + mod_disco:register_feature(Host, ?NS_BLOCKING), + gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_BLOCKING, + ?MODULE, process_iq, IQDisc). + +stop(Host) -> + ejabberd_hooks:delete(privacy_iq_get, Host, + ?MODULE, process_iq_get, 40), + ejabberd_hooks:delete(privacy_iq_set, Host, + ?MODULE, process_iq_set, 40), + mod_disco:unregister_feature(Host, ?NS_BLOCKING), + gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_BLOCKING). + +process_iq(_From, _To, IQ) -> + SubEl = IQ#iq.sub_el, + IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}. + +process_iq_get(_, From, _To, + #iq{xmlns = ?NS_BLOCKING, + sub_el = {xmlelement, "blocklist", _, _}}, + _) -> + #jid{luser = LUser, lserver = LServer} = From, + process_blocklist_get(LUser, LServer); + +process_iq_get(Acc, _, _, _, _) -> + Acc. + +process_iq_set(_, From, _To, #iq{xmlns = ?NS_BLOCKING, + sub_el = {xmlelement, SubElName, _, SubEls}}) -> + #jid{luser = LUser, lserver = LServer} = From, + case {SubElName, xml:remove_cdata(SubEls)} of + {"block", []} -> + {error, ?ERR_BAD_REQUEST}; + {"block", Els} -> + JIDs = parse_blocklist_items(Els, []), + process_blocklist_block(LUser, LServer, JIDs); + {"unblock", []} -> + process_blocklist_unblock_all(LUser, LServer); + {"unblock", Els} -> + JIDs = parse_blocklist_items(Els, []), + process_blocklist_unblock(LUser, LServer, JIDs); + _ -> + {error, ?ERR_BAD_REQUEST} + end; + +process_iq_set(Acc, _, _, _) -> + Acc. + +is_list_needdb(Items) -> + lists:any( + fun(X) -> + case X#listitem.type of + subscription -> true; + group -> true; + _ -> false + end + end, Items). + +list_to_blocklist_jids([], JIDs) -> + JIDs; + +list_to_blocklist_jids([#listitem{type = jid, + action = deny, + value = JID} = Item | Items], JIDs) -> + case Item of + #listitem{match_all = true} -> + Match = true; + #listitem{match_iq = true, + match_message = true, + match_presence_in = true, + match_presence_out = true} -> + Match = true; + _ -> + Match = false + end, + if + Match -> + list_to_blocklist_jids(Items, [JID | JIDs]); + true -> + list_to_blocklist_jids(Items, JIDs) + end; + +% Skip Privacy List items than cannot be mapped to Blocking items +list_to_blocklist_jids([_ | Items], JIDs) -> + list_to_blocklist_jids(Items, JIDs). + +parse_blocklist_items([], JIDs) -> + JIDs; + +parse_blocklist_items([{xmlelement, "item", Attrs, _} | Els], JIDs) -> + case xml:get_attr("jid", Attrs) of + {value, JID1} -> + JID = jlib:jid_tolower(jlib:string_to_jid(JID1)), + parse_blocklist_items(Els, [JID | JIDs]); + false -> + % Tolerate missing jid attribute + parse_blocklist_items(Els, JIDs) + end; + +parse_blocklist_items([_ | Els], JIDs) -> + % Tolerate unknown elements + parse_blocklist_items(Els, JIDs). + +process_blocklist_block(LUser, LServer, JIDs) -> + F = + fun() -> + case mnesia:wread({privacy, {LUser, LServer}}) of + [] -> + % No lists yet + P = #privacy{us = {LUser, LServer}}, + % TODO: i18n here: + NewDefault = "Blocked contacts", + NewLists1 = [], + List = []; + [#privacy{default = Default, + lists = Lists} = P] -> + case lists:keysearch(Default, 1, Lists) of + {value, {_, List}} -> + % Default list exists + NewDefault = Default, + NewLists1 = lists:keydelete(Default, 1, Lists); + false -> + % No default list yet, create one + % TODO: i18n here: + NewDefault = "Blocked contacts", + NewLists1 = Lists, + List = [] + end + end, + + AlreadyBlocked = list_to_blocklist_jids(List, []), + NewList = + lists:foldr(fun(JID, List1) -> + case lists:member(JID, AlreadyBlocked) of + true -> + List1; + false -> + [#listitem{type = jid, + value = JID, + action = deny, + order = 0, + match_all = true + } | List1] + end + end, List, JIDs), + NewLists = [{NewDefault, NewList} | NewLists1], + mnesia:write(P#privacy{default = NewDefault, + lists = NewLists}), + {ok, NewDefault, NewList} + end, + case mnesia:transaction(F) of + {atomic, {error, _} = Error} -> + Error; + {atomic, {ok, Default, List}} -> + broadcast_list_update(LUser, LServer, Default, List), + broadcast_blocklist_event(LUser, LServer, {block, JIDs}), + {result, []}; + _ -> + {error, ?ERR_INTERNAL_SERVER_ERROR} + end. + +process_blocklist_unblock_all(LUser, LServer) -> + F = + fun() -> + case mnesia:read({privacy, {LUser, LServer}}) of + [] -> + % No lists, nothing to unblock + ok; + [#privacy{default = Default, + lists = Lists} = P] -> + case lists:keysearch(Default, 1, Lists) of + {value, {_, List}} -> + % Default list, remove all deny items + NewList = + lists:filter( + fun(#listitem{action = A}) -> + A =/= deny + end, List), + + NewLists1 = lists:keydelete(Default, 1, Lists), + NewLists = [{Default, NewList} | NewLists1], + mnesia:write(P#privacy{lists = NewLists}), + + {ok, Default, NewList}; + false -> + % No default list, nothing to unblock + ok + end + end + end, + case mnesia:transaction(F) of + {atomic, {error, _} = Error} -> + Error; + {atomic, ok} -> + {result, []}; + {atomic, {ok, Default, List}} -> + broadcast_list_update(LUser, LServer, Default, List), + broadcast_blocklist_event(LUser, LServer, unblock_all), + {result, []}; + _ -> + {error, ?ERR_INTERNAL_SERVER_ERROR} + end. + +process_blocklist_unblock(LUser, LServer, JIDs) -> + F = + fun() -> + case mnesia:read({privacy, {LUser, LServer}}) of + [] -> + % No lists, nothing to unblock + ok; + [#privacy{default = Default, + lists = Lists} = P] -> + case lists:keysearch(Default, 1, Lists) of + {value, {_, List}} -> + % Default list, remove matching deny items + NewList = + lists:filter( + fun(#listitem{action = deny, + type = jid, + value = JID}) -> + not(lists:member(JID, JIDs)); + (_) -> + true + end, List), + + NewLists1 = lists:keydelete(Default, 1, Lists), + NewLists = [{Default, NewList} | NewLists1], + mnesia:write(P#privacy{lists = NewLists}), + + {ok, Default, NewList}; + false -> + % No default list, nothing to unblock + ok + end + end + end, + case mnesia:transaction(F) of + {atomic, {error, _} = Error} -> + Error; + {atomic, ok} -> + {result, []}; + {atomic, {ok, Default, List}} -> + broadcast_list_update(LUser, LServer, Default, List), + broadcast_blocklist_event(LUser, LServer, {unblock, JIDs}), + {result, []}; + _ -> + {error, ?ERR_INTERNAL_SERVER_ERROR} + end. + +broadcast_list_update(LUser, LServer, Name, List) -> + NeedDb = is_list_needdb(List), + ejabberd_router:route( + jlib:make_jid(LUser, LServer, ""), + jlib:make_jid(LUser, LServer, ""), + {xmlelement, "broadcast", [], + [{privacy_list, + #userlist{name = Name, list = List, needdb = NeedDb}, + Name}]}). + +broadcast_blocklist_event(LUser, LServer, Event) -> + JID = jlib:make_jid(LUser, LServer, ""), + ejabberd_router:route( + JID, JID, + {xmlelement, "broadcast", [], + [{blocking, Event}]}). + +process_blocklist_get(LUser, LServer) -> + case catch mnesia:dirty_read(privacy, {LUser, LServer}) of + {'EXIT', _Reason} -> + {error, ?ERR_INTERNAL_SERVER_ERROR}; + [] -> + {result, [{xmlelement, "blocklist", [{"xmlns", ?NS_BLOCKING}], []}]}; + [#privacy{default = Default, lists = Lists}] -> + case lists:keysearch(Default, 1, Lists) of + {value, {_, List}} -> + JIDs = list_to_blocklist_jids(List, []), + Items = lists:map( + fun(JID) -> + ?DEBUG("JID: ~p",[JID]), + {xmlelement, "item", + [{"jid", jlib:jid_to_string(JID)}], []} + end, JIDs), + {result, + [{xmlelement, "blocklist", [{"xmlns", ?NS_BLOCKING}], + Items}]}; + _ -> + {result, [{xmlelement, "blocklist", [{"xmlns", ?NS_BLOCKING}], []}]} + end + end. |