[followups to spambayes@python.org please, unless you're specifically
concerned about some particular bit of email policy for python.org]
OK, after much fiddling with and tweaking of /etc/exim/exim4.conf and
/etc/exim/local_scan.py on mail.python.org, I am fairly confident that
I can start harvesting all incoming email at a moment's notice.  For the
record, here's how it all works:
* exim4.conf works almost exactly the same as before if the file
/etc/exim/harvest does not exist.  That is, any "junk mail
condition" that can be detected by Exim ACLs (access control lists)
is handled entirely in exim4.conf: the message is rejected before it
ever gets to local_scan.py.  This covers such diverse cases as
"message from known spammer" (reject after every RCPT TO command),
"no message-id header", and "8-bit chars in subject" (both rejected
after the message headers/body are read).
The main things I have changed in the absence of /etc/exim/harvest
are:
- don't check for 8-bit chars in "From" header -- the vast
majority of hits for this test were bounces from some
Asian ISP; the remaining hits should be handled by SpamAssassin
- do header sender verification (ie. ensure that there's a
verifiable email address in at least one of "From", "Reply-to",
and "Sender") as late as possible, because it requires DNS
lookups which can be slow (and can also make messages that
should have been rejected merely be deferred, if those DNS
lookups timeout)
* if /etc/exim/harvest exists, then the behaviour of all of those
ACLs in exim4.conf suddenly changes: instead of rejecting recipients
or messages, they add an X-reject header to the message.  This
header is purely for internal use; it records the name of the folder
to which the rejected message should be saved, and also gives the
SMTP error message which should ultimately be used to reject
the message.
Thus, those messages will now be seen by local_scan.py, which now
looks for the X-reject header.  If found, it uses the folder name
specified there to save the message, and then rejects it with the
SMTP error message also given in X-reject.  (Currently X-reject is
retained in saved messages.)
If a message was not tagged with X-reject, then local_scan.py
runs the usual virus and spam checks.  (Namely, my homebrew
scan for attachments with filenames that look like Windows
executables, and a run through SpamAssassin.)  The logic is
basically this:
if virus:
folder = "virus"
else:
run through SpamAssassin
if score >= 10.0:
folder = "rejected-spam"
elif score >= 5.0:
folder = "caught-spam"
Finally, local_scan.py writes the message to the designated folder.
By far the biggest folder will be "accepted" -- the server handles
2000-5000 incoming messages per day, of which maybe 100-500 are junk
mail.  (Oops, just realized I haven't written the code that actually
saves the message -- d'ohh!  Also haven't written anything to
discriminate personal email, which I must do.  Sigh.)
* finally, the big catch: waiting until after you've read the message
headers and body to actually reject the message is problematic,
because certain broken MTAs (including those used by some spammers)
don't consider a 5xx after DATA as a permanent error, but keep
retrying.  D'ohh.  This is a minor annoyance currently, where a fair
amount of stuff is rejected at RCPT TO time.  But in harvest mode,
*everything* (with the exception of people probing for open relays)
will be rejected at DATA time.  So I have cooked up something called
the ASBL, or automated sender blacklist.  This is just a Berkeley DB
file that maps (sender_ip, sender_address) to an expiry time.  When
local_scan() rejects a message from (sender_ip, sender_address) --
for whatever reason, including finding an X-reject header added by
an ACL in exim4.conf -- it adds a record to the ASBL, with an expiry
time 3 hours in the future.  Meanwhile, there's an ACL in exim4.conf
that checks for records in the ASBL; if there's a record for the
current (sender_ip, sender_address) that hasn't expired yet, we
reject all recipients without ever looking at the message headers or
body.
The downside of this from the point-of-view of corpus collection is
that if some jerk is busily spamming *@python.org, one SMTP
connection per address, we will most likely only get one copy.  This
is a win if you're just thinking about reducing server load and
bandwidth, but I'm not sure if it's helpful for training spam
detectors.  Tim?
Happy harvesting --
Greg
--
Greg Ward <gward@python.net>                         http://www.gerg.ca/
Budget's in the red?  Let's tax religion!
-- Dead Kennedys