]> granicus.if.org Git - ejabberd/commitdiff
Support XEP-0191 Simple Communications Blocking (thanks to Stephan Maka)(EJAB-695)
authorBadlop <badlop@process-one.net>
Fri, 27 May 2011 09:43:52 +0000 (11:43 +0200)
committerBadlop <badlop@process-one.net>
Fri, 27 May 2011 09:47:22 +0000 (11:47 +0200)
doc/guide.tex
src/ejabberd.cfg.example
src/ejabberd_c2s.erl
src/jlib.hrl
src/mod_blocking.erl [new file with mode: 0644]

index 0fdea48aeceec9116fe7bd0f2e0cf475441c241d..463a12f48fb4f5ee46cf307b01a2a678caa7fd0b 100644 (file)
@@ -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}) &  \\
index c776a8cb987992fc32e0936d0aabeeebe7b4e7e0..92e78e32d11d4569ab50006568bfc4ff3b91e42b 100644 (file)
  [
   {mod_adhoc,    []},
   {mod_announce, [{access, announce}]}, % recommends mod_adhoc
+  {mod_blocking,[]}, % requires mod_privacy
   {mod_caps,     []},
   {mod_configure,[]}, % requires mod_adhoc
   {mod_disco,    []},
index 7888256f01e63bfdedc2e4be0d7fec207a8efa62..67fee64043d135dffa342c55b4f12a85a3fcab52 100644 (file)
@@ -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;
@@ -2237,6 +2242,51 @@ bounce_messages() ->
            ok
     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
 %%%----------------------------------------------------------------------
index 5d554d97656ccf20580fcb2899f42602d47d9a58..2dc2b53f0c736f6fe91c3cc1d9132f2884005531 100644 (file)
@@ -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 (file)
index 0000000..ea2c19c
--- /dev/null
@@ -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.