aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBadlop <badlop@process-one.net>2011-01-31 18:50:49 +0100
committerBadlop <badlop@process-one.net>2011-01-31 18:52:07 +0100
commit792512459dd8f19d27cd58ad346c0f6b9a5997ec (patch)
treeba3c70c5938bcfbcc41caadfc03be791c77fa122
parentAdd to example config file access_from comment (diff)
mod_pres_counter prevents subscription flood (thanks to Ahmed Omar and Alexey Shchepin)(EJAB-1388)
-rw-r--r--doc/guide.tex35
-rw-r--r--src/ejabberd.cfg.example1
-rw-r--r--src/mod_pres_counter.erl134
3 files changed, 170 insertions, 0 deletions
diff --git a/doc/guide.tex b/doc/guide.tex
index f7048f404..d8afbabd7 100644
--- a/doc/guide.tex
+++ b/doc/guide.tex
@@ -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}
diff --git a/src/ejabberd.cfg.example b/src/ejabberd.cfg.example
index cb6d31355..52ffc6825 100644
--- a/src/ejabberd.cfg.example
+++ b/src/ejabberd.cfg.example
@@ -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,[]},
diff --git a/src/mod_pres_counter.erl b/src/mod_pres_counter.erl
new file mode 100644
index 000000000..368883dbc
--- /dev/null
+++ b/src/mod_pres_counter.erl
@@ -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).