Support XEP-0191 Simple Communications Blocking (thanks to Stephan Maka)(EJAB-695)

This commit is contained in:
Badlop 2011-05-27 11:43:52 +02:00
parent 1c0b99e162
commit bfebcebeb7
5 changed files with 390 additions and 3 deletions

View 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}) & \\

View File

@ -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, []},

View 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
%%%----------------------------------------------------------------------

View 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

333
src/mod_blocking.erl Normal file
View File

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