Packages
ejabberd
26.3.0
26.4.0
26.3.0
26.2.0
26.1.0
25.10.0
25.8.0
25.7.0
25.4.0
25.3.0
24.12.0
24.10.0
24.7.0
24.6.0
24.2.6
23.10.0
23.4.0
23.1.0
22.10.0
22.5.0
21.12.0
21.7.0
21.4.0
21.1.0
20.12.0
20.7.0
20.4.0
20.3.0
20.2.0
20.1.0
19.9.1
19.9.0
19.8.0
19.5.0
19.2.0
18.12.1
18.12.0
18.6.0
18.4.0
18.3.0
18.1.0
17.11.0
17.9.0
17.6.0
17.3.0
17.1.0
16.12.0-beta1
16.9.0
16.8.0
16.6.2
16.6.1
16.6.0
16.4.1
16.4.0
16.3.0
16.2.0
16.1.0-beta1
Robust, Ubiquitous and Massively Scalable Messaging Platform (XMPP, MQTT, SIP Server)
Current section
Files
Jump to
Current section
Files
src/mod_blocking.erl
%%%----------------------------------------------------------------------
%%% 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-2026 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.,
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
%%%
%%%----------------------------------------------------------------------
-module(mod_blocking).
-behaviour(gen_mod).
-protocol({xep, 191, '1.3', '2.1.7', "complete", ""}).
-export([start/2, stop/1, reload/3, process_iq/1, depends/2,
disco_features/5, mod_options/1, mod_doc/0]).
-include("logger.hrl").
-include_lib("xmpp/include/xmpp.hrl").
-include("mod_privacy.hrl").
-include("translate.hrl").
start(_Host, _Opts) ->
{ok, [{hook, disco_local_features, disco_features, 50},
{iq_handler, ejabberd_sm, ?NS_BLOCKING, process_iq}]}.
stop(_Host) ->
ok.
reload(_Host, _NewOpts, _OldOpts) ->
ok.
depends(_Host, _Opts) ->
[{mod_privacy, hard}].
-spec disco_features({error, stanza_error()} | {result, [binary()]} | empty,
jid(), jid(), binary(), binary()) ->
{error, stanza_error()} | {result, [binary()]}.
disco_features({error, Err}, _From, _To, _Node, _Lang) ->
{error, Err};
disco_features(empty, _From, _To, <<"">>, _Lang) ->
{result, [?NS_BLOCKING]};
disco_features({result, Feats}, _From, _To, <<"">>, _Lang) ->
{result, [?NS_BLOCKING|Feats]};
disco_features(Acc, _From, _To, _Node, _Lang) ->
Acc.
-spec process_iq(iq()) -> iq().
process_iq(#iq{type = Type,
from = #jid{luser = U, lserver = S},
to = #jid{luser = U, lserver = S}} = IQ) ->
case Type of
get -> process_iq_get(IQ);
set -> process_iq_set(IQ)
end;
process_iq(#iq{lang = Lang} = IQ) ->
Txt = ?T("Query to another users is forbidden"),
xmpp:make_error(IQ, xmpp:err_forbidden(Txt, Lang)).
-spec process_iq_get(iq()) -> iq().
process_iq_get(#iq{sub_els = [#block_list{}]} = IQ) ->
process_get(IQ);
process_iq_get(#iq{lang = Lang} = IQ) ->
Txt = ?T("No module is handling this query"),
xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang)).
-spec process_iq_set(iq()) -> iq().
process_iq_set(#iq{lang = Lang, sub_els = [SubEl]} = IQ) ->
case SubEl of
#block{items = []} ->
Txt = ?T("No items found in this query"),
xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang));
#block{items = Items} ->
JIDs = [jid:tolower(JID) || #block_item{jid = JID} <- Items],
process_block(IQ, JIDs);
#unblock{items = []} ->
process_unblock_all(IQ);
#unblock{items = Items} ->
JIDs = [jid:tolower(JID) || #block_item{jid = JID} <- Items],
process_unblock(IQ, JIDs);
_ ->
Txt = ?T("No module is handling this query"),
xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang))
end.
-spec listitems_to_jids([listitem()], [ljid()]) -> [ljid()].
listitems_to_jids([], JIDs) ->
JIDs;
listitems_to_jids([#listitem{type = jid,
action = deny, value = JID} = Item | Items],
JIDs) ->
Match = case Item of
#listitem{match_all = true} ->
true;
#listitem{match_iq = true,
match_message = true,
match_presence_in = true,
match_presence_out = true} ->
true;
_ ->
false
end,
if Match -> listitems_to_jids(Items, [JID | JIDs]);
true -> listitems_to_jids(Items, JIDs)
end;
% Skip Privacy List items than cannot be mapped to Blocking items
listitems_to_jids([_ | Items], JIDs) ->
listitems_to_jids(Items, JIDs).
-spec process_block(iq(), [ljid()]) -> iq().
process_block(#iq{from = From} = IQ, LJIDs) ->
#jid{luser = LUser, lserver = LServer} = From,
case mod_privacy:get_user_list(LUser, LServer, default) of
{error, _} ->
err_db_failure(IQ);
Res ->
{Name, List} = case Res of
error -> {<<"Blocked contacts">>, []};
{ok, NameList} -> NameList
end,
AlreadyBlocked = listitems_to_jids(List, []),
NewList = lists:foldr(
fun(LJID, List1) ->
case lists:member(LJID, AlreadyBlocked) of
true ->
List1;
false ->
[#listitem{type = jid,
value = LJID,
action = deny,
order = 0,
match_all = true}|List1]
end
end, List, LJIDs),
case mod_privacy:set_list(LUser, LServer, Name, NewList) of
ok ->
case (if Res == error ->
mod_privacy:set_default_list(
LUser, LServer, Name);
true ->
ok
end) of
ok ->
mod_privacy:push_list_update(From, Name),
Items = [#block_item{jid = jid:make(LJID)}
|| LJID <- LJIDs],
broadcast_event(From, #block{items = Items}),
xmpp:make_iq_result(IQ);
{error, notfound} ->
?ERROR_MSG("Failed to set default list '~ts': "
"the list should exist, but not found",
[Name]),
err_db_failure(IQ);
{error, _} ->
err_db_failure(IQ)
end;
{error, _} ->
err_db_failure(IQ)
end
end.
-spec process_unblock_all(iq()) -> iq().
process_unblock_all(#iq{from = From} = IQ) ->
#jid{luser = LUser, lserver = LServer} = From,
case mod_privacy:get_user_list(LUser, LServer, default) of
{ok, {Name, List}} ->
NewList = lists:filter(
fun(#listitem{action = A}) ->
A /= deny
end, List),
case mod_privacy:set_list(LUser, LServer, Name, NewList) of
ok ->
mod_privacy:push_list_update(From, Name),
broadcast_event(From, #unblock{}),
xmpp:make_iq_result(IQ);
{error, _} ->
err_db_failure(IQ)
end;
error ->
broadcast_event(From, #unblock{}),
xmpp:make_iq_result(IQ);
{error, _} ->
err_db_failure(IQ)
end.
-spec process_unblock(iq(), [ljid()]) -> iq().
process_unblock(#iq{from = From} = IQ, LJIDs) ->
#jid{luser = LUser, lserver = LServer} = From,
case mod_privacy:get_user_list(LUser, LServer, default) of
{ok, {Name, List}} ->
NewList = lists:filter(
fun(#listitem{action = deny, type = jid,
value = LJID}) ->
not lists:member(LJID, LJIDs);
(_) ->
true
end, List),
case mod_privacy:set_list(LUser, LServer, Name, NewList) of
ok ->
mod_privacy:push_list_update(From, Name),
Items = [#block_item{jid = jid:make(LJID)}
|| LJID <- LJIDs],
broadcast_event(From, #unblock{items = Items}),
xmpp:make_iq_result(IQ);
{error, _} ->
err_db_failure(IQ)
end;
error ->
Items = [#block_item{jid = jid:make(LJID)}
|| LJID <- LJIDs],
broadcast_event(From, #unblock{items = Items}),
xmpp:make_iq_result(IQ);
{error, _} ->
err_db_failure(IQ)
end.
-spec broadcast_event(jid(), block() | unblock()) -> ok.
broadcast_event(#jid{luser = LUser, lserver = LServer} = From, Event) ->
BFrom = jid:remove_resource(From),
lists:foreach(
fun(R) ->
To = jid:replace_resource(From, R),
IQ = #iq{type = set, from = BFrom, to = To,
id = <<"push", (p1_rand:get_string())/binary>>,
sub_els = [Event]},
ejabberd_router:route(IQ)
end, ejabberd_sm:get_user_resources(LUser, LServer)).
-spec process_get(iq()) -> iq().
process_get(#iq{from = #jid{luser = LUser, lserver = LServer}} = IQ) ->
case mod_privacy:get_user_list(LUser, LServer, default) of
{ok, {_, List}} ->
LJIDs = listitems_to_jids(List, []),
Items = [#block_item{jid = jid:make(J)} || J <- LJIDs],
xmpp:make_iq_result(IQ, #block_list{items = Items});
error ->
xmpp:make_iq_result(IQ, #block_list{});
{error, _} ->
err_db_failure(IQ)
end.
-spec err_db_failure(iq()) -> iq().
err_db_failure(#iq{lang = Lang} = IQ) ->
Txt = ?T("Database failure"),
xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang)).
mod_options(_Host) ->
[].
mod_doc() ->
#{desc =>
[?T("The module implements "
"https://xmpp.org/extensions/xep-0191.html"
"[XEP-0191: Blocking Command]."), "",
?T("This module depends on _`mod_privacy`_ where "
"all the configuration is performed.")]}.