Main | Exim 4 Anti-SPAM Configuration   


The default Exim4 configuration is generally pretty good. Below, these are the additional tweaks I've made to the default. It needs more comments, and I'm currently mostly doing this to document things for myself since I'm in the middle of making a replacement Exim4 mail server.

ClamAV scan and attachment type blocking setup:

 #Main section:
   av_scanner = clamd:/var/run/clamav/clamd.ctl
 #
 # SEE "AVSCANNER PART 2" at the end of acl_check_data for the 2nd part.
 # Note that this requires exim4_daemon_heavy or an Exim4 daemon patched
 # with the exiscan_acl patch in order to function

Main configuration variable settings

 #Reject email that is over the size limit:
   MESSAGE_SIZE_LIMIT = 10M

 #Delete frozen messages after a time instead of having them build up:
   MAIN_TIMEOUT_FROZEN_AFTER = 1d

 #Set which DNSRBLs to check IP addresses with:
   CHECK_RCPT_IP_DNSBLS = sbl-xbl.spamhaus.org : bl.spamcop.net : cbl.abuseat.org

 #Set maximum bounce message size sent to 16K
   return_size_limit = 16K

HELO/EHLO checks to block obviously falsified inbound connections

 # ***
 #   Checks HELO/EHLO line for RFC2821 compatibility
 # ***
 #
 acl_check_helo:
  # Note:
  #   Underscores in hostnames are auotmatically rejected by Exim4 itself
  #   There's no need to make a HELO check for those.

  # A remote host can't be localhost or localdomain
  # Adds a little tarpitting at the same time with a 15 second delay
  #
  drop
    hosts       = !+relay_from_hosts
    condition   = ${if match\
                    {\N^(127\.0\.0\.1|localhost(\.localdomain)?)$\N}\
                    {yes}{no}}
    message     = $sender_helo_name is an invalid HELO/ELHO\n\
                  Required to be a valid FQDN as per RFC 2821
    log_message = $sender_host_name forged localhost
    delay      = 15s

  # A remote host using my own HELO is wrong
  # NOTE: MODIFY FOR MAIL SERVER IP and change domain.tld to proper domain name
  #
  drop
    hosts       = !+relay_from_hosts
    condition   = ${if match\
                    {\N^((\[)?111\.222\.33\.44](\])?|(.*\.)?domain\.tld)$\N}\
                    {yes}{no}}
    message     = Forged HELO detected and logged.  Connection terminated.
    log_message = $sender_host_name forged our info
    delay      = 15s

  # Another check on domain names being used in HELO from others
  # This matches the exact HELO greeting used and compares it to
  # the other domain names we accept.  Unfortunately certain mailers
  # I currently relay for seem to be using a helo of 'domain.tld',
  # not an FQDN, so I cannot include +relay_to_domains for this,
  # making the check somewhat pointless.
  #
  drop
    condition   = ${if match_domain\
                       {$primary_hostname:+local_domains}\
                       {true}{false}}
    #condition   = ${if match_domain\
    #                   {$primary_hostname:+local_domains:+relay_to_domains}\
    #                   {true}{false}}
    message     = Forged HELO detected and logged.  Connection terminated.
    log_message = $sender_host_name used our name in HELO/EHLO greeting.
    delay      = 15s

  # Don't accept an IP address for a HELO greeting
  # 
  drop
    hosts       = !+relay_from_hosts
    condition   = ${if isip {true}{false}}
    message     = An IP address is unacceptable for a HELO greeting.\n\
                  HELO greeting must conform to RFC 2821 -- connection terminated.
    log_message = $sender_host_name used raw IP address in HELO/EHLO greeting
    delay      = 15s

  # Helo should not be RFC 1918 address
  #
  drop
    hosts       = !+relay_from_hosts
    condition   = ${if match\
       {\N^(\[)?(10\.[0-9]{1,3}|172\.(1[6-9]|2[0-9]|31)|192\.168)\.[0-9]{1,3}\.[0-9]{1,3}(\])?$\N}\
       {yes}{no}}
    message     = RFC 1918 IP address in HELO
    log_message = $sender_host_name used RFC 1918 reserved IP in HELO
    delay       = 15s

  # A proper domain will contain at least one period.
  # A good percentage of spam worms HELO with random characters without a period.
  # thus obviously not containing a FQDN in the HELO greeting
  #
  drop
    hosts       = !+relay_from_hosts
    condition   = ${if match{\\.}{no}{yes}}
    message     = Invalid HELO/EHLO.  Should be FQDN per RFC 2821
    log_message = $sender_host_name has no . in HELO name
    delay       = 15s

  # If the HELO DNS check failed, delay the SMTP transaction for a bit
  # to slow down the scum bags
  #warn
  #  hosts       = !+relay_from_hosts
  #  log_message = delayed 15sec after helo
  #  !verify     = helo
  #  delay       = 15s

Checks done on the entry made at the SMTP "mail from:" entry

acl_check_mail:

  #
  # A domain absolutely can't be localhost or localdomain
  #
  drop
    condition   = ${if match\
                    {\N^(localhost|localhost\.localdomain|localdomain)$\N}\
                    {yes}{no}}
    message     = '$sender_address_domain' an is invalid domain
    log_message = $sender_host_name tried to use localhost email address
    delay       = 15s

  # Deny faked Yahoo email addresses
  #
  #deny
  #  senders     = *@yahoo.com
  #  condition   = ${if match {\Nyahoo.com$\N}{no}{yes}}
  #  message     = Address rejected -- appears to be faked Yahoo address
  #  log_message = Fake Yahoo from $sender_host_name
  #  delay       = 15s

  # Deny faked Hotmail email addresses
  #
  deny
    senders     = *@hotmail.com
    condition   = ${if match {\Nhotmail.com$\N}{no}{yes}}
    message     = Address rejected -- appears to be faked Hotmail address
    log_message = Fake hotmail from $sender_host_name
    delay       = 15s

  # Deny faked MSN email addresses
  #
  deny
    senders     = *@msn.com
    condition   = ${if match {\N(hotmail|msn).com$\N}{no}{yes}}
    message     = Address rejected -- appears to be faked MSN address
    log_message = Fake MSN from $sender_host_name
    delay       = 15s

  # Deny faked AOL email addresses
  #
  deny
    senders     = *@aol.com
    condition   = ${if match {\Nmx.aol.com$\N}{no}{yes}}
    message     = Address rejected -- appears to be faked AOL address
    log_message = Fake AOL from $sender_host_name
    delay       = 15s

Checks on SMTP "rcpt to:" entry

acl_check_rcpt:

  # GREYLIST, RCPT section (part 1)
  # Note: requires installing 'greylistd'
  #
  # greylistd(8) configuration follows.
  # This statement has been added by "greylistd-setup-exim4",
  # and can be removed by running "greylistd-setup-exim4 remove".
  # Any changes you make here will then be lost.
  # 
  # Perform greylisting on incoming messages from remote hosts.
  # We do NOT greylist messages with no envelope sender, because that
  # would conflict with remote hosts doing callback verifications, and we
  # might not be able to send mail to such hosts for a while (until the
  # callback attempt is no longer greylisted, and then some).
  #
  # We also check the local whitelist to avoid greylisting mail from
  # hosts that are expected to forward mail here (such as backup MX hosts,
  # list servers, etc).
  #
  # Because the recipient address has not yet been verified, we do so
  # now and skip this statement for non-existing recipients.  This is
  # in order to allow for a 550 (reject) response below.  If the delivery
  # happens over a remote transport (such as "smtp"), recipient callout
  # verification is performed, with the original sender intact.
  #
  # NOTE: normal callout verification is NOT being performed anymore because
  # it was causing issues I did NOT like.  The line below was placed after
  # the 'domains' line in the section below.  -- Chris
  #  verify         = recipient/callout=20s,use_sender,defer_ok
  defer
    message        = $sender_host_address is not yet authorized to deliver \
                     mail from <$sender_address> to <$local_part@$domain>. \
                     Please try later.
    log_message    = greylisted.
    !senders       = :
    !hosts         = : +relay_from_hosts : \
                     ${if exists {/etc/greylistd/whitelist-hosts}\
                                 {/etc/greylistd/whitelist-hosts}{}} : \
                     ${if exists {/var/lib/greylistd/whitelist-hosts}\
                                 {/var/lib/greylistd/whitelist-hosts}{}}
    !authenticated = *
    !acl           = acl_whitelist_local_deny
    domains        = +local_domains : +relay_to_domains
    condition      = ${readsocket{/var/run/greylistd/socket}\
                                 {--grey \
                                  $sender_host_address \
                                  $sender_address \
                                  $local_part@$domain}\
                                 {5s}{}{false}}

  # Accept mail to postmaster in any local domain, regardless of the source,
  # and without verifying the sender.
  #
  accept
    .ifndef CHECK_RCPT_POSTMASTER
    local_parts = postmaster
    .else
    local_parts = CHECK_RCPT_POSTMASTER
    .endif
    #  Not sure if this latter part is necessary
    domains = +local_domains : +relay_to_domains


  # ***
  #   Drop the connection if the envelope sender is empty, but there is
  #   more than one recipient address.  Legitimate DSNs are never sent
  #   to more than one address.
  # ***
  #
  drop
    senders     = : postmaster@*
    condition   = $recipients_count
    message     = Legitimate mail bounces are never sent to more than one recipient.
    log_message = rejected bounce message with multiple recipients
    delay       = 15s


  # ***
  #   Deny any more recipients if there have been a number of failed
  #   recipients already, and add incremental delay
  # ***
  #
  deny
    condition     = ${if >{2} {yes}{no}}
    message       = Too many bad recipients
    log_message   = $rcpt_fail_count failed recipient attempts
    delay         = ${eval: $rcpt_fail_count * 10}s

  # Check against classic DNS "black" lists (DNSBLs) which list
  # sender IP addresses.
  # warn
  #   message = X-Warning: $sender_host_address is listed at $dnslist_domain ($dnslist_value: $dnslist_text)
  .ifdef CHECK_RCPT_IP_DNSBLS
  warn
    message = X-Warning: $sender_host_address is listed at $dnslist_domain ($dnslist_value: $dnslist_text)
    log_message = $sender_host_address is listed at $dnslist_domain ($dnslist_value: $dnslist_text)
  deny
    dnslists = CHECK_RCPT_IP_DNSBLS
    message = X-blacklisted-at: $sender_host_address is listed at $dnslist_domain ($dnslist_value: $dnslist_text)
    log_message = $sender_host_address is listed at $dnslist_domain ($dnslist_value: $dnslist_text)
  .endif

DATA checks, done on the SMTP "data", I.E. the email body itself

acl_check_data:

  # GREYLIST DATA section (part 2).  I found this to be more efficient
  # if this is in the DATA section rather than RCPT section, since the
  # HELO and other checks are more efficient at blocking spam than
  # the greylist is by itself.  This can be added properly by installing
  # the greylist using the command 'greylistd-setup-exim4 -acltype=data'.
  # Note: requires installing 'greylistd'
  #
  # greylistd(8) configuration follows.
  # This statement has been added by "greylistd-setup-exim4",
  # and can be removed by running "greylistd-setup-exim4 remove".
  # Any changes you make here will then be lost.
  # 
  # Perform greylisting on incoming messages with no envelope sender here.
  # We did not subject these to greylisting after RCPT TO:, because that
  # would interfere with remote hosts doing sender callout verifications.
  #
  # Because there is no sender address, we supply only two data items:
  #  - The remote host address
  #  - The recipient address (normally, bounces have only one recipient)
  #
  # We also check the local whitelist to avoid greylisting mail from
  # hosts that are expected to forward mail here (such as backup MX hosts,
  # list servers, etc).
  #
  defer
    message        = $sender_host_address is not yet authorized to deliver \
                     mail from <$sender_address> to <$recipients>. \
                     Please try later.
    log_message    = greylisted.
    senders        = :
    !hosts         = : +relay_from_hosts : \
                     ${if exists {/etc/greylistd/whitelist-hosts}\
                                 {/etc/greylistd/whitelist-hosts}{}} : \
                     ${if exists {/var/lib/greylistd/whitelist-hosts}\
                                 {/var/lib/greylistd/whitelist-hosts}{}}
    !authenticated = *
    !acl           = acl_whitelist_local_deny
    condition      = ${readsocket{/var/run/greylistd/socket}\
                                 {--grey \
                                  $sender_host_address \
                                  $recipients}\
                                  {5s}{}{false}}

  # ***
  #  Check for existance of certain headers in the message body and reject
  #  the mail if they aren't included [Date, Message-ID]
  # ***
  #
  # Temporarily not checking the existance of the Message-ID header, as
  # it seems older Outlook MUA's don't include it
  #deny
  #  condition   = ${if !def:h_Message-ID: {1}}
  #  !senders    = *@att.net
  #  message     = Message SHOULD have Message-ID: but does not -- rejected.
  #  log_message = failed Message-ID header check

  # Temporarily not checking the Date header, similar reasoning
  #deny
  #  condition   = ${if !def:h_Date: {1}}
  #  message     = Message SHOULD have Date: but does not -- rejected
  #  log_message = failed Date header check


  #
  #  AV_SCANNER PART 2
  #
  # Don't allow file extensions that are hidden to Outlook MUAs
  deny
    message = Hiding of file extensions is not allowed!
    log_message = Dangerous extension (CLSID hidden)
    regex = ^(?i)Content-Disposition::(.*?)filename=\\s*"+((\{[a-hA-H0-9-]{25,}\})|((.*?)\\s{10,}(.*?)))"+\$

  # ***
  #   Do checking of MIME attachments
  # ***
  # MIME checking for errors, deny message if attachment is badly malformed
  deny message         = This message contains malformed MIME ($demime_reason)
    demime          = *
    condition       = ${if >{2}{1}{0}}

  # Block file attachments that have certain extensions that are dangrous
  deny message = This message contains an attachment of a type we do not accept (.$found_extension)
    demime  = bat:com:exe:pif:prf:scr:vbs:wmf
  deny message = This message contains a virus or other harmful content ($malware_name).
    log_message = $sender_host_address tried sending $malware_name
    demime  = *
    malware = *

May 02, 2007, at 11:30 PM