MIMEDefang and virii

OK so you use MIMEDefang together with ClamAV[*] to check incoming messages for viral content. But given the fact that an infected machine will bomb you with many many messages, why should you check every message sent for a given time window? This is what I came up with:

The default mimedefang-filter(5) has the following check which discards viral messages:

if ($FoundVirus) {
  md_graphdefang_log('virus', $VirusName, $RelayAddr);
  md_syslog('warning', "Discarding because of virus $VirusName");
  return action_discard();

Changing it to:

if ($FoundVirus) {
  # OK log $RelayAddr
  # If you are on a Debian-like system you have to put
  # use DB_File in /etc/mail/mimedefang.pl.conf
  # otherwise you have to put it somewhere in mimedefang-filter
  my %vbl;
  my $now;
  tie %vbl, 'DB_File', "/var/cache/local/virbl/virbl.db", O_CREAT|O_RDWR, 0644, $DB_BTREE or die;
  $now = time;
  $vbl{$RelayAddr} = $now;
  untie %vbl;
  md_graphdefang_log('virus', $VirusName, $RelayAddr);
  md_syslog('warning', "Discarding because of virus $VirusName");
  return action_discard();

logs $RelayAddr (the IP address of the infected machine) together with a timestamp in a BerkeleyDB B-Tree. In our example this is /var/cache/local/virbl/virbl.db. You have to make this file writeable by the user that runs MIMEDefang on your system. And now using the following sendmail.mc code one can block this IP address prior to inspecting the message content:

# .db is appended by sendmail automagically
Kvirbl btree -a.FOUND /var/cache/local/virbl/virbl

# Always remember:  In sendmail the LHS and the RHS of the sendmail.mc/.cf is 
# separated with tabs and not spaces.  So do not copy-paste this fragment,
# type it.
R$*                     $: $&{client_addr}
R$*                     $: $(virbl $1 $: $1.NOTFOUND $)
# The next line broken in two for readability
R$* . FOUND             $#error $@ 5.7.1 $: You have sent us mail containing
           a virus and are blocked from our systems for an hour.

So now you need an expiration proccess. How long shall these IP addresses remain in your database? I keep them for one hour. It seems to be a reasonable default. A simple expiry script is the following perl snippet:

use DB_File;
$db = shift or die;
$threshold = shift or die;
tie %d, 'DB_File', $db, O_RDONLY, 0644, $DB_BTREE;
$now = time;
foreach $i (keys %d) {
        $diff = $now - $d{$i};
        if ($diff > $threshold) {
                delete $d{$i};
untie %d;

You can run this script from cron every ten minutes or so. I’ve written my expiry program in C and run it every two minutes. If you also want to do this, you have to remember that the perl snippet on mimedefang-filter that logs $RelayAddr and the timestamp stores the timestamp as a string and not as an integer.

[*] There exist many HOWTOs on how to setup MIMEDefang to work with ClamAV. Just use Google.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s