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
#
2011-02-13 09:02:27 +01:00
# Purpose: Keeps track of who has said what and when. Used in
# conjunction with ChanOps and Quotegrabs for kick/ban on flood
2010-03-22 08:33:44 +01:00
# and grabbing quotes, respectively.
2010-03-17 07:36:54 +01:00
package PBot::AntiFlood ;
use warnings ;
use strict ;
2010-06-18 06:17:30 +02:00
use feature 'switch' ;
2010-03-24 07:47:40 +01:00
use vars qw( $VERSION ) ;
$ VERSION = $ PBot:: PBot:: VERSION ;
2010-03-17 07:36:54 +01:00
2011-12-11 23:56:57 +01:00
use PBot::DualIndexHashObject ;
2011-01-25 00:56:55 +01:00
use PBot::LagChecker ;
2011-01-22 10:26:04 +01:00
use Time::HiRes qw( gettimeofday tv_interval ) ;
2010-06-27 07:36:58 +02:00
use Time::Duration ;
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 ;
2010-06-19 21:19:56 +02:00
$ self - > { FLOOD_IGNORE } = - 1 ;
2010-03-22 08:33:44 +01:00
$ self - > { FLOOD_CHAT } = 0 ;
$ self - > { FLOOD_JOIN } = 1 ;
$ self - > { message_history } = { } ;
2010-03-23 19:24:02 +01:00
2011-12-11 23:56:57 +01:00
my $ filename = delete $ conf { filename } // $ self - > { pbot } - > { data_dir } . '/ban_whitelist' ;
$ self - > { ban_whitelist } = PBot::DualIndexHashObject - > new ( name = > 'BanWhitelist' , filename = > $ filename ) ;
$ self - > { ban_whitelist } - > load ;
2010-03-23 19:24:02 +01:00
$ pbot - > timer - > register ( sub { $ self - > prune_message_history } , 60 * 60 * 1 ) ;
2010-06-16 03:55:29 +02:00
2011-12-11 23:56:57 +01:00
$ pbot - > commands - > register ( sub { return $ self - > unbanme ( @ _ ) } , "unbanme" , 0 ) ;
$ pbot - > commands - > register ( sub { return $ self - > whitelist ( @ _ ) } , "whitelist" , 10 ) ;
}
sub ban_whitelisted {
2011-12-12 06:39:38 +01:00
my ( $ self , $ channel , $ mask ) = @ _ ;
$ channel = lc $ channel ;
$ mask = lc $ mask ;
2011-12-11 23:56:57 +01:00
2011-12-12 05:17:03 +01:00
$ self - > { pbot } - > logger - > log ( "whitelist check: $channel, $mask\n" ) ;
2011-12-12 05:21:59 +01:00
return defined $ self - > { ban_whitelist } - > hash - > { $ channel } - > { $ mask } - > { ban_whitelisted } ? 1 : 0 ;
2011-12-11 23:56:57 +01:00
}
sub whitelist {
my ( $ self , $ from , $ nick , $ user , $ host , $ arguments ) = @ _ ;
$ arguments = lc $ arguments ;
my ( $ command , $ args ) = split / / , $ arguments , 2 ;
return "Usage: whitelist <command>, where commands are: list/show, add, remove" if not defined $ command ;
given ( $ command ) {
when ( $ _ eq "list" or $ _ eq "show" ) {
my $ text = "Ban whitelist:\n" ;
my $ entries = 0 ;
foreach my $ channel ( keys % { $ self - > { ban_whitelist } - > hash } ) {
$ text . = " $channel:\n" ;
foreach my $ mask ( keys % { $ self - > { ban_whitelist } - > hash - > { $ channel } } ) {
$ text . = " $mask,\n" ;
$ entries + + ;
}
}
$ text . = "none" if $ entries == 0 ;
return $ text ;
}
when ( "add" ) {
my ( $ channel , $ mask ) = split / / , $ args , 2 ;
return "Usage: whitelist add <channel> <mask>" if not defined $ channel or not defined $ mask ;
$ self - > { ban_whitelist } - > hash - > { $ channel } - > { $ mask } - > { ban_whitelisted } = 1 ;
$ self - > { ban_whitelist } - > hash - > { $ channel } - > { $ mask } - > { owner } = "$nick!$user\@$host" ;
$ self - > { ban_whitelist } - > hash - > { $ channel } - > { $ mask } - > { created_on } = gettimeofday ;
$ self - > { ban_whitelist } - > save ;
return "$mask whitelisted in channel $channel" ;
}
when ( "remove" ) {
my ( $ channel , $ mask ) = split / / , $ args , 2 ;
return "Usage: whitelist remove <channel> <mask>" if not defined $ channel or not defined $ mask ;
if ( not defined $ self - > { ban_whitelist } - > hash - > { $ channel } ) {
return "No whitelists for channel $channel" ;
}
if ( not defined $ self - > { ban_whitelist } - > hash - > { $ channel } - > { $ mask } ) {
return "No such whitelist $mask for channel $channel" ;
}
delete $ self - > { ban_whitelist } - > hash - > { $ channel } - > { $ mask } ;
delete $ self - > { ban_whitelist } - > hash - > { $ channel } if keys % { $ self - > { ban_whitelist } - > hash - > { $ channel } } == 0 ;
$ self - > { ban_whitelist } - > save ;
return "$mask whitelist removed from channel $channel" ;
}
default {
return "Unknown command '$command'; commands are: list/show, add, remove" ;
}
}
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 ) = @ _ ;
2011-02-11 03:46:35 +01:00
return "$nick!$user\@$host" if exists $ self - > message_history - > { "$nick!$user\@$host" } ;
2010-06-06 07:19:31 +02:00
2011-02-11 03:46:35 +01:00
foreach my $ mask ( keys % { $ self - > message_history } ) {
2011-02-11 07:52:43 +01:00
# check if foo!bar@baz matches foo!*@*; e.g., same nick, but possibly different user@host
2011-02-12 06:30:44 +01:00
# (usually logging into nickserv or a dynamic ip address, but could possibly be attempted nick hijacking)
2011-02-11 07:52:43 +01:00
2011-02-11 03:46:35 +01:00
if ( $ mask =~ m/^\Q$nick\E!.*/i ) {
2011-02-11 07:52:43 +01:00
$ self - > { pbot } - > logger - > log ( "anti-flood: [get-account] $nick!$user\@$host seen previously as $mask\n" ) ;
2011-02-11 03:46:35 +01:00
}
2011-02-11 07:52:43 +01:00
# check if foo!bar@baz matches *!bar@baz; e.g., same user@host, but different nick
# (usually alternate-nicks due to rejoining)
2011-02-11 03:46:35 +01:00
if ( $ mask =~ m/!\Q$user\E@\Q$host\E$/i ) {
2011-02-13 09:02:27 +01:00
$ self - > { pbot } - > logger - > log ( "anti-flood: [get-account] $nick!$user\@$host linked to $mask\n" ) ;
$ self - > { message_history } - > { "$nick!$user\@$host" } = $ self - > { message_history } - > { $ mask } ;
2011-12-12 01:19:34 +01:00
$ self - > check_nickserv_accounts ( $ nick , $ self - > { message_history } - > { $ mask } - > { nickserv_account } ) ;
2011-02-13 09:02:27 +01:00
return "$nick!$user\@$host" ;
2010-06-06 07:19:31 +02:00
}
}
return undef ;
}
2010-06-19 21:19:56 +02:00
sub add_message {
my ( $ self , $ account , $ channel , $ text , $ mode ) = @ _ ;
my $ now = gettimeofday ;
2010-06-21 05:18:39 +02:00
return undef if $ channel =~ /[@!]/ ; # ignore QUIT messages from nick!user@host channels
2010-06-19 21:19:56 +02:00
#$self->{pbot}->logger->log("appending new message\n");
2011-02-11 03:46:35 +01:00
push ( @ { $ self - > message_history - > { $ account } - > { channels } - > { $ channel } { messages } } , { timestamp = > $ now , msg = > $ text , mode = > $ mode } ) ;
2010-06-19 21:19:56 +02:00
2011-02-11 03:46:35 +01:00
my $ length = $# { $ self - > message_history - > { $ account } - > { channels } - > { $ channel } { messages } } + 1 ;
2010-06-21 05:18:39 +02:00
if ( $ mode == $ self - > { FLOOD_JOIN } ) {
if ( $ text =~ /^JOIN/ ) {
2011-02-11 03:46:35 +01:00
$ self - > message_history - > { $ account } - > { channels } - > { $ channel } { join_watch } + + ;
2010-06-21 05:18:39 +02:00
} else {
# PART or QUIT
# check QUIT message for netsplits, and decrement joinwatch if found
if ( $ text =~ /^QUIT .*\.net .*\.split/ ) {
2011-02-11 03:46:35 +01:00
$ self - > message_history - > { $ account } - > { channels } - > { $ channel } { join_watch } - - ;
$ self - > message_history - > { $ account } - > { channels } - > { $ channel } { join_watch } = 0 if $ self - > message_history - > { $ account } - > { channels } - > { $ channel } { join_watch } < 0 ;
$ self - > { pbot } - > logger - > log ( "$account $channel joinwatch adjusted: " . $ self - > message_history - > { $ account } - > { channels } - > { $ channel } { join_watch } . "\n" ) ;
$ self - > message_history - > { $ account } - > { channels } - > { $ channel } { messages } - > [ $ length - 1 ] { mode } = $ self - > { FLOOD_IGNORE } ;
2010-06-21 05:18:39 +02:00
}
2011-01-22 05:17:35 +01:00
# check QUIT message for Ping timeout or Excess Flood
elsif ( $ text =~ /^QUIT Ping timeout/ or $ text =~ /^QUIT Excess Flood/ ) {
2011-02-11 03:46:35 +01:00
# ignore these (used to treat aggressively)
$ self - > message_history - > { $ account } - > { channels } - > { $ channel } { messages } - > [ $ length - 1 ] { mode } = $ self - > { FLOOD_IGNORE } ;
2010-06-21 05:18:39 +02:00
} else {
# some other type of QUIT or PART
2011-02-11 03:46:35 +01:00
$ self - > message_history - > { $ account } - > { channels } - > { $ channel } { messages } - > [ $ length - 1 ] { mode } = $ self - > { FLOOD_IGNORE } ;
2010-06-21 05:18:39 +02:00
}
}
} elsif ( $ mode == $ self - > { FLOOD_CHAT } ) {
# reset joinwatch if they send a message
2011-02-11 03:46:35 +01:00
$ self - > message_history - > { $ account } - > { channels } - > { $ channel } { join_watch } = 0 ;
2010-06-21 05:18:39 +02:00
}
2010-06-19 21:19:56 +02:00
2011-02-11 03:46:35 +01:00
# keep only MAX_NICK_MESSAGES message history per channel
2010-06-19 21:19:56 +02:00
if ( $ length >= $ self - > { pbot } - > { MAX_NICK_MESSAGES } ) {
2011-02-11 03:46:35 +01:00
my % msg = % { shift ( @ { $ self - > message_history - > { $ account } - > { channels } - > { $ channel } { messages } } ) } ;
2010-06-19 21:19:56 +02:00
#$self->{pbot}->logger->log("shifting message off top: $msg{msg}, $msg{timestamp}\n");
$ length - - ;
}
return $ length ;
}
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 ) = @ _ ;
2011-02-11 03:46:35 +01:00
my $ mask = lc "$nick!$user\@$host" ;
2010-03-17 07:36:54 +01:00
my $ now = gettimeofday ;
2011-02-11 03:46:35 +01:00
$ self - > { pbot } - > logger - > log ( sprintf ( "%-14s | %-65s | %s\n" , $ channel , $ mask , $ text ) ) ;
2010-06-05 08:07:15 +02:00
2010-06-21 05:18:39 +02:00
$ nick = lc $ nick ;
$ user = lc $ user ;
$ host = lc $ host ;
$ channel = lc $ channel ;
return if $ nick eq lc $ 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 ) ;
2010-06-12 06:27:58 +02:00
if ( not defined $ account ) {
# new addition
2011-02-11 03:46:35 +01:00
#$self->{pbot}->logger->log("brand new account addition\n");
#$self->message_history->{$mask} = {};
$ self - > message_history - > { $ mask } - > { channels } = { } ;
$ self - > { pbot } - > conn - > whois ( $ nick ) ;
2010-03-17 07:36:54 +01:00
2011-02-11 03:46:35 +01:00
$ account = $ mask ;
2010-06-12 06:27:58 +02:00
}
2010-06-05 08:07:15 +02:00
2010-06-21 05:18:39 +02:00
# handle QUIT events
# (these events come from $channel nick!user@host, not a specific channel or nick,
# so they need to be dispatched to all channels the bot exists on)
if ( $ mode == $ self - > { FLOOD_JOIN } and $ text =~ /^QUIT/ ) {
2011-02-11 03:46:35 +01:00
foreach my $ chan ( lc keys % { $ self - > { pbot } - > channels - > channels - > hash } ) {
2010-06-12 06:27:58 +02:00
2010-06-21 05:18:39 +02:00
next if $ chan eq $ channel ; # skip nick!user@host "channel"
2010-06-12 06:27:58 +02:00
2011-02-11 03:46:35 +01:00
if ( not exists $ self - > message_history - > { $ account } - > { channels } - > { $ chan } ) {
#$self->{pbot}->logger->log("adding new channel for existing account\n");
$ self - > message_history - > { $ account } - > { channels } - > { $ chan } { offenses } = 0 ;
$ self - > message_history - > { $ account } - > { channels } - > { $ chan } { last_offense_timestamp } = 0 ;
$ self - > message_history - > { $ account } - > { channels } - > { $ chan } { join_watch } = 0 ;
$ self - > message_history - > { $ account } - > { channels } - > { $ chan } { messages } = [] ;
2010-06-12 06:38:38 +02:00
}
2010-06-21 05:18:39 +02:00
$ self - > add_message ( $ account , $ chan , $ text , $ mode ) ;
2010-06-10 22:26:09 +02:00
}
2010-06-21 05:18:39 +02:00
2011-02-11 03:46:35 +01:00
# don't do flood processing for QUIT events
2010-06-21 05:18:39 +02:00
return ;
2010-06-12 06:27:58 +02:00
}
2010-06-10 22:26:09 +02:00
2011-02-11 03:46:35 +01:00
if ( not exists $ self - > message_history - > { $ account } - > { channels } - > { $ channel } ) {
2010-06-21 13:04:07 +02:00
#$self->{pbot}->logger->log("adding new channel for existing nick\n");
2011-02-11 03:46:35 +01:00
$ self - > message_history - > { $ account } - > { channels } - > { $ channel } { offenses } = 0 ;
$ self - > message_history - > { $ account } - > { channels } - > { $ channel } { last_offense_timestamp } = 0 ;
$ self - > message_history - > { $ account } - > { channels } - > { $ channel } { join_watch } = 0 ;
$ self - > message_history - > { $ account } - > { channels } - > { $ channel } { messages } = [] ;
2010-06-21 13:04:07 +02:00
}
2010-06-21 05:18:39 +02:00
my $ length = $ self - > add_message ( $ account , $ channel , $ text , $ mode ) ;
return if not defined $ length ;
# do not do flood processing if channel is not in bot's channel list or bot is not set as chanop for the channel
return if ( $ channel =~ /^#/ ) and ( not exists $ self - > { pbot } - > channels - > channels - > hash - > { $ channel } or $ self - > { pbot } - > channels - > channels - > hash - > { $ channel } { chanop } == 0 ) ;
2011-01-25 00:56:55 +01:00
# do not do flood enforcement for this event if bot is lagging
if ( $ self - > { pbot } - > lagchecker - > lagging ) {
$ self - > { pbot } - > logger - > log ( "Disregarding enforcement of anti-flood due to lag: " . $ self - > { pbot } - > lagchecker - > lagstring . "\n" ) ;
return ;
}
2011-01-22 10:39:39 +01:00
2010-06-12 09:48:01 +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-06-12 06:27:58 +02:00
if ( $ max_messages > 0 and $ length >= $ max_messages ) {
2011-12-04 02:13:21 +01:00
# $self->{pbot}->logger->log("More than $max_messages messages, comparing time differences ($max_time)\n") if $mode == $self->{FLOOD_JOIN};
2010-06-16 03:55:29 +02:00
2010-06-19 21:19:56 +02:00
my % msg ;
if ( $ mode == $ self - > { FLOOD_CHAT } ) {
2011-02-11 03:46:35 +01:00
% msg = % { @ { $ self - > message_history - > { $ account } - > { channels } - > { $ channel } { messages } } [ $ length - $ max_messages ] } ;
}
elsif ( $ mode == $ self - > { FLOOD_JOIN } ) {
2010-06-19 21:19:56 +02:00
my $ count = 0 ;
my $ i = $ length - 1 ;
2011-12-04 02:13:21 +01:00
# $self->{pbot}->logger->log("Checking flood history, i = $i\n") if $self->message_history->{$account}->{channels}->{$channel}{join_watch} >= $max_messages;
2010-06-19 21:19:56 +02:00
for ( ; $ i >= 0 ; $ i - - ) {
2011-12-04 02:13:21 +01:00
# $self->{pbot}->logger->log($i . " " . $self->message_history->{$account}->{channels}->{$channel}{messages}->[$i]{mode} ." " . $self->message_history->{$account}->{channels}->{$channel}{messages}->[$i]{msg} . " " . $self->message_history->{$account}->{channels}->{$channel}{messages}->[$i]{timestamp} . " [" . ago_exact(time - $self->message_history->{$account}->{channels}->{$channel}{messages}->[$i]{timestamp}) . "]\n") if $self->message_history->{$account}->{channels}->{$channel}{join_watch} >= $max_messages;
2011-02-11 03:46:35 +01:00
next if $ self - > message_history - > { $ account } - > { channels } - > { $ channel } { messages } - > [ $ i ] { mode } != $ self - > { FLOOD_JOIN } ;
2010-06-19 21:19:56 +02:00
last if + + $ count >= 4 ;
}
$ i = 0 if $ i < 0 ;
2011-02-11 03:46:35 +01:00
% msg = % { @ { $ self - > message_history - > { $ account } - > { channels } - > { $ channel } { messages } } [ $ i ] } ;
}
else {
$ self - > { pbot } - > logger - > log ( "Unknown flood mode [$mode] ... aborting flood enforcement.\n" ) ;
return ;
2010-06-19 21:19:56 +02:00
}
2011-02-11 03:46:35 +01:00
my % last = % { @ { $ self - > message_history - > { $ account } - > { channels } - > { $ channel } { messages } } [ $ length - 1 ] } ;
2010-06-05 08:07:15 +02:00
2011-01-24 23:21:29 +01:00
$ self - > { pbot } - > logger - > log ( "Comparing $nick!$user\@$host " . int ( $ last { timestamp } ) . " against " . int ( $ msg { timestamp } ) . ": " . ( int ( $ last { timestamp } - $ msg { timestamp } ) ) . " seconds [" . duration_exact ( $ last { timestamp } - $ msg { timestamp } ) . "]\n" ) if $ mode == $ self - > { FLOOD_JOIN } ;
2010-06-05 08:07:15 +02:00
2010-06-12 06:27:58 +02:00
if ( $ last { timestamp } - $ msg { timestamp } <= $ max_time && not $ self - > { pbot } - > admins - > loggedin ( $ channel , "$nick!$user\@$host" ) ) {
if ( $ mode == $ self - > { FLOOD_JOIN } ) {
2011-02-11 03:46:35 +01:00
if ( $ self - > message_history - > { $ account } - > { channels } - > { $ channel } { join_watch } >= $ max_messages ) {
$ self - > message_history - > { $ account } - > { channels } - > { $ channel } { offenses } + + ;
$ self - > message_history - > { $ account } - > { channels } - > { $ channel } { last_offense_timestamp } = gettimeofday ;
2010-06-16 03:55:29 +02:00
2011-02-11 03:46:35 +01:00
my $ timeout = ( 2 ** ( ( $ self - > message_history - > { $ account } - > { channels } - > { $ channel } { offenses } + 2 ) < 10 ? $ self - > message_history - > { $ account } - > { channels } - > { $ channel } { offenses } + 2 : 10 ) ) ;
2010-06-18 06:15:28 +02:00
my $ banmask = address_to_mask ( $ host ) ;
2010-06-19 23:38:50 +02:00
$ self - > { pbot } - > chanops - > ban_user_timed ( "*!$user\@$banmask\$##stop_join_flood" , $ channel , $ timeout * 60 * 60 ) ;
2010-06-16 03:55:29 +02:00
2011-02-11 03:46:35 +01:00
$ self - > { pbot } - > logger - > log ( "$nick!$user\@$banmask banned for $timeout hours due to join flooding (offense #" . $ self - > message_history - > { $ account } - > { channels } - > { $ channel } { offenses } . ").\n" ) ;
2010-06-16 03:55:29 +02:00
2010-06-12 06:27:58 +02:00
$ timeout = "several" if ( $ timeout > 8 ) ;
2010-06-16 03:55:29 +02:00
2010-06-27 05:22:29 +02:00
$ self - > { pbot } - > conn - > privmsg ( $ nick , "You have been banned from $channel for $timeout hours due to join flooding. If your connection issues have been fixed, or this was an accident, you may request an unban at any time by responding to this message with: unbanme $channel" ) ;
2010-06-16 03:55:29 +02:00
2011-02-11 03:46:35 +01:00
$ self - > message_history - > { $ account } - > { channels } - > { $ channel } { join_watch } = $ max_messages - 2 ; # give them a chance to rejoin
2010-06-12 06:27:58 +02:00
}
} elsif ( $ mode == $ self - > { FLOOD_CHAT } ) {
2011-02-11 03:46:35 +01:00
$ self - > message_history - > { $ account } - > { channels } - > { $ channel } { offenses } + + ;
$ self - > message_history - > { $ account } - > { channels } - > { $ channel } { last_offense_timestamp } = gettimeofday ;
my $ length = $ self - > message_history - > { $ account } - > { channels } - > { $ channel } { offenses } ** $ self - > message_history - > { $ account } - > { channels } - > { $ channel } { offenses } * $ self - > message_history - > { $ account } - > { channels } - > { $ channel } { offenses } * 30 ;
2010-06-12 06:27:58 +02:00
if ( $ channel =~ /^#/ ) { #channel flood (opposed to private message or otherwise)
2011-01-22 05:42:38 +01:00
# don't ban again if already banned
return if exists $ self - > { pbot } - > chanops - > { unban_timeout } - > hash - > { "*!$user\@$host" } ;
2011-02-11 03:46:35 +01:00
$ self - > { pbot } - > chanops - > ban_user_timed ( "*!$user\@$host" , $ channel , $ length ) ;
2010-06-12 06:27:58 +02:00
2011-02-11 03:46:35 +01:00
$ self - > { pbot } - > logger - > log ( "$nick $channel flood offense " . $ self - > message_history - > { $ account } - > { channels } - > { $ channel } { offenses } . " earned $length second ban\n" ) ;
2010-06-05 08:07:15 +02:00
2011-02-11 03:46:35 +01:00
if ( $ length < 1000 ) {
$ length = "$length seconds" ;
} else {
$ length = "a little while" ;
2010-06-12 06:27:58 +02:00
}
2011-02-11 03:46:35 +01:00
$ self - > { pbot } - > conn - > privmsg ( $ nick , "You have been muted 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" } - > { channels } - > { $ channel } ;
$ self - > { pbot } - > logger - > log ( "$nick msg flood offense " . $ self - > message_history - > { $ account } - > { channels } - > { $ channel } { offenses } . " earned $length second ignore\n" ) ;
2010-06-12 06:27:58 +02:00
$ self - > { pbot } - > { ignorelistcmds } - > ignore_user ( "" , "floodcontrol" , "" , "" , "$nick!$user\@$host $channel $length" ) ;
2011-02-11 03:46:35 +01:00
2010-06-12 06:27:58 +02:00
if ( $ length < 1000 ) {
$ length = "$length seconds" ;
} else {
$ length = "a little while" ;
2010-05-30 04:02:29 +02:00
}
2010-06-12 06:27:58 +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-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-06-06 09:22:23 +02:00
2011-02-11 03:46:35 +01:00
foreach my $ mask ( keys % { $ self - > { message_history } } ) {
foreach my $ channel ( keys % { $ self - > { message_history } - > { $ mask } - > { channels } } ) {
#$self->{pbot}->logger->log("Checking [$mask][$channel]\n");
my $ length = $# { $ self - > { message_history } - > { $ mask } - > { channels } - > { $ channel } { messages } } + 1 ;
my % last = % { @ { $ self - > { message_history } - > { $ mask } - > { channels } - > { $ channel } { messages } } [ $ length - 1 ] } ;
2010-03-23 19:24:02 +01:00
2011-02-11 03:46:35 +01:00
# delete channel key if no activity within 3 days
2011-02-11 07:52:43 +01:00
if ( gettimeofday - $ last { timestamp } >= 60 * 60 * 24 * 90 ) {
$ self - > { pbot } - > logger - > log ( "$mask in $channel hasn't spoken in ninety days; removing channel history.\n" ) ;
2011-02-11 03:46:35 +01:00
delete $ self - > { message_history } - > { $ mask } - > { channels } - > { $ channel } ;
next ;
}
# decrease offenses counter if 24 hours of elapsed without any new offense
elsif ( $ self - > { message_history } - > { $ mask } - > { channels } - > { $ channel } { offenses } > 0 and
$ self - > { message_history } - > { $ mask } - > { channels } - > { $ channel } { last_offense_timestamp } > 0 and
( gettimeofday - $ self - > { message_history } - > { $ mask } - > { channels } - > { $ channel } { last_offense_timestamp } >= 60 * 60 * 24 ) ) {
$ self - > { message_history } - > { $ mask } - > { channels } - > { $ channel } { offenses } - - ;
$ self - > { message_history } - > { $ mask } - > { channels } - > { $ channel } { last_offense_timestamp } = gettimeofday ;
$ self - > { pbot } - > logger - > log ( "anti-flood: [$channel][$mask] 24 hours since last offense/decrease -- decreasing offenses to $self->{message_history}->{$mask}->{channels}->{$channel}{offenses}\n" ) ;
2010-03-23 19:24:02 +01:00
}
}
2011-02-11 03:46:35 +01:00
# delete account for this $mask if all its channels have been deleted
my $ count = 0 ;
foreach my $ channel ( keys % { $ self - > { message_history } - > { $ mask } } ) {
$ count + + ;
}
if ( $ count == 0 ) {
$ self - > { pbot } - > logger - > log ( "$mask has no more channels remaining; deleting history account.\n" ) ;
delete $ self - > { message_history } - > { $ mask } ;
}
2010-03-23 19:24:02 +01:00
}
}
2010-06-16 03:55:29 +02:00
sub unbanme {
my ( $ self , $ from , $ nick , $ user , $ host , $ arguments ) = @ _ ;
2010-06-22 05:01:02 +02:00
my $ channel = lc $ arguments ;
$ host = lc $ host ;
$ user = lc $ user ;
2010-06-16 03:55:29 +02:00
2010-06-18 13:12:55 +02:00
if ( not defined $ channel ) {
return "/msg $nick Usage: unbanme <channel>" ;
2010-06-16 03:55:29 +02:00
}
2010-06-18 06:15:28 +02:00
my $ banmask = address_to_mask ( $ host ) ;
2010-06-22 05:01:02 +02:00
my $ mask = lc "*!$user\@$banmask\$##stop_join_flood" ;
2010-06-16 03:55:29 +02:00
2010-06-18 12:46:23 +02:00
if ( not exists $ self - > { pbot } - > { chanops } - > { unban_timeout } - > hash - > { $ mask } ) {
2010-06-16 03:55:29 +02:00
return "/msg $nick There is no temporary ban set for $mask in channel $channel." ;
}
2010-06-18 12:46:23 +02:00
if ( not $ self - > { pbot } - > chanops - > { unban_timeout } - > hash - > { $ mask } { channel } eq $ channel ) {
2010-06-16 03:55:29 +02:00
return "/msg $nick There is no temporary ban set for $mask in channel $channel." ;
}
2010-06-18 09:03:16 +02:00
$ self - > { pbot } - > chanops - > unban_user ( $ mask , $ channel ) ;
2010-06-18 12:46:23 +02:00
delete $ self - > { pbot } - > chanops - > { unban_timeout } - > hash - > { $ mask } ;
$ self - > { pbot } - > chanops - > { unban_timeout } - > save_hash ( ) ;
2010-06-16 03:55:29 +02:00
return "/msg $nick You have been unbanned from $channel." ;
}
2010-06-18 06:15:28 +02:00
sub address_to_mask {
my $ address = shift ;
my $ banmask ;
if ( $ address =~ m/^([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)$/ ) {
my ( $ a , $ b , $ c , $ d ) = ( $ 1 , $ 2 , $ 3 , $ 4 ) ;
given ( $ a ) {
when ( $ _ <= 127 ) { $ banmask = "$a.*" ; }
when ( $ _ <= 191 ) { $ banmask = "$a.$b.*" ; }
default { $ banmask = "$a.$b.$c.*" ; }
}
} elsif ( $ address =~ m/[^.]+\.([^.]+\.[^.]+)$/ ) {
$ banmask = "*.$1" ;
} else {
$ banmask = $ address ;
}
return $ banmask ;
}
2011-02-11 03:46:35 +01:00
sub check_nickserv_accounts {
my ( $ self , $ nick , $ account ) = @ _ ;
2011-02-14 00:44:05 +01:00
my @ banned_channels ;
2011-02-14 07:23:22 +01:00
my @ account_masks ;
2011-02-13 10:05:48 +01:00
2011-02-11 03:46:35 +01:00
foreach my $ mask ( keys % { $ self - > { message_history } } ) {
if ( exists $ self - > { message_history } - > { $ mask } - > { nickserv_account } ) {
2011-02-14 07:23:22 +01:00
# has nickserv account
2011-02-11 03:46:35 +01:00
if ( lc $ self - > { message_history } - > { $ mask } - > { nickserv_account } eq lc $ account ) {
2011-02-14 07:23:22 +01:00
# pre-existing mask found using this account previously, check for bans
2011-02-11 07:52:43 +01:00
$ self - > { pbot } - > logger - > log ( "anti-flood: [check-account] $nick [nickserv: $account] seen previously as $mask.\n" ) ;
2011-02-14 00:44:05 +01:00
2011-02-13 10:05:48 +01:00
my $ baninfo = $ self - > { pbot } - > bantracker - > get_baninfo ( $ mask ) ;
2011-02-14 00:44:05 +01:00
2011-02-13 10:05:48 +01:00
if ( defined $ baninfo ) {
2011-12-12 20:40:54 +01:00
if ( $ self - > ban_whitelisted ( $ baninfo - > { channel } , $ baninfo - > { banmask } ) ) {
$ self - > { pbot } - > logger - > log ( "anti-flood: [check-bans] $mask evaded $baninfo->{banmask} in $baninfo->{channel}, but allowed through whitelist\n" ) ;
next ;
} else {
$ self - > { pbot } - > logger - > log ( "anti-flood: [check-bans] $mask evaded $baninfo->{banmask} banned in $baninfo->{channel} by $baninfo->{owner}\n" ) ;
push @ banned_channels , $ baninfo - > { channel } ;
$ self - > { pbot } - > conn - > privmsg ( $ nick , "You have been banned in $baninfo->{channel} for attempting to evade a ban on $baninfo->{banmask} set by $baninfo->{owner}" ) ;
}
2011-02-13 10:05:48 +01:00
}
2011-12-12 20:40:54 +01:00
}
}
2011-02-11 03:46:35 +01:00
else {
2011-02-14 07:23:22 +01:00
# no nickserv account set yet
2011-02-11 03:46:35 +01:00
if ( $ mask =~ m/^\Q$nick\E!/i ) {
2011-02-14 07:23:22 +01:00
# nick matches, must belong to account
2011-02-11 07:52:43 +01:00
$ self - > { pbot } - > logger - > log ( "anti-flood: $mask: setting nickserv account to [$account]\n" ) ;
2011-02-11 03:46:35 +01:00
$ self - > message_history - > { $ mask } - > { nickserv_account } = $ account ;
2011-02-13 10:05:48 +01:00
2011-02-14 07:23:22 +01:00
push @ account_masks , $ mask ;
2011-02-11 03:46:35 +01:00
}
}
}
2011-02-14 07:23:22 +01:00
foreach my $ banned_channel ( @ banned_channels ) {
foreach my $ account_mask ( @ account_masks ) {
$ account_mask =~ m/[^@]+\@(.*)/ ;
2011-12-11 23:56:57 +01:00
my $ banmask = "*!*\@$1" ;
2011-02-14 07:23:22 +01:00
$ self - > { pbot } - > logger - > log ( "anti-flood: [check-bans] Ban detected on account $account in $banned_channel, banning $banmask.\n" ) ;
$ self - > { pbot } - > chanops - > ban_user_timed ( $ banmask , $ banned_channel , 60 * 60 * 5 ) ;
}
}
2011-02-11 03:46:35 +01:00
}
sub on_whoisaccount {
my ( $ self , $ conn , $ event ) = @ _ ;
my $ nick = $ event - > { args } [ 1 ] ;
my $ account = $ event - > { args } [ 2 ] ;
$ self - > { pbot } - > logger - > log ( "$nick is using NickServ account [$account]\n" ) ;
$ self - > check_nickserv_accounts ( $ nick , $ account ) ;
}
2010-03-17 07:36:54 +01:00
1 ;