diff --git a/doc/guide.tex b/doc/guide.tex index 06814b5f1..ca60f37d1 100644 --- a/doc/guide.tex +++ b/doc/guide.tex @@ -68,6 +68,7 @@ \newcommand{\modannounce}{\module{mod\_announce}} \newcommand{\modannounceodbc}{\module{mod\_announce\_odbc}} \newcommand{\modblocking}{\module{mod\_blocking}} +\newcommand{\modblockingodbc}{\module{mod\_blocking\_odbc}} \newcommand{\modcaps}{\module{mod\_caps}} \newcommand{\modconfigure}{\module{mod\_configure}} \newcommand{\moddisco}{\module{mod\_disco}} @@ -2586,6 +2587,7 @@ The following table lists all modules included in \ejabberd{}. \hline \ahrefloc{modannounce}{\modannounceodbc{}} & Manage announcements & recommends \modadhoc{} \\ & & supported DB (*) \\ \hline \modblocking{} & Simple Communications Blocking (\xepref{0191}) & \modprivacy{} \\ + \hline \modblockingodbc{} & Simple Communications Blocking (\xepref{0191}) & \modprivacyodbc{} \\ \hline \modcaps{} & Entity Capabilities (\xepref{0115}) & \\ \hline \modconfigure{} & Server configuration using Ad-Hoc & \modadhoc{} \\ \hline \ahrefloc{moddisco}{\moddisco{}} & Service Discovery (\xepref{0030}) & \\ @@ -2664,6 +2666,7 @@ database for the following data: \item vCard-Based Avatars: Use \term{mod\_vcard\_xupdate\_odbc} instead of \term{mod\_vcard\_xupdate}. \item Private XML storage: Use \term{mod\_private\_odbc} instead of \term{mod\_private}. \item User rules for blocking communications: Use \term{mod\_privacy\_odbc} instead of \term{mod\_privacy}. +\item Simple Communications Blocking: Use \term{mod\_blocking\_odbc} instead of \term{mod\_blocking}. \item Pub-Sub nodes, items and subscriptions: Use \term{mod\_pubsub\_odbc} instead of \term{mod\_pubsub}. \item Multi-user chats: Use \term{mod\_muc\_odbc} instead of \term{mod\_muc}. \item Manage announcements: Use \term{mod\_announce\_odbc} instead of \term{mod\_announce}. diff --git a/src/mod_blocking_odbc.erl b/src/mod_blocking_odbc.erl new file mode 100644 index 000000000..016e7945e --- /dev/null +++ b/src/mod_blocking_odbc.erl @@ -0,0 +1,365 @@ +%%%---------------------------------------------------------------------- +%%% File : mod_blocking_odbc.erl +%%% Author : Stephan Maka +%%% Purpose : XEP-0191: Simple Communications Blocking +%%% Created : 24 Aug 2008 by Stephan Maka +%%% +%%% +%%% ejabberd, Copyright (C) 2002-2012 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_odbc). + +-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, + {stop, 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, + Res = + 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, + {stop, Res}; + +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() -> + Default = + case mod_privacy_odbc:sql_get_default_privacy_list_t(LUser) of + {selected, ["name"], []} -> + Name = "Blocked contacts", + mod_privacy_odbc:sql_add_privacy_list(LUser, Name), + mod_privacy_odbc:sql_set_default_privacy_list( + LUser, Name), + Name; + {selected, ["name"], [{Name}]} -> + Name + end, + {selected, ["id"], [{ID}]} = + mod_privacy_odbc:sql_get_privacy_list_id_t(LUser, Default), + case mod_privacy_odbc:sql_get_privacy_list_data_by_id_t(ID) of + {selected, + ["t", "value", "action", "ord", + "match_all", "match_iq", "match_message", + "match_presence_in", + "match_presence_out"], + RItems = [_|_]} -> + List = lists:map( + fun mod_privacy_odbc:raw_to_item/1, + RItems); + _ -> + List = [] + 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), + NewRItems = lists:map( + fun mod_privacy_odbc:item_to_raw/1, + NewList), + mod_privacy_odbc:sql_set_privacy_list( + ID, NewRItems), + {ok, Default, NewList} + end, + case ejabberd_odbc:sql_transaction(LServer, F) of + {atomic, {error, _} = Error} -> + Error; + {atomic, {ok, Default, List}} -> + UserList = make_userlist(Default, List), + broadcast_list_update(LUser, LServer, Default, UserList), + broadcast_blocklist_event(LUser, LServer, {block, JIDs}), + {result, [], UserList}; + _ -> + {error, ?ERR_INTERNAL_SERVER_ERROR} + end. + +process_blocklist_unblock_all(LUser, LServer) -> + F = fun() -> + case mod_privacy_odbc:sql_get_default_privacy_list_t(LUser) of + {selected, ["name"], []} -> + ok; + {selected, ["name"], [{Default}]} -> + {selected, ["id"], [{ID}]} = + mod_privacy_odbc:sql_get_privacy_list_id_t( + LUser, Default), + case mod_privacy_odbc:sql_get_privacy_list_data_by_id_t(ID) of + {selected, + ["t", "value", "action", "ord", + "match_all", "match_iq", "match_message", + "match_presence_in", + "match_presence_out"], + RItems = [_|_]} -> + List = lists:map( + fun mod_privacy_odbc:raw_to_item/1, + RItems), + NewList = + lists:filter( + fun(#listitem{action = A}) -> + A =/= deny + end, List), + NewRItems = lists:map( + fun mod_privacy_odbc:item_to_raw/1, + NewList), + mod_privacy_odbc:sql_set_privacy_list( + ID, NewRItems), + {ok, Default, NewList}; + _ -> + ok + end; + _ -> + ok + end + end, + case ejabberd_odbc:sql_transaction(LServer, F) of + {atomic, {error, _} = Error} -> + Error; + {atomic, ok} -> + {result, []}; + {atomic, {ok, Default, List}} -> + UserList = make_userlist(Default, List), + broadcast_list_update(LUser, LServer, Default, UserList), + broadcast_blocklist_event(LUser, LServer, unblock_all), + {result, [], UserList}; + _ -> + {error, ?ERR_INTERNAL_SERVER_ERROR} + end. + +process_blocklist_unblock(LUser, LServer, JIDs) -> + F = fun() -> + case mod_privacy_odbc:sql_get_default_privacy_list_t(LUser) of + {selected, ["name"], []} -> + ok; + {selected, ["name"], [{Default}]} -> + {selected, ["id"], [{ID}]} = + mod_privacy_odbc:sql_get_privacy_list_id_t( + LUser, Default), + case mod_privacy_odbc:sql_get_privacy_list_data_by_id_t(ID) of + {selected, + ["t", "value", "action", "ord", + "match_all", "match_iq", "match_message", + "match_presence_in", + "match_presence_out"], + RItems = [_|_]} -> + List = lists:map( + fun mod_privacy_odbc:raw_to_item/1, + RItems), + NewList = + lists:filter( + fun(#listitem{action = deny, + type = jid, + value = JID}) -> + not(lists:member(JID, JIDs)); + (_) -> + true + end, List), + NewRItems = lists:map( + fun mod_privacy_odbc:item_to_raw/1, + NewList), + mod_privacy_odbc:sql_set_privacy_list( + ID, NewRItems), + {ok, Default, NewList}; + _ -> + ok + end; + _ -> + ok + end + end, + case ejabberd_odbc:sql_transaction(LServer, F) of + {atomic, {error, _} = Error} -> + Error; + {atomic, ok} -> + {result, []}; + {atomic, {ok, Default, List}} -> + UserList = make_userlist(Default, List), + broadcast_list_update(LUser, LServer, Default, UserList), + broadcast_blocklist_event(LUser, LServer, {unblock, JIDs}), + {result, [], UserList}; + _ -> + {error, ?ERR_INTERNAL_SERVER_ERROR} + end. + +make_userlist(Name, List) -> + NeedDb = is_list_needdb(List), + #userlist{name = Name, list = List, needdb = NeedDb}. + +broadcast_list_update(LUser, LServer, Name, UserList) -> + ejabberd_router:route( + jlib:make_jid(LUser, LServer, ""), + jlib:make_jid(LUser, LServer, ""), + {xmlelement, "broadcast", [], + [{privacy_list, UserList, 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 mod_privacy_odbc:sql_get_default_privacy_list(LUser, LServer) of + {selected, ["name"], []} -> + {result, [{xmlelement, "blocklist", + [{"xmlns", ?NS_BLOCKING}], []}]}; + {selected, ["name"], [{Default}]} -> + case catch mod_privacy_odbc:sql_get_privacy_list_data( + LUser, LServer, Default) of + {selected, ["t", "value", "action", "ord", "match_all", + "match_iq", "match_message", + "match_presence_in", "match_presence_out"], + RItems} -> + List = lists:map(fun mod_privacy_odbc:raw_to_item/1, RItems), + 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}]}; + {'EXIT', _} -> + {error, ?ERR_INTERNAL_SERVER_ERROR} + end; + {'EXIT', _} -> + {error, ?ERR_INTERNAL_SERVER_ERROR} + end. diff --git a/src/mod_privacy_odbc.erl b/src/mod_privacy_odbc.erl index 432617ebe..87303b57a 100644 --- a/src/mod_privacy_odbc.erl +++ b/src/mod_privacy_odbc.erl @@ -37,8 +37,19 @@ check_packet/6, remove_user/2, item_to_raw/1, + raw_to_item/1, updated_list/3]). +%% For mod_blocking_odbc +-export([sql_add_privacy_list/2, + sql_get_default_privacy_list/2, + sql_get_default_privacy_list_t/1, + sql_get_privacy_list_data/3, + sql_get_privacy_list_data_by_id_t/1, + sql_get_privacy_list_id_t/2, + sql_set_default_privacy_list/2, + sql_set_privacy_list/2]). + -include("ejabberd.hrl"). -include("jlib.hrl"). -include("mod_privacy.hrl"). @@ -836,6 +847,9 @@ sql_get_privacy_list_data(LUser, LServer, Name) -> sql_get_privacy_list_data_by_id(ID, LServer) -> odbc_queries:get_privacy_list_data_by_id(LServer, ID). +sql_get_privacy_list_data_by_id_t(ID) -> + odbc_queries:get_privacy_list_data_by_id_t(ID). + sql_set_default_privacy_list(LUser, Name) -> Username = ejabberd_odbc:escape(LUser), SName = ejabberd_odbc:escape(Name), diff --git a/src/odbc/odbc_queries.erl b/src/odbc/odbc_queries.erl index 2af5f2ba6..3ec3b1be5 100644 --- a/src/odbc/odbc_queries.erl +++ b/src/odbc/odbc_queries.erl @@ -70,6 +70,7 @@ get_privacy_list_id_t/2, get_privacy_list_data/3, get_privacy_list_data_by_id/2, + get_privacy_list_data_by_id_t/1, set_default_privacy_list/2, unset_default_privacy_list/2, remove_privacy_list/2, @@ -506,6 +507,13 @@ get_privacy_list_data_by_id(LServer, ID) -> "from privacy_list_data " "where id='", ID, "' order by ord;"]). +get_privacy_list_data_by_id_t(ID) -> + ejabberd_odbc:sql_query_t( + ["select t, value, action, ord, match_all, match_iq, " + "match_message, match_presence_in, match_presence_out " + "from privacy_list_data " + "where id='", ID, "' order by ord;"]). + set_default_privacy_list(Username, SName) -> update_t("privacy_default_list", ["username", "name"], [Username, SName], ["username='", Username, "'"]). @@ -834,6 +842,10 @@ get_privacy_list_data_by_id(LServer, ID) -> LServer, ["EXECUTE dbo.get_privacy_list_data_by_id '", ID, "'"]). +get_privacy_list_data_by_id_t(ID) -> + ejabberd_odbc:sql_query_t( + ["EXECUTE dbo.get_privacy_list_data_by_id '", ID, "'"]). + set_default_privacy_list(Username, SName) -> ejabberd_odbc:sql_query_t( ["EXECUTE dbo.set_default_privacy_list '", Username, "' , '", SName, "'"]).