Skip to content

SimpleGreylisting, MySQL (MariaDB) implementation

Alexandr S. Yatulchik edited this page Mar 4, 2020 · 4 revisions

This article was taken as a basis: https://github.com/Exim/exim/wiki/SimpleGreylisting.

If you use mysql to store users, you can use it for greylisting.

Below is a working example of greylisting working with Mysql, with cleaning old records using Exim itself.

Create 2 new tables in database, used for store users:

CREATE TABLE `greylist` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'Record ID',
  `msgid` varchar(150) NOT NULL DEFAULT '' COMMENT 'Message ID',
  `expire` int(11) DEFAULT NULL COMMENT 'Record Expiry time',
  `host` varchar(150) NOT NULL COMMENT 'Original IP address',
  `helo` varchar(150) NOT NULL COMMENT 'Original HELO',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8 COMMENT='Greylisted mail';

CREATE TABLE `resenders` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'Record ID',
  `host` varchar(150) NOT NULL DEFAULT '' COMMENT 'IP address',
  `helo` varchar(150) NOT NULL DEFAULT '' COMMENT 'HELO name',
  `added` int(11) NOT NULL COMMENT 'Record add time',
  `updated` int(11) NOT NULL COMMENT 'Record update time',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8 COMMENT='Known resenders';

Edit exim.conf and add:

# delete entries from greylist table, older than 14 days
GREYLIST_CLEAN = DELETE FROM `greylist` WHERE (`expire` < UNIX_TIMESTAMP(DATE_ADD(now(),INTERVAL -14 DAY)))

# update entries in resenders table
GREYLIST_UPDATE = UPDATE `resenders` SET `updated` = UNIX_TIMESTAMP(now()) WHERE `helo` = '${quote_mysql:$sender_helo_name}' AND `host` = '$sender_host_address'}

# delete entries from table resenders, not updated 90 days
GREYLIST_DELETE = DELETE FROM `resenders` WHERE (`updated` < UNIX_TIMESTAMP(DATE_ADD(now(),INTERVAL -90 DAY)))

# ACL: GREYLIST
greylist_mail:

# Clean greylist records at 00 and 30 of all day minutes
  warn
        condition       = ${if or {{eq {${substr{10}{2}{$tod_zulu}}}{00}}{eq {${substr{10}{2}{$tod_zulu}}}{30}}}{yes}{no}}
        set acl_m3      = ${lookup mysql{GREYLIST_CLEAN}}
        set acl_m4      = ${lookup mysql{GREYLIST_DELETE}}
        logwrite        = Old entries was deleted from the greylist tables.

# Accept if message was generated locally
  accept
        hosts           = +relay_from_hosts : +wl_hosts

# Accept if message was sent by authenticated clients
  accept
       authenticated   = *

# Accept mail from hosts which are known to resend their mail.
  accept
        condition       = ${lookup mysql{SELECT `host` FROM `resenders` WHERE `helo` = '${quote_mysql:$sender_helo_name}' AND `host` = '$sender_host_address'} {1}}
        set acl_m5      = ${lookup mysql{GREYLIST_UPDATE}}

# Generate a hashed 'identity' for the mail, as described above.
  warn
        set acl_m_greyident = ${hash{20}{62}{$sender_address$recipients$h_message-id:}}

# Attempt to look up this mail in the greylist database. If it's there, remember
# the expiry time for it; we need to make sure they've waited long enough.
  warn
        set acl_m_greyexpiry = ${lookup mysql{SELECT `expire` FROM `greylist` WHERE `msgid` = '${quote_mysql:$acl_m_greyident}'}{$value}}

# If there's absolutely nothing suspicious about the email, accept it. BUT...
  accept
        condition       = ${if eq {$acl_m_greylistreasons}{} {1}}
        condition       = ${if eq {$acl_m_greyexpiry}{} {1}}

# ..if this same mail was greylisted before (perhaps because it came from a
# host which *was* suspicious), then we still want to mark that original host
# as a "known resender". If we don't, then hosts which attempt to deliver from
# a dodgy Legacy IP address but then fall back to using IPv6 after greylisting
# will *never* see their Legacy IP address added to the 'known resenders' list.
  accept
        condition       = ${if eq {$acl_m_greylistreasons}{} {1}}
        acl             = write_known_resenders

# If the mail isn't already the database -- i.e. if the $acl_m_greyexpiry
# variable we just looked up is empty -- then try to add it now. This is
# where the 15 minute timeout is set ($tod_epoch + 900), should you wish
# to change it.
  warn
        condition       = ${if eq {$acl_m_greyexpiry}{} {1}}
        set acl_m_dontcare = ${lookup mysql{INSERT INTO `greylist` ( `msgid`, `expire`, `host`, `helo` )  VALUES ( '$acl_m_greyident', '${eval10:$tod_epoch+900}', '$sender_host_address', '${quote_mysql:$sender_helo_name}' )}}

# Be paranoid, and check if the insertion succeeded (by doing another lookup).
# Otherwise, if there's a database error we might end up deferring for ever.
  defer
        condition       = ${if eq {$acl_m_greyexpiry}{} {1}}
        condition       = ${lookup mysql{SELECT `expire` FROM `greylist` WHERE msgid = '${quote_mysql:$acl_m_greyident}'} {1}}
        message         = Greylisted: ${sg {$acl_m_greylistreasons}{\n}{ }}

# Handle the error case (which should never happen, but would be bad if it did).
# First by whining about it in the logs, so the admin can deal with it...
  warn
        condition       = ${if eq {$acl_m_greyexpiry}{} {1}}
        log_message     = Greylist insertion failed. Bypassing greylist.

# ... and then by just accepting the message.
  accept
        condition       = ${if eq {$acl_m_greyexpiry}{} {1}}

# OK, we've dealt with the "new" messages. Now we deal with messages which _were_ already in the database...
# If the message was already listed but its time hasn't yet expired, keep rejecting it
  defer
        condition       = ${if > {$acl_m_greyexpiry}{$tod_epoch}}
        message         = Greylisted: greylisting in progress... retry after ${eval10:$acl_m_greyexpiry-$tod_epoch} seconds

  accept
        acl             = write_known_resenders

# The message was listed but it's been more than five minutes. Accept it now and whitelist
# the _original_ sending host by its { IP, HELO } so that we don't delay its mail again.
write_known_resenders:
  warn
        set acl_m_orighost      = ${lookup mysql{SELECT `host` FROM `greylist` WHERE `msgid` = '${quote_mysql:$acl_m_greyident}'}{$value}}
        set acl_m_orighelo      = ${lookup mysql{SELECT `helo` FROM `greylist` WHERE `msgid` = '${quote_mysql:$acl_m_greyident}'}{$value}}
        set acl_m_dontcare      = ${lookup mysql{INSERT INTO `resenders` (`host`, `helo`, `added`, `updated`) VALUES ( '$acl_m_orighost', '${quote_mysql:$acl_m_orighelo}', '$tod_epoch', '$tod_epoch' ) }}
        logwrite                = Added host $acl_m_orighost with HELO '$acl_m_orighelo' to known resenders

  accept

add to the bottom of acl_check_data before accept:

# new to me? greylisting
  warn
        set acl_m_greylistreasons = Sender is new to me\n$acl_m_greylistreasons

# do not accept until greylisting passes
  require
        acl             = greylist_mail
Clone this wiki locally