mod_pres_counter prevents subscription flood (thanks to Ahmed Omar and Alexey Shchepin)(EJAB-1388)

This commit is contained in:
Badlop 2011-01-31 18:50:49 +01:00
parent 0359e345b0
commit 792512459d
3 changed files with 170 additions and 0 deletions

View File

@ -80,6 +80,7 @@
\newcommand{\modoffline}{\module{mod\_offline}}
\newcommand{\modofflineodbc}{\module{mod\_offline\_odbc}}
\newcommand{\modping}{\module{mod\_ping}}
\newcommand{\modprescounter}{\module{mod\_pres\_counter}}
\newcommand{\modprivacy}{\module{mod\_privacy}}
\newcommand{\modprivacyodbc}{\module{mod\_privacy\_odbc}}
\newcommand{\modprivate}{\module{mod\_private}}
@ -2531,6 +2532,7 @@ The following table lists all modules included in \ejabberd{}.
\hline \ahrefloc{modoffline}{\modoffline{}} & Offline message storage (\xepref{0160}) & \\
\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{modprivate}{\modprivate{}} & Private XML Storage (\xepref{0049}) & \\
@ -3555,6 +3557,39 @@ and if a client does not answer to the ping in less than 32 seconds, its connect
]}.
\end{verbatim}
\makesubsection{modprescounter}{\modprescounter{}}
\ind{modules!\modprescounter{}}
This module detects flood/spam in presence subscription stanza traffic.
If a user sends or receives more of those stanzas in a time interval,
the exceeding stanzas are silently dropped, and warning is logged.
Configuration options:
\begin{description}
\titem{\{count, StanzaNumber\}}\ind{options!count}
The number of subscription presence stanzas
(subscribe, unsubscribe, subscribed, unsubscribed)
allowed for any direction (input or output)
per time interval.
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.
\titem{\{interval, Seconds\}}\ind{options!interval}
The time interval defined in seconds.
The default value is 60.
\end{description}
This example enables the module, and allows up to 5 presence subscription stanzas
to be sent or received by the users in 60 seconds:
\begin{verbatim}
{modules,
[
...
{mod_pres_counter, [{count, 5}, {interval, 60}]},
...
]}.
\end{verbatim}
\makesubsection{modprivacy}{\modprivacy{}}
\ind{modules!\modprivacy{}}\ind{Blocking Communication}\ind{Privacy Rules}\ind{protocols!RFC 3921: XMPP IM}

View File

@ -511,6 +511,7 @@
%%{mod_muc_log,[]},
{mod_offline, [{access_max_user_messages, max_user_offline_messages}]},
{mod_ping, []},
%%{mod_pres_counter,[{count, 5}, {interval, 60}]},
{mod_privacy, []},
{mod_private, []},
%%{mod_proxy65,[]},

134
src/mod_pres_counter.erl Normal file
View File

@ -0,0 +1,134 @@
%%%----------------------------------------------------------------------
%%% File : mod_pres_counter.erl
%%% Author : Ahmed Omar
%%% Purpose : Presence subscription flood prevention
%%% Created : 23 Sep 2010 by Ahmed Omar
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2010 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_pres_counter).
-behavior(gen_mod).
-export([start/2,
stop/1,
check_packet/6]).
-include("ejabberd.hrl").
-include("jlib.hrl").
-record(pres_counter, {dir, start, count, logged = false}).
start(Host, _Opts) ->
ejabberd_hooks:add(privacy_check_packet, Host,
?MODULE, check_packet, 25),
ok.
stop(Host) ->
ejabberd_hooks:delete(privacy_check_packet, Host,
?MODULE, check_packet, 25),
ok.
check_packet(_, _User, Server,
_PrivacyList,
{From, To, {xmlelement, Name, Attrs, _}},
Dir) ->
case Name of
"presence" ->
IsSubscription =
case xml:get_attr_s("type", Attrs) of
"subscribe" -> true;
"subscribed" -> true;
"unsubscribe" -> true;
"unsubscribed" -> true;
_ -> false
end,
if
IsSubscription ->
JID = case Dir of
in -> To;
out -> From
end,
update(Server, JID, Dir);
true ->
allow
end;
_ ->
allow
end.
update(Server, JID, Dir) ->
%% get options
StormCount = gen_mod:get_module_opt(Server, ?MODULE, count, 5),
TimeInterval = gen_mod:get_module_opt(Server, ?MODULE, interval, 60),
{MegaSecs, Secs, _MicroSecs} = now(),
TimeStamp = MegaSecs * 1000000 + Secs,
case read(Dir) of
undefined ->
write(Dir, #pres_counter{dir = Dir,
start = TimeStamp,
count = 1}),
allow;
#pres_counter{start = TimeStart, count = Count, logged = Logged} = R ->
%% record for this key exists, check if we're
%% within TimeInterval seconds, and whether the StormCount is
%% high enough. or else just increment the count.
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 ->
?WARNING_MSG(
"User ~s is being flooded, "
"ignoring received presence subscriptions",
[jlib:jid_to_string(JID)]);
out ->
IP = ejabberd_sm:get_user_ip(
JID#jid.luser,
JID#jid.lserver,
JID#jid.lresource),
?WARNING_MSG(
"Flooder detected: ~s, on IP: ~s "
"ignoring sent presence subscriptions~n",
[jlib:jid_to_string(JID),
jlib:ip_to_list(IP)])
end,
{stop, deny};
true ->
write(Dir, R#pres_counter{
start = TimeStamp,
count = Count + 1}),
allow
end
end.
read(K)->
get({pres_counter, K}).
write(K, V)->
put({pres_counter, K}, V).