2011-01-31 18:50:49 +01:00
|
|
|
%%%----------------------------------------------------------------------
|
|
|
|
%%% File : mod_pres_counter.erl
|
|
|
|
%%% Author : Ahmed Omar
|
|
|
|
%%% Purpose : Presence subscription flood prevention
|
|
|
|
%%% Created : 23 Sep 2010 by Ahmed Omar
|
|
|
|
%%%
|
|
|
|
%%%
|
2023-01-09 17:09:06 +01:00
|
|
|
%%% ejabberd, Copyright (C) 2002-2023 ProcessOne
|
2011-01-31 18:50:49 +01:00
|
|
|
%%%
|
|
|
|
%%% 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.
|
|
|
|
%%%
|
2014-02-22 11:27:40 +01:00
|
|
|
%%% 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.
|
2011-01-31 18:50:49 +01:00
|
|
|
%%%
|
|
|
|
%%%----------------------------------------------------------------------
|
|
|
|
|
|
|
|
-module(mod_pres_counter).
|
|
|
|
|
2018-01-31 14:57:43 +01:00
|
|
|
-behaviour(gen_mod).
|
2011-01-31 18:50:49 +01:00
|
|
|
|
2017-02-22 17:46:47 +01:00
|
|
|
-export([start/2, stop/1, reload/3, check_packet/4,
|
2020-01-08 10:24:51 +01:00
|
|
|
mod_opt_type/1, mod_options/1, depends/2, mod_doc/0]).
|
2011-01-31 18:50:49 +01:00
|
|
|
|
2013-04-08 11:12:54 +02:00
|
|
|
-include("logger.hrl").
|
2020-01-08 10:24:51 +01:00
|
|
|
-include("translate.hrl").
|
2020-09-03 13:45:57 +02:00
|
|
|
-include_lib("xmpp/include/xmpp.hrl").
|
2011-01-31 18:50:49 +01:00
|
|
|
|
2013-03-14 10:33:02 +01:00
|
|
|
-record(pres_counter,
|
|
|
|
{dir, start, count, logged = false}).
|
2011-01-31 18:50:49 +01:00
|
|
|
|
2023-08-04 17:53:50 +02:00
|
|
|
start(_Host, _Opts) ->
|
|
|
|
{ok, [{hook, privacy_check_packet, check_packet, 25}]}.
|
2011-01-31 18:50:49 +01:00
|
|
|
|
2023-08-04 17:53:50 +02:00
|
|
|
stop(_Host) ->
|
2011-01-31 18:50:49 +01:00
|
|
|
ok.
|
|
|
|
|
2017-02-22 17:46:47 +01:00
|
|
|
reload(_Host, _NewOpts, _OldOpts) ->
|
|
|
|
ok.
|
|
|
|
|
2016-07-07 11:17:38 +02:00
|
|
|
depends(_Host, _Opts) ->
|
|
|
|
[].
|
|
|
|
|
2017-01-09 15:02:17 +01:00
|
|
|
-spec check_packet(allow | deny, ejabberd_c2s:state() | jid(),
|
|
|
|
stanza(), in | out) -> allow | deny.
|
|
|
|
check_packet(Acc, #{jid := JID}, Packet, Dir) ->
|
|
|
|
check_packet(Acc, JID, Packet, Dir);
|
|
|
|
check_packet(_, #jid{lserver = LServer},
|
|
|
|
#presence{from = From, to = To, type = Type}, Dir) ->
|
2016-07-29 12:21:00 +02:00
|
|
|
IsSubscription = case Type of
|
|
|
|
subscribe -> true;
|
|
|
|
subscribed -> true;
|
|
|
|
unsubscribe -> true;
|
|
|
|
unsubscribed -> true;
|
|
|
|
_ -> false
|
|
|
|
end,
|
|
|
|
if IsSubscription ->
|
|
|
|
JID = case Dir of
|
|
|
|
in -> To;
|
|
|
|
out -> From
|
|
|
|
end,
|
2017-01-09 15:02:17 +01:00
|
|
|
update(LServer, JID, Dir);
|
2016-07-29 12:21:00 +02:00
|
|
|
true -> allow
|
|
|
|
end;
|
2017-01-09 15:02:17 +01:00
|
|
|
check_packet(Acc, _, _, _) ->
|
|
|
|
Acc.
|
2011-01-31 18:50:49 +01:00
|
|
|
|
|
|
|
update(Server, JID, Dir) ->
|
2019-06-14 11:33:26 +02:00
|
|
|
StormCount = mod_pres_counter_opt:count(Server),
|
|
|
|
TimeInterval = mod_pres_counter_opt:interval(Server),
|
2019-07-17 21:15:56 +02:00
|
|
|
TimeStamp = erlang:system_time(millisecond),
|
2011-01-31 18:50:49 +01:00
|
|
|
case read(Dir) of
|
2013-03-14 10:33:02 +01:00
|
|
|
undefined ->
|
|
|
|
write(Dir,
|
|
|
|
#pres_counter{dir = Dir, start = TimeStamp, count = 1}),
|
|
|
|
allow;
|
|
|
|
#pres_counter{start = TimeStart, count = Count,
|
|
|
|
logged = Logged} =
|
|
|
|
R ->
|
|
|
|
if TimeStamp - TimeStart > TimeInterval ->
|
|
|
|
write(Dir,
|
|
|
|
R#pres_counter{start = TimeStamp, count = 1}),
|
|
|
|
allow;
|
|
|
|
(Count =:= StormCount) and Logged -> {stop, deny};
|
|
|
|
Count =:= StormCount ->
|
|
|
|
write(Dir, R#pres_counter{logged = true}),
|
|
|
|
case Dir of
|
|
|
|
in ->
|
2019-09-23 14:17:20 +02:00
|
|
|
?WARNING_MSG("User ~ts is being flooded, ignoring received "
|
2013-03-14 10:33:02 +01:00
|
|
|
"presence subscriptions",
|
2017-02-26 08:07:12 +01:00
|
|
|
[jid:encode(JID)]);
|
2013-03-14 10:33:02 +01:00
|
|
|
out ->
|
|
|
|
IP = ejabberd_sm:get_user_ip(JID#jid.luser,
|
|
|
|
JID#jid.lserver,
|
|
|
|
JID#jid.lresource),
|
2019-09-23 14:17:20 +02:00
|
|
|
?WARNING_MSG("Flooder detected: ~ts, on IP: ~ts ignoring "
|
2013-03-14 10:33:02 +01:00
|
|
|
"sent presence subscriptions~n",
|
2017-02-26 08:07:12 +01:00
|
|
|
[jid:encode(JID),
|
2017-04-11 12:13:58 +02:00
|
|
|
misc:ip_to_list(IP)])
|
2013-03-14 10:33:02 +01:00
|
|
|
end,
|
|
|
|
{stop, deny};
|
|
|
|
true ->
|
|
|
|
write(Dir,
|
|
|
|
R#pres_counter{start = TimeStamp, count = Count + 1}),
|
|
|
|
allow
|
|
|
|
end
|
2011-01-31 18:50:49 +01:00
|
|
|
end.
|
|
|
|
|
2013-03-14 10:33:02 +01:00
|
|
|
read(K) -> get({pres_counter, K}).
|
2011-01-31 18:50:49 +01:00
|
|
|
|
2013-03-14 10:33:02 +01:00
|
|
|
write(K, V) -> put({pres_counter, K}, V).
|
2015-06-01 14:38:27 +02:00
|
|
|
|
|
|
|
mod_opt_type(count) ->
|
2019-06-14 11:33:26 +02:00
|
|
|
econf:pos_int();
|
2015-06-01 14:38:27 +02:00
|
|
|
mod_opt_type(interval) ->
|
2019-07-17 21:15:56 +02:00
|
|
|
econf:timeout(second).
|
2018-01-23 08:54:52 +01:00
|
|
|
|
|
|
|
mod_options(_) ->
|
2019-07-17 21:15:56 +02:00
|
|
|
[{count, 5}, {interval, timer:seconds(60)}].
|
2020-01-08 10:24:51 +01:00
|
|
|
|
|
|
|
mod_doc() ->
|
|
|
|
#{desc =>
|
|
|
|
?T("This module detects flood/spam in presence "
|
|
|
|
"subscriptions traffic. If a user sends or receives "
|
|
|
|
"more of those stanzas in a given time interval, "
|
|
|
|
"the exceeding stanzas are silently dropped, and a "
|
|
|
|
"warning is logged."),
|
|
|
|
opts =>
|
|
|
|
[{count,
|
|
|
|
#{value => ?T("Number"),
|
|
|
|
desc =>
|
|
|
|
?T("The number of subscription presence stanzas "
|
|
|
|
"(subscribe, unsubscribe, subscribed, unsubscribed) "
|
|
|
|
"allowed for any direction (input or output) per time "
|
|
|
|
"defined in 'interval' option. Please note that two "
|
|
|
|
"users subscribing to each other usually generate 4 "
|
|
|
|
"stanzas, so the recommended value is '4' or more. "
|
|
|
|
"The default value is '5'.")}},
|
|
|
|
{interval,
|
|
|
|
#{value => "timeout()",
|
|
|
|
desc =>
|
|
|
|
?T("The time interval. The default value is '1' minute.")}}],
|
|
|
|
example =>
|
|
|
|
["modules:",
|
|
|
|
" ...",
|
|
|
|
" mod_pres_counter:",
|
|
|
|
" count: 5",
|
|
|
|
" interval: 30 secs",
|
|
|
|
" ..."]}.
|