exim greylisting

Attention!

I no longer maintain this because I no longer use greylisting on my server, it proved to be little efficient when having a lot of other SPAM checks and often deferred valid email for me and my users. I'm leaving this here for reference only, I'll gladly accept patches and publish them here (with permission) if you send them to me.

advanced greylisting including statistics and manual whitelisting capability with exim 4 and mysql.

Author: Johannes Berg

credits

Based on the original whitelisting whitepaper at http://projects.puremagic.com/greylisting/.

Partially based on the database code from http://theinternetco.net/projects/exim/greylist which is in turn based on http://raw.no/personal/blog/tech/Debian/2004-03-14-15-55_greylisting

features

  • manual white- and blacklisting by IP and IP subnet
  • manual white- and blacklisting by $sender_address and $sender_address_domain
  • manual white- and blacklisting by $local_part@domain, $local_part and $domain
  • manual white- and blacklisting by any combination of the above
  • individual email logging (including the rule relevant rule)
  • IP class definitions for common-pool servers

usage

First you need to include the greylist-acl and the config-mysql files into your configuration (use .include, or if you use debian's split-config symlink them into acl/ and main/ respectively giving them appropriate numbers)

Based on the original greylisting whitepaper, you should use the greylist_acl as follows:

  • in your RCPT acl put:

    .ifdef GREYLIST_ENABLED
    defer !senders = : postmaster
          acl = greylist_acl
          message = greylisted - try again later
    .endif
    
  • in your DATA acl put:

    .ifdef GREYLIST_ENABLED
    defer senders = : postmaster
          acl = greylist_acl
          message = greylisted - try again later
    .endif
    

Attention!

This data acl part is wrong. Don't use it. I will rewrite this to also work properly for bounces and mails addressed to postmaster. The idea is to allow bounces to be only to a single recipient, save that from $recipients into an acl variable and use that variable in the macros. For now, just leave it out completely, this will disable greylisting to postmaster and for bounces, but that doesn't normally affect its efficiency much.

This works since the greylist_acl can only return accept and deny. In the case of accept, the message is deferred, and in the case of deny the defer condition is skipped. We check the sender as well to not break callbacks that use an empty sender, or just postmaster as the sender.

howto

whitelisting

fill in the appropriate fields (relay_ip, sender, recipient), leaving the others set to NULL, set type=MANUAL.

Combinations are possible.

For recipient you can also use "local_part@" and "@domain" for recipient, in addition to "local_part@domain"; For sender you can use "sender@domain" as well as just "@domain". Set record_expires to some high value (e.g. 9999-12-31 23:59:59).

IP class matching

I have added a new table that contains IP classes (see below). If a regular expression given in an ip_regex field matches the IP of the sender ($sender_host_address), the name given to that class is used instead. For example, I used the following statement to group web.de into a single class:

INSERT INTO exim4.exim_greylist_classes (ip_regex, name) values ('217\\.72\\.19[2-6]\\.[0-9]*', 'web.de');

after which the greylisting implementation will use "web.de" in the main table as relay_ip for any IP addres that matches the given regex (any address in the range 217.72.192.0 - 217.72.196.255).

Often, you will want to use this functionality instead of partial IP matching.

For reference, here are my current classes:

id ip_regex name comment
1 217.72.19[2-6].[0-9]* web.de None
2 213.165.6[4-6].[0-9]* gmx None
3 216.239.(3[2-9]|[45][0-9]|6[0-3]).[0-9]* google/gmail None
4 65.5[2-5].[0-9]*.[0-9]* microsoft hotmail uses this netblock, and retries from different servers
5 207.171.1([6-8][0-9]|91)..* amazon None
6 217.115.139.137 mail.duncanthrax.net None
7 216.73.(8[0-9]|9[0-5]).[0-9]* DOUBLECLICK-NET None

partial IP matching (subnets)

Instead of setting a full IP address in the relay_ip field, insert a manual record that contains only a partial IP adress, for example "1.2.3.". Be sure to include the trailing dot ("."), otherwise it is possible to also match "1.2.33.222" with an entry like "1.2.3" (because this is simply done with substring extraction).

blacklisting

Just like whitelisting, but also set block_expires very high.

database

greylist tables

relay_ip is that long to allow full IPv6 addresses and classnames from the class-table to fit.

CREATE TABLE exim_greylist (
  id bigint(20) NOT NULL auto_increment,
  relay_ip varchar(80) default NULL,
  sender varchar(255) default NULL,
  recipient varchar(255) default NULL,
  block_expires datetime NOT NULL default '0000-00-00 00:00:00',
  record_expires datetime NOT NULL default '9999-12-31 23:59:59',
  create_time datetime NOT NULL default '0000-00-00 00:00:00',
  type enum('AUTO','MANUAL') NOT NULL default 'MANUAL',
  passcount bigint(20) NOT NULL default '0',
  blockcount bigint(20) NOT NULL default '0',
  PRIMARY KEY  (id)
);
CREATE TABLE exim_greylist_classes (
  id bigint(20) NOT NULL auto_increment,
  ip_regex varchar(255) NOT NULL,
  name varchar(80) NOT NULL,
  comment varchar(255) default NULL,
  PRIMARY KEY (id)
);

logging table

if you want to enable logging, add this table:

CREATE TABLE exim_greylist_log (
  id bigint(20) NOT NULL auto_increment,
  listid bigint(20) NOT NULL,
  timestamp datetime NOT NULL default '0000-00-00 00:00:00',
  kind enum('deferred', 'accepted') NOT NULL,
  PRIMARY KEY (id)
);

todo

  • index the database!
  • figure out above for IPv6 Addresses (does exim give us the full address, with zeroes filled in? - no, it seems to give the shortest possible form (from glibc), therefore the current logic should suffice)