Stopping SPAM BEFORE the door with Spamcop & IPTables
October 10th, 2007 by admin
As many of you who are stuck with the task of administering your own server know, SPAM is one giant pain in the neck. There are usually fifty other things you would prefer to be doing than finding new ways to defeat spam.
For those of us using Sendmail on some flavor of Linux, there are a few ways to slow down the barrage of junk coming in. Spamassassin, MailScanner, etc., and of course there are always black lists. One of the more popular is Spamcop. Spamcop can plug right into the Sendmail configuration files. It checks a sender against a known database of spamming IP addresses and will deny mail delivery. (More below)
This method is fine and will work but would it not be nice to take it a step further and once Spamcop has denied a sender, to just block them from even knocking on the mail servers door? Not only would this reduce the overhead of a Sendmail child, it would also stop another query to spamcop.net saving myself and them a bit more overhead/bandwidth.
After examining the maillog file generated by Sendmail, I found I was able to easily parse out the IP addresses of offending senders. From there, it was as simple as issuing a drop to IPTables of that IP. Basically this tells your Linux firewall to refuse connections from the IP address.
This worked good at first but I found that running a cronjob to go through the mail log and ban IP’s got rather resource intensive so I decided to rewrite it a bit so it had a memory and would only ban IP addresses that haven’t already been banned. This saved a lot of overhead.
After thinking about it a bit, I also decided that dropping the IP totally was overkill. Many of these spam bots are just home users who have no clue on how to secure their PC and why should I deny these idiots access to my websites/advertising? So I rewrote it just to block them from accessing port 25 (my SMTP port).
Here is the Perl script:
## The port you wish to block (25 is the normal SMTP port).
my $port = 25;
## Path to where you want the ip_hash.txt file stored (ip database of previously dropped IP's) - NO TRAILING SLASH!
my $path_to_ip_hash = '/home/anywhere';
## You should change the path here to where you uploaded serialize.pm. Cron env probably won't find it otherwise.
use lib '/folder_where_this_file_is_located';
## Path and name of the maillog file.
my $maillog = '/var/log/maillog';
## Location of iptables.
my $iptables = '/sbin/iptables';
####################################### No more editing required #######################################
use serialize;
## If you need the serialize module, it is located here: http://www.hurring.com/scott/code/perl/serialize/
my $y = 0;
if ($ARGV[0] eq 'flush') {
my %ip_flush = ();
my $ip_flushed = serialize(\%ip_flush);
open (IPS, '>' . $path_to_ip_hash . '/ip_hash.txt');
flock(IPS, 2);
print IPS $ip_flushed;
close IPS;
print "List flushed\n";
exit;
}
open (IPS, $path_to_ip_hash . '/ip_hash.txt');
flock(IPS, 2);
my $ip_hash = <IPS>;
close IPS;
my $ip_list = unserialize($ip_hash);
my %ip_list = %$ip_list;
open(FH,$maillog);
my @log = <FH>;
close FH;
my ($ban, $key, @ip, @temp, %ban);
## Here is where the log is parsed, searches for rejected (by spamcop) mail attempts (553). ## ## You may need to alter this to parse your log files. This was written for for mail logs generated by ## ## Sendmail 8.14.1 (and probably updated by now). This line gives a positive result: ## ## Oct 9 01:50:13 ns1 sendmail[26040]: ruleset=check_relay, arg1=[123.123.123.123], arg2=127.0.0.1, relay=[123.123.123.123], reject=553 5.3.0 Mail Rejected: http://spamcop.net/bl.shtml?123.123.123.123 ##
foreach (@log) {
if ($_ =~ m/reject\=553/) {
@temp = split(/\,/, $_);
@ip = split(/[\[\]]/, $temp[3]);
if (!(exists $ip_list{$ip[1]})) {
$ban{$ip[1]}++;
$ip_list{$ip[1]}++;
}
}
}
## End parsing. IP's are in the %ban hash. IP's are the key values.
foreach $key (keys %ban) {
print "Issuing $iptables -I INPUT -s $key -p tcp --dport $port -j DROP\n";
$ban = `$iptables -I INPUT -s $key -p tcp --dport $port -j DROP`;
## If you want to ban them completely, comment out the above line and use the next line instead.
#$ban = `$iptables -I INPUT -s $key -j DROP`;
$y++;
}
my @months = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec);
my @weekDays = qw(Sun Mon Tue Wed Thu Fri Sat Sun);
my ($second, $minute, $hour, $dayOfMonth, $month, $yearOffset, $dayOfWeek, $dayOfYear, $daylightSavings) = localtime();
my $year = 1900 + $yearOffset;
my $theTime = "$hour:$minute:$second, $weekDays[$dayOfWeek] $months[$month] $dayOfMonth, $year";
print "\n$theTime: $y new bans " . (keys %ip_list) . " total bans.\n";
$ip_hash = serialize(\%ip_list);
open (IPS, '>' . $path_to_ip_hash . '/ip_hash.txt');
flock(IPS, 2);
print IPS $ip_hash;
close IPS;
1;
Make sure you download the module serialize from Scott Hurrings’ website and place it in the same directory where the above code will go.
Calling this file every ten minutes or so via a cron job will execute the parsing routine. You should test this out on the command line before using it live. The parsing routines work on my version of Sendmail on my flavor of Linux. As far as I know, it should work on any other flavor too. You will need to have Sendmail configured to use Spamcop.
All it does is parse the log file searching for 553 rejects and drops them via iptables. Previous drops are saved to a file that contains a serialized hash. I chose to store it this way so it could be easily accessed by PHP scripts too. This can come in handy if you want to show people the IP’s you have banned like I have done here.
Here is the PHP code to do this:
echo "<P>List of IP's recently banned:</P>";
$ips = file_get_contents('/path/to/ip_hash.txt');
$ips = unserialize($ips);
foreach ($ips as $key => $value) {
echo "<a href=http://spamcop.net/bl.shtml?$key>$key</a><BR>";
}
I piped the output of this file to a log in my /var/log directory and set up logrotate to handle the rotation. I have been watching it slowly ice spammers and have noticed a serious improvement in server load especially during peak spam times. It also seems to have slowed down the spam itself a bit.
So you are probably saying, ok, you saved some resources and have lowered your spam intake a little too, so what?
Well, first, I know that I am probably at least slowing a bit of the spam by rejecting packets from these spammers, but I think the bigger picture here is just the concept. If this type of approach was used by a majority of mail servers, would it not take a bite out of spam right there? Imagine every Linux box running Sendmail using this? It could easily be modified to use any other mail log or firewall. Would this not also reduce the usefulness of a bot army used for the purpose of sending spam?
Now of course, one could be concerned about false positives. This is something handled more on the Spamcop end of it, but flushing your IPTables and the above perl script from time to time will lower the chances of this. (use the flush command line argument to flush the database).
Anyway, feel free to comment. :)
This entry was posted on Wednesday, October 10th, 2007 at 1:29 am and is filed under Uncategorized. You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site.
November 5th, 2007 at 2:35 pm
Good Article. Spamcop is a good RBL, but has sometimes caught ISP’s and other hosting companies that many people use time after time. I would rather tag SPAM, than actually remove it. I can setup a filter in my email client and I always go through my SPAM to make sure that a false gets through.
One thing that works really well is sa-exim:
http://marc.merlins.org/linux/exim/sa.html
It’s runs inside of Exim and scans messages at SMTP time. Which is good for many reasons, you’re not straight out blocking the ISP so it doesn’t look like a network/firewall issue. This is important, because most of the time when there is an issue with mail people will point fingers (and for customers this isn’t fun).
Also, you want to reject mail at SMTP. As you can send a note along with the bounced mail back to the sender.
However, changing your MTA is a big hassle if you’re already using one that you love. Food for thought.
November 5th, 2007 at 3:06 pm
I understand your point. I guess what I am saying is that the user is already bounced back a message when they first get blocked via the RBL.
All this script does is comb through the logs and find those RBL blocks and go one step further and block them via IP tables so as to stop further email from these IP’s.
BTW, since this article was posted, over 17583 IP bans have been issued. Slowly but surely the amount getting banned is smaller and smaller as the list gets larger.
November 8th, 2007 at 7:51 pm
That’s great! A scoring system would be more appropriate in this case. How many non-existent user address has the IP tried to send to. How many SMTP session timeouts. Then when you have the scoring down you can assign a block based on hours, days, weeks or months.
If you’re getting many connections from one host, or the host is sending lots of spam your way. Then yes filtering the traffic would be given. But basing your filtering on an RBL that includes hosts based on user submissions is something you should re-think. Lots of ISP and small business get on the block very easily.
I sound like a broken record, but I’d rather receive my mail than have it blocked entirely. Before employing SA-Exim and a lot of Exim configuration, I was getting over 150 spam emails a day. Now its 3-4, and thats with a catchall!