This is the old config snippet I am about to copy from one config to another and, while I'm at it, I thought I'd put it here for future reference.
The good thing about this approach is that almost the whole thing is completely done in the config file and requires almost no setup elsewhere. The bad thing is that I don't think it will perform well on high load. On my MX with approximately 10K incoming connections a day it performs alright.
So, this is how it's done. Exim needs to be compiled with sqlite support, of course. First step is to initialize the database with this table:
CREATE TABLE greylist ( gl_mailid text NOT NULL, gl_hostid text NOT NULL, gl_ctime integer NOT NULL DEFAULT ( strftime('%s','now') ), gl_atime integer NOT NULL DEFAULT ( strftime('%s','now') ), gl_btime integer NOT NULL DEFAULT ( strftime('%s','now') ), gl_ptime integer, gl_blocked integer DEFAULT 1, gl_passed integer DEFAULT 0, PRIMARY KEY (gl_mailid,gl_hostid) );
Then put this (or similar)
GREYDB=/var/lib/exim4/greylist.db
line into main section of exim configuration.
This acl does the whole thing:
acl_greylist: warn set acl_c_gl_mailid = $sender_address+$local_part@$domain set acl_c_gl_hostid = $sender_host_address+$sender_helo_name set acl_c_gl = ${lookup sqlite {GREYDB select *, \ strftime('%s','now')-gl_atime as sincea, strftime('%s','now')-gl_ctime as sincec from greylist \ where gl_mailid='${quote_sqlite:$acl_c_gl_mailid}' and gl_hostid='${quote_sqlite:$acl_c_gl_hostid}'; \ }} # defer strangers defer condition = ${if eq {$acl_c_gl}{} {true}{false}} set acl_c_nothing = ${lookup sqlite {GREYDB insert into greylist (gl_mailid,gl_hostid) VALUES \ ('${quote_sqlite:$acl_c_gl_mailid}','${quote_sqlite:$acl_c_gl_hostid}'); }} set acl_c_gl_message = Come back in some 10 minutes, stranger # accept passers accept condition = ${if >{${extract{gl_passed}{$acl_c_gl}}}{0} {true}{false}} set acl_c_nothing = ${lookup sqlite {GREYDB update greylist SET gl_passed=gl_passed+1, gl_atime=strftime('%s','now') \ where gl_mailid='${quote_sqlite:$acl_c_gl_mailid}' and gl_hostid='${quote_sqlite:$acl_c_gl_hostid}'; \ }} # accept after 12 mins of silence or 3 hours of knocking accept condition = ${if or{\ {>{${extract{sincea}{$acl_c_gl}}}{720}}\ {>{${extract{sincec}{$acl_c_gl}}}{10800}}\ }{true}{false}} set acl_c_nothing = ${lookup sqlite {GREYDB update greylist SET gl_atime=strftime('%s','now'), gl_passed=gl_passed+1, \ gl_ptime=coalesce(gl_ptime,strftime('%s','now')) \ where gl_mailid='${quote_sqlite:$acl_c_gl_mailid}' and gl_hostid='${quote_sqlite:$acl_c_gl_hostid}'; \ }} defer set acl_c_nothing = ${lookup sqlite {GREYDB update greylist SET gl_atime=strftime('%s','now'), gl_blocked=gl_blocked+1, \ gl_btime=strftime('%s','now') \ where gl_mailid='${quote_sqlite:$acl_c_gl_mailid}' and gl_hostid='${quote_sqlite:$acl_c_gl_hostid}'; \ }} set acl_c_gl_message = Come back in some 10 minutes, buddy
Finally, somewhere in acl_check_rcpt it would make sense to call the thing. Something like this:
warn acl = acl_greylist
So the only thing left is a daily cleanup:
DELETE FROM greylist WHERE 3600*24*gl_passed < (strftime('%s','now')-gl_atime);
And, unless I am mistaken (which is not completely unlikely) the greylisting setup is complete.