Hi all,
I previously ran SpamAssassin via the SA-Exim local_scan plugin
(
http://marc.merlins.org/linux/exim/sa.html), partly due to its
"teergrubing" facility. The way this worked was that if a message was
deemed to be spam, then my server would stall the sending host by
sending a multi-line 451 response after the DATA, one line about every
10 seconds for 30 minutes or so -- essentially occupying one out of
~65500 sockets available on the spamming machine for that long as well.
Over time, I found that it was only so effective, because most spamware
clients do not honor the "-" between the status code (in this case 451)
and the following text. They would simply disconnect after the first
line of the response.
So I now do something a little different. For various reasons I no
longer use SA-Exim; instead, I have ACLs with "delay = 20s" at various
points in the SMTP transaction (see below). For instance,
- If a host provides a suspicious HELO greeting, they are stalled for
20 seconds right there, again after the MAIL, RCPT, and DATA
commands, after which the message is rejected.
- If the sender/callout verfication fails, that's 20 seconds after
MAIL, another 20 seconds after RCPT, and a third time after DATA
(but in this case, I do eventually accept the mail, unless SA flags
it as spam as well).
- If the recipient address does not exist, then that's 20 seconds
after RCPT TO:.
- And so on, and so forth. I'll include the various ACLs below.
A typical dialogue might be:
<- 220 dot.slett.net ESMTP Exim 4.30 Fri, 19 Mar 2004 23:24:24 -0800
-> EHLO bogushost
=== 20 second delay ===
<- 250-net.slett.net Hello plain.knkco.com [216.127.72.3]
<- 250-SIZE 52428800
<- 250-8BITMIME
<- 250-PIPELINING
<- 250-STARTTLS
<- 250 HELP
-> MAIL FROM:<test@???>
=== 20 second delay ===
<- 250 OK
-> RCPT TO:<tor@???>
=== 20 second delay ===
<- 250 Accepted
-> DATA
<- 354 Enter message, ending with "." on a line by itself
-> [...]
<- 250 OK id=1B4arF-0003lQ-Ak
-> QUIT
=== 20 second delay ===
<- 221 net.slett.net closing connection
This has worked great so far (a few days). However, I can see a couple
of issues looming:
- What if someone else does a "verify = sender/callback=15s" on our
mail, but they have a misconfigured mailname on their system?
They would be subject to a 20 second wait after HELO, and thus
time out.
(I don't expect this to be a major concern in practice -- the
default callback timeout is 30s; and if my mail is ever rejected
based on a callback, I can always deal with it at that point).
- Am I breaking any standards? (RFC2822, etc)
(Yeah, I know, I'll RTFM one of these days).
- My machine is not busy (perhaps the largest source of incoming mail
is this very list), so I don't expect to be processing and stalling
a whole slew of incoming SMTP connections at once - but is there a
limit to how many fork()s the Exim daemon creates for processing
incoming mails? Is that configurable?
Thanks for any replies!
-tor
My releveant ACL snippets follow:
========================================================================
acl_check_helo:
# If the remote host greets with an IP address, then stall for 20
# seconds, and prepare a message in $acl_c0. Later we will use the
# presence of this message to stall the sender further and eventually
# to reject the message after the DATA block.
accept
condition = ${if isip {$sender_helo_name}{true}{false}}
delay = 20s
set acl_c0 = You greeted me with an IP address. \
What I wanted was your name.
# Likewise if the peer greets with a name that resolves to our own address
accept
condition = ${if eq {${lookup dnsdb{a=$sender_helo_name} {$value}}} \
{$interface_address} \
{true}{false}}
delay = 20s
set acl_c0 = Did you say your name was $sender_helo_name? \
Give me a break, impostor!
# If HELO verification fails, we stall for 20 seconds, and prepare a
# message in acl_c1. We will later use the presence of this message
# to stall the sender further and generate a warning.
warn !hosts = +relay_from_hosts
!verify = helo
set acl_c1 = Remote host $sender_host_address \
incorrectly identified itself as $sender_helo_name
log_message = $acl_c1
delay = 20s
accept
acl_check_helo:
# If the remote host greets with an IP address, then stall for 20
# seconds, and prepare a message in $acl_c0. Later we will use the
# presence of this message to stall the sender further and eventually
# to reject the message after the DATA block.
accept
condition = ${if isip {$sender_helo_name}{true}{false}}
delay = 20s
set acl_c0 = You greeted me with an IP address. \
What I wanted was your name.
# Likewise if the peer greets with a name that resolves to our own address
accept
condition = ${if eq {${lookup dnsdb{a=$sender_helo_name} {$value}}} \
{$interface_address} \
{true}{false}}
delay = 20s
set acl_c0 = Did you say your name was $sender_helo_name? \
Give me a break, impostor!
# If HELO verification fails, we stall for 20 seconds, and prepare a
# message in acl_c1. We will later use the presence of this message
# to stall the sender further and generate a warning.
warn !hosts = +relay_from_hosts
!verify = helo
set acl_c1 = Remote host $sender_host_address \
incorrectly identified itself as $sender_helo_name
log_message = $acl_c1
delay = 20s
accept
acl_check_rcpt:
# Accept if the source is local SMTP (i.e. not over TCP/IP). We do this by
# testing for an empty sending host field.
accept hosts = :
# Deny if the local part contains @ or % or / or | or !. These are rarely
# found in genuine local parts, but are often tried by people looking to
# circumvent relaying restrictions.
#
# Also deny if the local part starts with a dot. Empty components aren't
# strictly legal in RFC 2822, but Exim allows them because this is common.
# However, actually starting with a dot may cause trouble if the local part
# is used as a file name (e.g. for a mailing list).
#
deny local_parts = ^.*[@%!/|] : ^\\.
# Accept mail to postmaster in any local domain. However, if we
# previously generated a message in $acl_c0 or $acl_c1, stall the sender.
#
accept
local_parts = postmaster
domains = +local_domains : +relay_to_domains
delay = ${if or {{def:acl_c0}{def:acl_c1}}{20s}{0s}}
# If the address is in a local domain or in a domain for which are
# relaying, but is invalid, stall the sender and then reject.
#
deny domains = +local_domains : +relay_to_domains
message = unknown user
!verify = recipient/callout=20s,defer_ok,random
delay = 20s
# Otherwise, if the address is in one of our domains, accept it.
# However, if we have previously generated a message in acl_c0 or acl_c1,
# stall the sender.
accept
domains = +local_domains : +relay_to_domains
delay = ${if or {{def:acl_c0}{def:acl_c1}}{20s}{0s}}
# Accept if the message comes from one of the hosts for which we are an
# outgoing relay. Recipient verification is omitted here, because in many
# cases the clients are dumb MUAs that don't cope well with SMTP error
# responses. If you are actually relaying out from MTAs, you should probably
# add recipient verification here.
#
accept
hosts = +relay_from_hosts
# Accept if the message arrived over an authenticated connection, from
# any host. Again, these messages are usually from MUAs, so recipient
# verification is omitted.
#
accept
authenticated = *
# Reaching the end of the ACL causes a "deny", but we might as well give
# an explicit message.
#
deny message = relay not permitted
delay = 20s
acl_check_data:
# Deny if we have previously given a reason for doing so in $acl_c0
deny message = $acl_c0
condition = ${if def:acl_c0 {true}{false}}
delay = 20s
# Add Message-ID if missing
warn condition = ${if !def:h_Message-ID: {1}}
hosts = +relay_from_hosts
message = Message-ID: <E$message_id@$primary_hostname>
# Deny unless the address list headers are syntactically correct.
deny message = Message headers fail syntax check
!acl = acl_whitelist_local_deny
!verify = header_syntax
delay = 20s
# require that there is a verifiable sender address in at least
# one of the "Sender:", "Reply-To:", or "From:" header lines.
warn
!acl = acl_whitelist_local_deny
!verify = header_sender
set acl_c1 = No verifiable address in message headers
message = X-Sender-Verify-Failed: $acl_c1
log_message = $acl_c1
# enforce a message-size limit
deny message = Message size $message_size is larger than limit of MESSAGE_SIZE_LIMIT
condition = ${if >{$message_size}{MESSAGE_SIZE_LIMIT}{yes}{no}}
# --- BEGIN EXISCAN configuration ---
# Do not scan messages submitted from our own hosts
# and locally submitted messages. Since the DATA ACL
# is not called for messages not submitted via SMTP
# protocols, we do not need to check for an empty
# host field.
#
accept hosts = 127.0.0.1:+relay_from_hosts
# Reject messages that have serious MIME errors.
#
deny message = Serious MIME defect detected ($demime_reason)
demime = *
condition = ${if >{$demime_errorlevel}{2}{1}{0}}
delay = 20s
# Unpack MIME containers and reject file extensions used by worms.
# This calls the demime condition again, but it will return cached results.
# Note that the extension list may be incomplete.
#
deny message = We do not accept ".$found_extension" attachments here.
demime = bat:btm:cmd:com:cpl:dll:exe:lnk:msi:pif:prf:\
reg:scr:vbs:url
delay = 20s
# Invoke SpamAssassin to obtain $spam_score and $spam_report.
# Add appropriate headers to the message, and if it turns out
# to be spam, stall and pretend to reject the message.
# Note that even though the "spam" condition is called several
# times, the results are cached, and only the first call invokes
# SpamAssassin.
warn message = X-Spam-Score: $spam_score
spam = mail:true
warn message = X-Spam-Status: $spam_report
spam = mail:true
accept
spam = mail
control = fakereject
logwrite = :main: Classified as spam (score $spam_score)
logwrite = :reject: SPAM: $spam_report
delay = 20s
# If the message is not flagged as spam, accept it normally.
# However, stall if we previously generated a warning in $acl_c1.
accept
logwrite = :main: Classified as ham (score $spam_score)
delay = ${if def:acl_c1 {20s}{0s}}