summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBadlop <badlop@process-one.net>2011-05-27 11:43:52 +0200
committerBadlop <badlop@process-one.net>2011-05-27 11:47:22 +0200
commitbfebcebeb72b7ab4558e26ab594e065e52b370c6 (patch)
treeefd7b08a6c60612d7e602d5ffd53aa2ea495e581
parentA 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.tex6
-rw-r--r--src/ejabberd.cfg.example1
-rw-r--r--src/ejabberd_c2s.erl52
-rw-r--r--src/jlib.hrl1
-rw-r--r--src/mod_blocking.erl333
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.