2010-03-22 08:33:44 +01:00
# File: AntiFlood.pm
2010-03-24 07:47:40 +01:00
# Author: pragma_
2010-03-17 07:36:54 +01:00
#
2010-03-22 08:33:44 +01:00
# Purpose: Keeps track of which nick has said what and when. Used in
# conjunction with OperatorStuff and Quotegrabs for kick/quiet on flood
# and grabbing quotes, respectively.
2010-03-17 07:36:54 +01:00
package PBot::AntiFlood ;
use warnings ;
use strict ;
2010-03-24 07:47:40 +01:00
use vars qw( $VERSION ) ;
$ VERSION = $ PBot:: PBot:: VERSION ;
2010-03-17 07:36:54 +01:00
use Time::HiRes qw( gettimeofday ) ;
2010-03-22 08:33:44 +01:00
use Carp ( ) ;
sub new {
if ( ref ( $ _ [ 1 ] ) eq 'HASH' ) {
Carp:: croak ( "Options to AntiFlood should be key/value pairs, not hash reference" ) ;
}
2010-03-17 07:36:54 +01:00
2010-03-22 08:33:44 +01:00
my ( $ class , % conf ) = @ _ ;
2010-03-17 07:36:54 +01:00
2010-03-22 08:33:44 +01:00
my $ self = bless { } , $ class ;
$ self - > initialize ( % conf ) ;
return $ self ;
}
2010-03-17 07:36:54 +01:00
2010-03-22 08:33:44 +01:00
sub initialize {
my ( $ self , % conf ) = @ _ ;
2010-03-17 07:36:54 +01:00
2010-03-22 08:33:44 +01:00
my $ pbot = delete $ conf { pbot } ;
if ( not defined $ pbot ) {
Carp:: croak ( "Missing pbot reference to AntiFlood" ) ;
}
$ self - > { pbot } = $ pbot ;
$ self - > { FLOOD_CHAT } = 0 ;
$ self - > { FLOOD_JOIN } = 1 ;
$ self - > { flood_msg_count } = 0 ;
$ self - > { last_timestamp } = gettimeofday ;
$ self - > { message_history } = { } ;
2010-03-23 19:24:02 +01:00
$ pbot - > timer - > register ( sub { $ self - > prune_message_history } , 60 * 60 * 1 ) ;
2010-03-22 08:33:44 +01:00
}
2010-03-17 07:36:54 +01:00
2010-06-06 07:19:31 +02:00
sub get_flood_account {
my ( $ self , $ nick , $ user , $ host ) = @ _ ;
return $ nick if exists $ { $ self - > message_history } { $ nick } ;
foreach my $ n ( keys % { $ self - > { message_history } } ) {
my $ userhost = "$user\@$host" ;
if ( $ { $ self - > { message_history } } { $ n } { hostmask } =~ /\Q$userhost\E/i ) {
$ self - > { pbot } - > logger - > log ( "Using existing hostmask found with nick $n\n" ) ;
return $ n ;
}
}
return undef ;
}
2010-03-17 07:36:54 +01:00
sub check_flood {
2010-06-05 08:07:15 +02:00
my ( $ self , $ channel , $ nick , $ user , $ host , $ text , $ max_messages , $ max_time , $ mode ) = @ _ ;
2010-03-17 07:36:54 +01:00
my $ now = gettimeofday ;
$ channel = lc $ channel ;
2010-03-26 06:14:03 +01:00
$ self - > { pbot } - > logger - > log ( sprintf ( "%-14s | %-65s | %s\n" , $ channel , "$nick!$user\@$host" , $ text ) ) ;
2010-06-05 08:07:15 +02:00
2010-03-22 08:33:44 +01:00
return if $ nick eq $ self - > { pbot } - > botnick ;
2010-03-17 07:36:54 +01:00
2010-06-06 07:19:31 +02:00
my $ account = $ self - > get_flood_account ( $ nick , $ user , $ host ) ;
if ( defined $ account ) {
2010-03-22 08:33:44 +01:00
#$self->{pbot}->logger->log("nick exists\n");
2010-03-17 07:36:54 +01:00
2010-06-06 07:19:31 +02:00
if ( not exists $ { $ self - > message_history } { $ account } { $ channel } ) {
2010-03-22 08:33:44 +01:00
#$self->{pbot}->logger->log("adding new channel for existing nick\n");
2010-06-06 07:19:31 +02:00
$ { $ self - > message_history } { $ account } { $ channel } { offenses } = 0 ;
$ { $ self - > message_history } { $ account } { $ channel } { join_watch } = $ mode ; # FLOOD_CHAT = 0; FLOOD_JOIN = 1
$ { $ self - > message_history } { $ account } { $ channel } { messages } = [] ;
2010-03-17 07:36:54 +01:00
}
2010-03-22 08:33:44 +01:00
#$self->{pbot}->logger->log("appending new message\n");
2010-06-05 08:07:15 +02:00
2010-06-06 07:19:31 +02:00
push ( @ { $ { $ self - > message_history } { $ account } { $ channel } { messages } } , { timestamp = > $ now , msg = > $ text , mode = > $ mode } ) ;
2010-03-17 07:36:54 +01:00
2010-06-06 07:19:31 +02:00
my $ length = $# { $ { $ self - > message_history } { $ account } { $ channel } { messages } } + 1 ;
2010-03-17 07:36:54 +01:00
2010-06-05 08:07:15 +02:00
if ( $ max_messages > $ self - > { pbot } - > { MAX_NICK_MESSAGES } ) {
$ self - > { pbot } - > logger - > log ( "Warning: max_messages greater than MAX_NICK_MESSAGES; truncating.\n" ) ;
$ max_messages = $ self - > { pbot } - > { MAX_NICK_MESSAGES } ;
}
2010-03-17 07:36:54 +01:00
2010-03-22 08:33:44 +01:00
if ( $ length >= $ self - > { pbot } - > { MAX_NICK_MESSAGES } ) {
2010-06-06 07:19:31 +02:00
my % msg = % { shift ( @ { $ { $ self - > message_history } { $ account } { $ channel } { messages } } ) } ;
2010-03-22 08:33:44 +01:00
#$self->{pbot}->logger->log("shifting message off top: $msg{msg}, $msg{timestamp}\n");
2010-03-17 07:36:54 +01:00
$ length - - ;
}
2010-03-23 19:24:02 +01:00
return if ( $ channel =~ /^#/ ) and ( not exists $ { $ self - > { pbot } - > channels - > channels } { $ channel } or $ { $ self - > { pbot } - > channels - > channels } { $ channel } { is_op } == 0 ) ;
2010-03-17 07:36:54 +01:00
2010-06-05 08:07:15 +02:00
if ( $ length >= $ max_messages ) {
$ self - > { pbot } - > logger - > log ( "More than $max_messages messages spoken, comparing time differences ($max_time)\n" ) ;
2010-06-06 07:19:31 +02:00
my % msg = % { @ { $ { $ self - > message_history } { $ account } { $ channel } { messages } } [ $ length - $ max_messages ] } ;
my % last = % { @ { $ { $ self - > message_history } { $ account } { $ channel } { messages } } [ $ length - 1 ] } ;
2010-03-17 07:36:54 +01:00
2010-06-05 19:45:41 +02:00
$ self - > { pbot } - > logger - > log ( "Comparing " . int ( $ last { timestamp } ) . " against " . int ( $ msg { timestamp } ) . ": " . ( int ( $ last { timestamp } - $ msg { timestamp } ) ) . " seconds\n" ) ;
2010-06-05 08:07:15 +02:00
if ( $ last { timestamp } - $ msg { timestamp } <= $ max_time && not $ self - > { pbot } - > admins - > loggedin ( $ channel , "$nick!$user\@$host" ) ) {
if ( $ mode == $ self - > { FLOOD_JOIN } ) {
2010-06-06 07:19:31 +02:00
if ( $ { $ self - > message_history } { $ account } { $ channel } { join_watch } >= $ max_messages ) {
2010-06-06 02:35:24 +02:00
$ self - > { pbot } - > chanops - > quiet_user_timed ( "*!$user\@$host" , $ channel , 60 * 60 * 2 ) ;
$ self - > { pbot } - > logger - > log ( "$nick!$user\@$host banned for two hours due to join flooding.\n" ) ;
$ self - > { pbot } - > conn - > privmsg ( $ nick , "You have been banned from $channel for two hours due to join flooding." ) ;
2010-06-06 07:19:31 +02:00
$ { $ self - > message_history } { $ account } { $ channel } { join_watch } = $ max_messages - 2 ; # give them a chance to rejoin
2010-06-05 08:07:15 +02:00
}
} elsif ( $ mode == $ self - > { FLOOD_CHAT } ) {
2010-06-06 07:19:31 +02:00
$ { $ self - > message_history } { $ account } { $ channel } { offenses } + + ;
my $ length = $ { $ self - > message_history } { $ account } { $ channel } { offenses } ** $ { $ self - > message_history } { $ account } { $ channel } { offenses } * $ { $ self - > message_history } { $ account } { $ channel } { offenses } * 30 ;
2010-06-05 08:07:15 +02:00
if ( $ channel =~ /^#/ ) { #channel flood (opposed to private message or otherwise)
return if exists $ self - > { pbot } - > chanops - > { quieted_masks } - > { "*!*\@$host" } ;
if ( $ mode == $ self - > { FLOOD_CHAT } ) {
$ self - > { pbot } - > chanops - > quiet_user_timed ( "*!$user\@$host" , $ channel , $ length ) ;
2010-06-06 07:19:31 +02:00
$ self - > { pbot } - > logger - > log ( "$nick $channel flood offense ${ $self->message_history }{$account}{$channel}{offenses} earned $length second quiet\n" ) ;
2010-06-05 08:07:15 +02:00
if ( $ length < 1000 ) {
$ length = "$length seconds" ;
} else {
$ length = "a little while" ;
}
$ self - > { pbot } - > conn - > privmsg ( $ nick , "You have been quieted due to flooding. Please use a web paste service such as http://codepad.org for lengthy pastes. You will be allowed to speak again in $length." ) ;
}
} else { # private message flood
return if exists $ self - > { pbot } - > ignorelist - > { ignore_list } - > { "$nick!$user\@$host" } { $ channel } ;
2010-06-06 07:19:31 +02:00
$ self - > { pbot } - > logger - > log ( "$nick msg flood offense ${ $self->message_history }{$account}{$channel}{offenses} earned $length second ignore\n" ) ;
2010-06-05 08:07:15 +02:00
$ self - > { pbot } - > { ignorelistcmds } - > ignore_user ( "" , "floodcontrol" , "" , "" , "$nick!$user\@$host $channel $length" ) ;
2010-05-30 04:02:29 +02:00
if ( $ length < 1000 ) {
$ length = "$length seconds" ;
} else {
$ length = "a little while" ;
}
2010-06-05 08:07:15 +02:00
$ self - > { pbot } - > conn - > privmsg ( $ nick , "You have used too many commands in too short a time period, you have been ignored for $length." ) ;
2010-05-30 04:02:29 +02:00
}
2010-03-17 07:36:54 +01:00
}
}
}
2010-06-05 08:07:15 +02:00
if ( $ mode == $ self - > { FLOOD_JOIN } ) {
2010-06-06 07:19:31 +02:00
$ { $ self - > message_history } { $ account } { $ channel } { join_watch } + + ;
$ self - > { pbot } - > logger - > log ( "$nick $channel joinwatch adjusted: ${ $self->message_history }{$account}{$channel}{join_watch}\n" ) ;
2010-06-05 08:07:15 +02:00
} elsif ( $ mode == $ self - > { FLOOD_CHAT } ) {
2010-06-06 07:19:31 +02:00
$ { $ self - > message_history } { $ account } { $ channel } { join_watch } = 0 ;
2010-06-05 08:07:15 +02:00
}
2010-03-17 07:36:54 +01:00
} else {
2010-03-22 08:33:44 +01:00
#$self->{pbot}->logger->log("brand new nick addition\n");
2010-03-17 07:36:54 +01:00
# new addition
2010-06-05 08:07:15 +02:00
$ { $ self - > message_history } { $ nick } { $ channel } { offenses } = 0 ;
$ { $ self - > message_history } { $ nick } { $ channel } { join_watch } = $ mode ; # FLOOD_CHAT = 0; FLOOD_JOIN = 1
2010-03-22 08:33:44 +01:00
$ { $ self - > message_history } { $ nick } { $ channel } { messages } = [] ;
2010-06-06 07:19:31 +02:00
$ { $ self - > message_history } { $ nick } { hostmask } = "$nick!$user\@$host" ;
2010-03-22 08:33:44 +01:00
push ( @ { $ { $ self - > message_history } { $ nick } { $ channel } { messages } } , { timestamp = > $ now , msg = > $ text , mode = > $ mode } ) ;
2010-03-17 07:36:54 +01:00
}
}
2010-03-22 08:33:44 +01:00
sub message_history {
my $ self = shift ;
return $ self - > { message_history } ;
}
2010-03-23 19:24:02 +01:00
sub prune_message_history {
my $ self = shift ;
$ self - > { pbot } - > logger - > log ( "Pruning message history . . .\n" ) ;
2010-03-29 10:39:48 +02:00
foreach my $ nick ( keys % { $ self - > { message_history } } ) {
foreach my $ channel ( keys % { $ self - > { message_history } - > { $ nick } } )
2010-03-23 19:24:02 +01:00
{
2010-04-10 00:53:17 +02:00
#$self->{pbot}->logger->log("Checking [$nick][$channel]\n");
2010-03-29 10:39:48 +02:00
my $ length = $# { $ self - > { message_history } - > { $ nick } { $ channel } { messages } } + 1 ;
my % last = % { @ { $ self - > { message_history } - > { $ nick } { $ channel } { messages } } [ $ length - 1 ] } ;
2010-03-23 19:24:02 +01:00
2010-05-30 04:02:29 +02:00
if ( gettimeofday - $ last { timestamp } >= 60 * 60 * 24 * 3 ) {
$ self - > { pbot } - > logger - > log ( "$nick in $channel hasn't spoken in three days, removing message history.\n" ) ;
2010-03-29 10:39:48 +02:00
delete $ self - > { message_history } - > { $ nick } { $ channel } ;
2010-03-23 19:24:02 +01:00
}
}
}
}
2010-03-17 07:36:54 +01:00
1 ;