[Mimedefang] blocking broadband connections (was: Greylist-busting ratware?)

Paul Murphy pjm at ousekjarr.org
Wed Apr 19 05:11:52 EDT 2006


David,

> I think greylisting is nearing the end of its useful life.  I'm
> noticing a new kind of ratware that retries every 5 minutes
> like clockwork, mutating message bodies.  Our CanIt software tempfails
> mail until it's approved by a human, and this mechanism has 
> the side-effect of illuminating ratware behaviour.

Hmm, I guess it was just a matter of time...
 
> Anyone else seeing this?  We see it quite a lot, and always from cable
modem
> or DSL machines (probably cracked Windoze boxes.)

I'm not seeing any of these, but then my site policy is to refuse connections
from anything which can be identified as a broadband host, i.e. anything
which has its IP address in its name, or where the name contains indicators
of a DSL or cable connection.  I firewall them as well, and keep a record of
when and why the firewall entry was added.  Many of the systems I see have
names like "24-196-192-021.dhcp.hckr.nc.charter.com", which is 24.196.192.21
- if they want to mail me, they should either be using their ISP's mail host,
or should have the ability to set up a reverse DNS entry as well as a mail
server.

If this is of use to anyone, here's what I do:

In filter_sender:
=================

  if (check_broadband($hostip,$hostname,$MsgID)==0)
    {
    open(FIREWALL,"/etc/mail/spam_block");
    my @fw=<FIREWALL>;
    @match= grep /$hostip/, at fw;
    md_syslog('debug', "Firewall - broadband positive, check for $hostip -
resul
t = $match[0]");
    close(FIREWALL);
    if ( !defined $match[0] )
      {
      md_syslog('debug', "Firewall - broadband positive, added $hostip due to
nu
ll result");
      my ( $sec,$min,$hour,$mday,$mon,$year,$yday,$isdst ) = localtime(time);
      open(FIREWALL,">>/etc/mail/spam_block");
      printf FIREWALL "%s       # %d %02d/%02d/%02d %02d:%02d:%02d #
Broadband\n
", $hostip, time, $mday, $mon+1, $year-100, $hour, $min, $sec;
      close(FIREWALL);
      }
    else
      {
      md_syslog('info',"Existing Broadband block $hostip ($hostname) skipped
for
 msg $MsgID");
      }
    firewall_block($hostip);
    md_syslog('info',
"MDLOG,$MsgID,Broadband,0,$hostip,$sender,$hostname,?");
    return("REJECT","Connection rejected - we don't accept mail from end-user
ho
sts - get a proper reverse IP mapping, or route via your ISPs mail server.");
    }

In filter_initialize:
=====================
	require("/etc/mail/check_broadband.pl");

check_broadband.pl:
===================
sub check_broadband {

  my ($hostip,$hostname,$MsgID) = @_;
  $exceptions{'1.1.1.1'} = 1; # permit this host

  if ( exists ( $exceptions{$hostname} )
    {
    md_syslog("info","$MsgID - Host $hostname($hostip) is broadband
exception");
    return (1);
    }
  if ( $hostname =~ /cable./i )
    {
    md_syslog("info","$MsgID - Host $hostname is a CABLE broadband client");
    return (0);
    }
  if ( $hostname =~ /hsdl./i )
    {
    md_syslog("info","$MsgID - Host $hostname is a HSDL broadband client");
    return (0);
    }
  if ( $hostname =~ /dsl./i )
    {
    md_syslog("info","$MsgID - Host $hostname is a DSL broadband client");
    return (0);
    }
  my @ipparts=split /\./,$hostip;
  my $partcount=0;
  foreach my $part ( @ipparts )
    {
    if ( $hostname =~ /$part/ )
      {
      $partcount++;
      #md_syslog('info',"$MsgID - ** hostname $hostname contains IP part
$part,
count=$partcount");
      }
    }
  if ( $partcount==4)
    {
      md_syslog('info',"$MsgID - ** hostname $hostname has its IP in its
reverse
 address - broadband");
      return (0);
    }
  return (1);
}
1;

firewall_block: (somewhere in the top of the filter)
===============

sub firewall_block($) {
my ($blockip) = @_;
my $reply;
my $msg;
my $sock=new IO::Socket::INET ( PeerAddr =>     'gate',
                                PeerPort =>     26,
                                proto    =>     'tcp'
                                );
md_syslog('debug',"    $MsgID: Firewall server Socket connection could not be
ma
de\n") unless $sock;
if ( $sock )
  {
  $reply = <$sock>;
  md_syslog('debug',"    $MsgID: Firewall Server check: $reply\n");
  $msg="add $blockip\n";
  md_syslog('debug', "    $MsgID: Firewall - Sending $msg...");
  print $sock $msg;
  $reply = <$sock>;
  md_syslog('debug',"    $MsgID: Firewall Server response: $reply");
  shutdown $sock, 0;
  }
return 0;
}

The firewall management process is a simplistic daemon written in Perl which
listens for requests and adds them to iptables - because my system is small
and fully under my control, there are no security measures in this code.  For
larger uses, it would need an authentication system or access control at
least.

fwd.pl:
=======

#!/usr/bin/perl
use strict;
use IPTables::IPv4;
use IO::Socket;
use POSIX qw(setsid);
use IO::Select;
use IO::Handle;

open(LOGFILE, ">>/root/spam/fwd.log");
autoflush LOGFILE 1;

##
# MAIN: Fork and setsid().
##
unless(my $pid = fork()) 
  {
  exit if $pid;
  print STDOUT (scalar localtime)."     Firewall check process running as
$$\n";
  print LOGFILE (scalar localtime)."    Firewall check process running as
$$\n";
  setsid;
  umask 0;
  my $socket= new IO::Socket::INET (LocalHost =>        'gate',
                                    LocalPort =>        26,
                                    Proto     =>        'tcp',
                                    Listen    =>        5,
                                    Reuse     =>        1
                                );
  die "Can't create server socket : $!" unless $socket;
  print STDOUT (scalar localtime)."     Listening for connections on port
26\n";
  print LOGFILE (scalar localtime)."    Listening for connections on port
26\n";
  my $readable = IO::Select->new;
  $readable->add($socket);

  while(1) 
    {
    my ($ready) = IO::Select->select($readable,undef,undef,undef);
    foreach my $s ( @$ready )
      {
      if ( $s == $socket )  # new connection
        {
        my $new_sock = $socket->accept;
        $readable->add($new_sock) if $new_sock;
        print $new_sock "Firewall server available\n";
        # print LOGFILE ("Accepted connection from ",
join('.'unpack('C*',$new_sock->peername))[4..7]),"\n");
        }
      else
        {       # not new socket
        my $buf = <$s>;
        if ( defined $buf )
          {
          chomp $buf;
          print LOGFILE (scalar localtime)."    Received: $buf\n";
          my @cmds = split / /,$buf;
          
          if ( (defined $cmds[0]) && ($cmds[0] eq 'add') )
            {
            my $ip=$cmds[1];
            chomp $ip;
            print LOGFILE (scalar localtime)."  $ip - ADDING\n";
            my $stat='';

            # Check for existing rules for this IP address
            print LOGFILE (scalar localtime)."  Search REJECT entry = $ip\n";
            my $filter = IPTables::IPv4::init('filter');
            my @rules=$filter->list_rules("INPUT");
            my $count=scalar @rules;

            my $index=0;
            my $found=0;
            my @srch;
            while ( ($index < $count) )
              {
              if ( (defined $rules[$index]{'source'} ) && (
$rules[$index]{'source'} eq $ip ))
                {
                $found=1;
                print LOGFILE (scalar localtime)."      Search: Match for $ip
found at position $index\n";
                if ( defined ($rules[$index]{'reject-with'} ))
                  {
                  print LOGFILE (scalar localtime)."    Search: Match for $ip
at position $index is a REJECT\n";
                  push @srch,"REJECT=".$index;
                  }
                elsif ( defined ($rules[$index]{'log-prefix'} ))
                  {
                  print LOGFILE (scalar localtime)."    Search: Match for $ip
at position $index is a LOG\n";
                  push @srch,"LOG=".$index;
                  }
                else
                  {
                  print LOGFILE (scalar localtime)."    Search: Match for $ip
at position $index is unknown type?\n";
                  }
                }
              $index++;
              }
            if ( ! $found )
              {
              push @srch,"NOTFOUND";
              }
            
            # check if a REJECT rule already exists
            my @match= grep /REJECT/, at srch;
            print LOGFILE " - search $ip result @srch\n";
            if ( defined $match[0] ) #  existing REJECT rule
              {
              print LOGFILE (scalar localtime)."        Matched REJECT entry
= @match\n";
              $stat.='-';
              }
            else #  no match - add one
              {
              my $success=0; 
              my $count=0;
              my $result;
              while ( ( $success == 0 ) && ( $count < 5 ) )
                {
                $success=$filter->insert_entry('INPUT',
                        {'in-interface'         => "ppp0",
                        'source'                => $ip,
                        'jump'                 => "REJECT",
                        'reject-with'          => "icmp-port-unreachable"},
0);
                print LOGFILE (scalar localtime)."      Reject_ip: attempt
$count for $ip result = $success\n";
                $count++;
                }
              if ( $success == 1 )
                {
                $stat.='+';
                }
              else
                {
                $stat.='0';
                }
              }

            print LOGFILE (scalar localtime)."  Checking LOG entry = $ip\n";
            # see if there's already a LOG entry for this address
            my @match3= grep /LOG/, at srch;
            if ( defined $match3[0] )
              {
              print LOGFILE (scalar localtime)."        Matched LOG entry =
@match3\n";
              $stat.='-';
              }
            else
              {
              print LOGFILE (scalar localtime)."        No match for LOG
entry\n";
              my $success=0; 
              my $count=0;
              my $result;
              print LOGFILE (scalar localtime)."        About to insert LOG
entry\n";
              while ( ( $success == 0 ) && ( $count < 5 ) )
                {
                $success=$filter->insert_entry('INPUT',
                        {'in-interface'         => "ppp0",
                         'source'               => $ip,
                         'jump'                 => "LOG",
                         'log-prefix'           => "SPAM_BLOCK: "}, 0);
                print LOGFILE (scalar localtime)."      Log_ip: attempt
$count for $ip result = $success\n";
                $count++;
                }
              if ( $success == 1 )
                {
                $stat.='+';
                }
              else
                {
                $stat.='0';
                }
              }
            print LOGFILE (scalar localtime)."  About to commit entry\n";
            $filter->commit();
            print LOGFILE (scalar localtime)."  About to reply to client\n";
            print $s "OK $stat - $buf\n";
            }
          elsif ( (defined $cmds[0]) && ($cmds[0] eq 'del') )
            {
            print LOGFILE (scalar localtime)."  Delete command $buf -
unimplemented\n";
            }
          elsif ( ! defined $cmds[0] )
            {
            print LOGFILE (scalar localtime)."  Error parsing command from
buffer $buf\n";
            }
          else
            {
            print LOGFILE (scalar localtime)."  Invalid command cmds[0]\n";
            }
          }
        else
          {
          $readable->remove($s);
          $s->close;
          print LOGFILE (scalar localtime)."    Client connection closed\n";
          }
        }       # end else not new socket
      } # end foreach
    } # end while 1
  }  # end unless

-- 
No virus found in this outgoing message.
Checked by AVG Free Edition.
Version: 7.1.385 / Virus Database: 268.4.4/318 - Release Date: 18/04/2006
 




More information about the MIMEDefang mailing list