Wizzy Digital Courier
August public beta
( 14.03.2006 )
Wizzy in the News
( 29.04.2004 )
The Big Issue
( 16.04.2004 )
Flagship install
( 29.07.2003 )

Educational resources

Similar projects


South Africa


Suggest a link

August public beta

LTSP from Hard Disk


Blocking spam

How it works

List of software

Thin Client


Shuttleworth Foundation

Eshowe Jnr.

KwaZibonele Jnr.

Esangweni High

Eshowe High


Contact us
Powered by eZ publish


Blocking spam

Top level Technology

I have to rewrite my spam-filtering systems every six months. This is my latest attempt, using greylisting with the exim MTA in addition to HELO checking and tests on known spammers.

Other useful resources are :-

I started with solution, but it used the sender domain, and had no local address part in the lookup, and no good index.
Packages to install - using whitebox linux / Redhat Enterprise.
  1. exim - with exiscan and mysql support compiled in
  2. mysql-server

Setting up the database - first give it a root password, for basic security. Ensure that it starts up on boot, and, for security purposes, use unix sockets and not TCP. We do the last by adding --skip-networking to the safe_mysqld command in /etc/rc.d/init.d/mysqld.
/sbin/chkconfig mysqld on
/etc/rc.d/init.d/mysqld start
mysqladmin -u root password 'SecRet'

Next, create a database for exim to use, and grant permissions, and then create the table. This is indexed by sender email address, sender IP address, and username of the recipient and holds a timestamp. sender and recipient are in CHAR fields for speed, and are truncated by MySQL when being added if they are over 25 chars long.

The block_expires field is used as follows :-
  • Absent ? Start Greylisting process, 25 mins in future
  • In the future ? Greylisted, 25 minute block still active
  • In the recent (6 hours) past? No retry of delivery
  • 6-8 hours past ? Purge from db
  • Over 8 hours past ? Set by UPDATE - accept.
  • Over 35 days past ? Purge from db

If this timestamp is in the future, we wait until then before accepting mail (greylisting). If it is in the past, we accept the mail. Cleanup routines flush entries more than 7 days old.
mysqladmin -u root -p create exim
mysql -u root -p
mysql> grant all on exim.* to exim@localhost identified by 'exim%worD';
mysql> flush privileges;
mysql> quit;

mysql -u exim -p exim
mysql>  CREATE TABLE greylist (
   relay_ip char(15) NOT NULL,
   sender char(100) NOT NULL,
   recipient char(25) NOT NULL,
   block_expires datetime NOT NULL,
   PRIMARY KEY (relay_ip, sender, recipient)
mysql> quit;

We now need to add some ACLs to exim for it to use these.

At the top of /etc/exim.conf, we set up the acl_ routines, and add some MySQL macros for use later on.

Please note that dollar signs should not have a space between them and the following curly brackets. A 'feature' of my CMS, sorry

acl_smtp_rcpt = check_recipient

# are they in the database already ?
   WHEN now() - block_expires > 0 THEN 2 \
   ELSE 1 \
 END \
 FROM greylist \
 WHERE relay_ip = '$ {quote_mysql:$sender_host_address}' \
   AND sender = '$ {quote_mysql:$sender_address}' \
   AND recipient = '$ {quote_mysql:$local_part}'

# add them in, with 25 minute block
GREYLIST_ADD = INSERT INTO greylist (relay_ip, sender, recipient, block_expires) \
VALUES ( '$ {quote_mysql:$sender_host_address}', \
  '$ {quote_mysql:$sender_address}', \
  '$ {quote_mysql:$local_part}', \

# keep the entry fresh.
  block_expires = DATE_SUB(now(), INTERVAL 8 HOUR) \
  WHERE relay_ip = '$ {quote_mysql:$sender_host_address}' \
    AND sender = '$ {quote_mysql:$sender_address}' \
    AND recipient = '$ {quote_mysql:$local_part}'

hide mysql_servers = localhost/exim/exim/exim%worD

Down in the acl section, we put the following. I also check for a properly-formatted HELO message from the sending SMTP server. Please change your hostname and IP address to match your own. I also do a check against known spammer IPs. Denying on the basis of EHLO checks is controversial, you may wish to omit these. However, denying based on the other party using your own name or IP I consider reasonable.

# deny addresses with funny letters and shell escapes
  deny    local_parts   = ^.*[@%!/|] : ^\.

# accept locally generated emails
  accept  hosts = :

# accept anyone who can authenticate
  accept authenticated = *

# accept null senders - bounced bounces or address verification
  accept  senders = : postmaster
          domains = +local_domains : +relay_domains

  deny    condition   = $ {if eq{$sender_helo_name}{}{yes}{no}}
    message     = Polite hosts say HELO first\n\
    Please see RFC 2821 section
    log_message = Bad HELO: Empty HELO

# drop messages without at least one dot in the HELO hostname
  drop message = HELO/EHLO must contain a Fully Qualified Domain Name
        condition = $ {if match{$sender_helo_name} \
                        {\N^[^.].*\.[^.]+$\N} \
                        {no}{yes} \

# surprising number of spammers spoof my own address
  drop message = HELO/EHLO invalid
  condition = $ {if match{$sender_helo_name} \
                          {your.hostname.org.za} \
                     {yes}{no} \

# and IP
  deny    condition     = $ {if eq{$sender_helo_name}{$interface_address}{yes}{no}}
      message       = Forged HELO: you are not $sender_helo_name
      log_message   = Forged HELO: is our interface address

# listed as a spammer
  deny  message         = $sender_host_address is listed at \
                          $dnslist_domain ($dnslist_value: $dnslist_text)
        dnslists        = sbl-xbl.spamhaus.org

# set a variable marking whether they were in the database
  warn set acl_m2 = $ {lookup mysql{GREYLIST_TEST}{$value}{0}}

# new entry in database - defer, and add them
  defer message = Greylisted - You have been greylisted. This is part of our standard anti-spam \
  measures and your mail system should automatically try again later.
      condition = $ {if eq{$acl_m2}{0}{1}}
      condition = $ {lookup mysql{GREYLIST_ADD}{yes}{no}}

# they are listed, but it is still too early
  defer message = Greylisted - try again in half an hour
      condition = $ {if eq{$acl_m2}{1}{1}}

# freshen entry
  warn condition = $ {lookup mysql{GREYLIST_UPDATE}{yes}{no}}

# could we deliver a bounce message if needed ?
  require verify = sender
# we relay for our friends only
  accept  domains = +relay_domains

  deny    message = relay not permitted

Finally, we need to put in a cron job to remove stale entries from the database. It will run as the exim user, on an hourly basis. We therefore drop the following file into the /etc/cron.hourly/ directory, as exim-db.

#! /bin/sh

# purge items from database on two conditions :-
# between 6 and 8 hours old (Greylisted address did not retry)
# 28 days old (stale)

/usr/bin/mysql --user=exim --password='exim%worD' exim << END_SQL

DELETE FROM greylist 
   WHERE now() > DATE_ADD(block_expires, INTERVAL 6 HOUR) AND 
   now() < DATE_ADD(block_expires, INTERVAL 8 HOUR);
DELETE FROM greylist 
   WHERE now() > DATE_ADD(block_expires, INTERVAL 35 DAY);


| Printer-friendly page | |