In short, a method for reducing spam at the MTA (mail server) outlined at http://projects.puremagic.com/greylisting/whitepaper.html .
From the whitepaper:
"Greylisting has been designed from the start to satisfy certain criteria:
User-level spam blocking, while somewhat effective has a few key drawbacks that make its use in the continuing spam war undesirable. A few of these are:
As a result, Greylisting is designed to be implemented at the MTA level, where we can cause the spammers the most amount of grief."
A tcl implementation using Sqlite as the database. Follow the directions on the Sqlite page to set up the Sqlite extension for use in your tcl installation.
#! /bin/sh # tcl \ exec tclsh "$0" ${1+"$@"} # Created 2004-06-09 by Steve Redler IV # greylisting engine based on the principles of # http://projects.puremagic.com/greylisting/whitepaper.html lappend auto_path lib package require sqlite #main test proc, returns PASS or FAIL to caller proc grey_test {ipadress sender recipient} { # Test if weve seen this triplet before # For all checks, we ignore records whose lifetime has expired # If we have not seen it, create a record describing it and # return a tempfail to the sending MTA. set curtriple "ipadress = '$ipadress' AND sender = '$sender' AND recipient = '$recipient'" greydb eval "SELECT * FROM triples WHERE $curtriple" query {} if {! [info exists query(ipadress)]} { greydb eval "INSERT INTO triples VALUES('$ipadress', '$sender', \ '$recipient', [clock seconds], [expr [clock seconds] + ($::blockmins * 60)], \ [expr [clock seconds] + ($::expiremins * 60)], 1, 0)" return "FAIL new record " } #update records whose lifetime has expired ie: inactive records if {$query(recexptime) < [clock seconds]} { incr query(blockedattempts) set recexptime [expr [clock seconds] + ($::expiremins * 60)] set blockexptime [expr [clock seconds] + ($::blockmins * 60)] greydb eval "UPDATE triples SET blockexptime = $blockexptime, blockedattempts = \ $query(blockedattempts), recexptime = $recexptime WHERE $curtriple" return "FAIL expired record" } # If we have seen it, and the block is not expired , # return a tempfail to the sending MTA. # Increment the failed count on the matching row. # If the sender is the special case of the null sender, # do not return a failure after RCPT, instead wait until # after the DATA phase. if {$query(blockexptime) > [clock seconds]} { incr query(blockedattempts) greydb eval "UPDATE triples SET blockedattempts = $query(blockedattempts) WHERE $curtriple" return "FAIL timed block " } # If we have seen it, and the block has expired and lifetime # has expired, then pass the email. # Increment the passed count on the matching row. # Reset the expiration time of the record to be the standard # lifetime past the current time. incr query(msgspassed) set recexptime [expr [clock seconds] + ($::expiremins * 60)] greydb eval "UPDATE triples SET msgspassed = $query(msgspassed), recexptime = $recexptime WHERE $curtriple" return "PASS email passed " } catch {source [file join $::confdir smtpd.conf]} catch {source smtpd.conf} if {! [info exists ::confdir]} {set ::confdir "."} #blockmins is suggested to be 60 minutes set ::blockmins 60 #expiremins is suggested to be 240 minutes set ::expiremins 240 #open and/or initialize the database sqlite greydb [file join $::confdir greylist.db] if {[greydb eval {Select name from SQLITE_MASTER}] == ""} { puts "Initializing databse [file join $::confdir greylist.db]" greydb eval {CREATE TABLE triples(ipadress text, sender text, recipient text, createtime int, blockexptime int, recexptime int, blockedattempts int, msgspassed int)} } puts "greylisting module loaded" #end of module # test code to simulate some usage if 1 { # set some short time, we dont got all day! set ::blockmins 1 set ::expiremins 2 set run 0 #heres some initial messages while {$run < 5} { puts [clock format [clock seconds] -format %T] puts " [grey_test 192.168.0.1 [email protected] [email protected]] 192.168.0.1 [email protected] [email protected]" puts " [grey_test 192.168.0.1 [email protected] [email protected]] 192.168.0.1 [email protected] [email protected]" puts " [grey_test 192.168.0.1 [email protected] [email protected]] 192.168.0.1 [email protected] [email protected]" after 20000 incr run } #wait for records to expire, see if they reset puts "pausing for 120 seconds" after 130000 puts [clock format [clock seconds] -format %T] puts " [grey_test 69.3.36.98 [email protected] [email protected]] 69.3.36.98 [email protected] [email protected]" puts " [grey_test 69.3.36.98 [email protected] [email protected]] 69.3.36.98 [email protected] [email protected]" puts " [grey_test 69.3.36.98 [email protected] [email protected]] 69.3.36.98 [email protected] [email protected]" #test again set run 0 while {$run < 5} { puts [clock format [clock seconds] -format %T] puts " [grey_test 69.3.36.98 [email protected] [email protected]] 69.3.36.98 [email protected] [email protected]" puts " [grey_test 69.3.36.98 [email protected] [email protected]] 69.3.36.98 [email protected] [email protected]" puts " [grey_test 69.3.36.98 [email protected] [email protected]] 69.3.36.98 [email protected] [email protected]" after 20000 incr run } #done }