diff --git a/doc/guide.tex b/doc/guide.tex index bb09c1f97..5077630ba 100644 --- a/doc/guide.tex +++ b/doc/guide.tex @@ -78,6 +78,7 @@ \newcommand{\modmulticast}{\module{mod\_multicast}} \newcommand{\modoffline}{\module{mod\_offline}} \newcommand{\modping}{\module{mod\_ping}} +\newcommand{\modprescounter}{\module{mod\_pres\_counter}} \newcommand{\modprivacy}{\module{mod\_privacy}} \newcommand{\modprivate}{\module{mod\_private}} \newcommand{\modproxy}{\module{mod\_proxy65}} @@ -2586,6 +2587,7 @@ The following table lists all modules included in \ejabberd{}. \hline \ahrefloc{modmulticast}{\modmulticast{}} & Multicast Service (\xepref{0033}) & \\ \hline \ahrefloc{modoffline}{\modoffline{}} & Offline message storage (\xepref{0160}) & \\ \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{modprivate}{\modprivate{}} & Private XML Storage (\xepref{0049}) & \\ \hline \ahrefloc{modproxy}{\modproxy{}} & SOCKS5 Bytestreams (\xepref{0065}) & \\ @@ -3589,6 +3591,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 1583cc943..c90a38943 100644 --- a/src/ejabberd.cfg.example +++ b/src/ejabberd.cfg.example @@ -535,6 +535,7 @@ %%{mod_multicast,[]}, {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..f7c38c708 --- /dev/null +++ b/src/mod_pres_counter.erl @@ -0,0 +1,133 @@ +%%%---------------------------------------------------------------------- +%%% 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) -> + HostB = list_to_binary(Host), + ejabberd_hooks:add(privacy_check_packet, HostB, + ?MODULE, check_packet, 25), + ok. + +stop(Host) -> + HostB = list_to_binary(Host), + ejabberd_hooks:delete(privacy_check_packet, HostB, + ?MODULE, check_packet, 25), + ok. + +check_packet(_, _User, Server, + _PrivacyList, + {From, To, Stanza}, + Dir) -> + case exmpp_presence:is_presence(Stanza) of + true -> + IsSubscription = + case exmpp_presence:get_type(Stanza) 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; + false -> + 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", + [exmpp_jid:to_list(JID)]); + out -> + {IP, _Port} = ejabberd_sm:get_user_ip(JID), + ?WARNING_MSG( + "Flooder detected: ~s, on IP: ~s " + "ignoring sent presence subscriptions", + [exmpp_jid:to_list(JID), + inet_parse:ntoa(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).