[exim] BATV Implementation Complete

Top Page
Delete this message
Reply to this message
Author: Chris Meadors
Date:  
To: exim-users
Subject: [exim] BATV Implementation Complete
If anyone has been paying attention over the last couple weeks I've been
trying to put together a BATV (Bounce Address Tag Validation)
configuration on my server. BATV prevents bounces from being returned
to your server that did not originate from it. Every time there is a
virus outbreak I get a load of messages returned to me from viruses that
forged my return address. Even worse is the number of support calls
that have to be answered from users who want to know why they are
getting messages returned that they did not send.

The examples in the spec.txt file assume tagging will only be performed
for select users. While this may be a little less heavy handed. That
is, the return address will only be tagged for users that send through
the server exclusively, and not those who use third-party services to
deliver messages with their own address. The vast majority of my users
only send mail using my server. So I will sign all outbound addresses
from my domains, and reject any bounces that are returned without a
signature. So there is a rare chance that a valid bounce could be
turned away, but with the number of false bounces I see every day, the
positives outweigh the negatives in my case.

What follows are the changes made to the default Exim configuration
file. I will make notes where I feel explanations are needed.


Note: Somewhere near the top of the configuration file add this macro
definition. Change "privatekey" to something unique for your server.

# Define the server-wide private key to be used in BATV signatures.

BATVKEY = privatekey


Note: Place the following in the "acl_check_rcpt" section. First
bounces need to be limited to having only one recipient, so that later
in the DATA ACL the $recipients variable will only have one address.
Then check the validity of any signed address from empty senders. This
will only deny the recipient address if the signature has expired or the
hash is invalid. Unsigned addresses will pass. One thing of which to
make special note, the way the $return_path is modified when sending
messages is caseful. The hash check will fail if case of the local part
is not preserved.

# Bounces must only be returned to one recipient.

  deny    message       = bounce messages must contain only one RCPT
          senders       = :
          condition     = ${if >{$rcpt_count}{1}}


# Empty sender (a bounce) to prvs address, check signature.

  deny    message       = invalid or expired BATV signature
          senders       = :
          control       = caseful_local_part
          condition     = ${prvscheck {$local_part@$domain}{BATVKEY}{1}}
         !condition     = $prvscheck_result
          control       = caselower_local_part



Note: This next bit can go in the existing "acl_check_data" section, or
you can create a predata check and place it there. I'm running mine
after the DATA has been completed, because I do not know for sure how
other MTAs will react to an error response directly after the DATA
command.

# If this message is a true bounce (i.e. made it to DATA), and not just a
# callout, require that the recipient not be unsigned.

  deny    message     = bounce messages must be returned to a BATV signed address
          senders     = :
         !condition   = ${prvscheck {$recipients}{BATVKEY}{true}}



Note: Place this router before existing "dnslookup" router. It may
also be useful create another domain list for a BATV signing blacklist.
I've found a few servers that will reject sender addresses that contain
either an '=' or a '/', both of which occur in signed return-paths.

# This router will be used for senders with addresses at any of the domains
# defined by "domainlist local_domains". Messages matching this condition
# will be sent to the external_smtp_batv transport to have their
# return-path signed with a BATV key. Other messages that do not have a local
# domain will fall through to the next router.

dnslookup_batv:
driver = dnslookup
condition = ${if match_domain{$sender_address_domain}{+local_domains}}
domains = ! +local_domains
transport = external_smtp_batv
ignore_target_hosts = 0.0.0.0 : 127.0.0.0/8


Note: This router also needs to go very early on in the list. I put
mine just before the "system_aliases" router so it could still do its
work on the decoded addresses.

# This router removes the BATV signature to return the original recipient
# address.

batv_redirect:
driver = redirect
data = ${prvscheck {$local_part@$domain}{BATVKEY}}


Note: Finally the transport that signs outbound messages.

# This transport is used for delivering messages over SMTP connections,
# while adding BATV signing to the return-path.

external_smtp_batv:
driver = smtp
return_path = ${prvs {$return_path}{BATVKEY}}