Recently, spammers were able to guess the password of a user account, and used this account to send thousands of spam-mails in a very short time.
To limit the propability of this happening again, I've been working on a small script that monitors qmail's log-file. If the script detects that the number of emails sent from a user account is over a certain threshold, appropriate action can be taken. Right now, the script does nothing but write in a log file of its own.
I know that spammers can fake the from-address, so of course this script will not be guaranteed to block spam attacks. But hopefully it will block some attacks. Please have a look at the script and provide some feedback.
#!/usr/bin/perl -w
use Time::TAI64 qw/tai2unix unixtai64 tai64nlocal/;
use File::Tail;
use strict;
use warnings;
my (%stats, $line, $timestamp, $from, %blacklist);
my $qmail_logfile = File::Tail -> new(name=>"/var/log/qmail/current", tail=> -1);
my $interval = 1800; # 30 minutes
my $max = 10; # 10 emails - really low number, just for test purposes
open (LOG, ">>", "logfile2");
select((select(LOG), $|=1)[0]); # Disable buffering
print LOG "Starting ...\n";
# Typical log line
# @40000000571c9b1d32a4496c info msg 101581385: bytes 26018 from <user.name@domain.org> qp 21365 uid 400
#
while (defined($line = $qmail_logfile->read)) {
chomp $line;
next unless ($line =~ /\sinfo\s/);
if ($line =~ /^(@[0-9,a-f]+)\sinfo\smsg\s.*from\s<(\S+)>\s/) {
$timestamp = tai2unix($1);
$from = $2;
next unless ($from =~ /\w/);
if (exists ($stats{$from})) {
push @{$stats{$from}}, $timestamp
} else {
$stats{$from} = [$timestamp]
}
# Add new timestamp
push @{$stats{$from}}, $timestamp;
# Delete obsolete timestamps
while ($stats{$from}[0] < $timestamp - $interval) {
shift @{$stats{$from}}
}
# Check if limit is exceeded
my $readable_timestamp = tai64nlocal(unixtai64($timestamp));
$readable_timestamp =~ s/\.\d*//;
if ( (scalar @{$stats{$from}} > $max) &&
(not exists($blacklist{$from}))
) {
$blacklist{$from} = 1;
print LOG "Maximum exceeded for $from at $readable_timestamp\n";
} elsif ( (scalar @{$stats{$from}} <= $max) &&
(exists($blacklist{$from}))
) {
delete $blacklist{$from};
print LOG "Mail from $from accepted again at $readable_timestamp\n";
}
}
}