Exim

From Gentoo Wiki
Jump to:navigation Jump to:search
Resources

Exim is a powerful and the most popular Mail Transfer Agent (MTA). It's reported to have a 57% market share[1]. Also, Exim is the default MTA for Debian / Ubuntu.

Pre-installation

As only one MTA can be installed at the same time on a system, it might be required to remove an installed MTA. The package manager will report a block when another MTA is still installed. This block can resolved by manually deselecting the old mail server. For example, preparing to remove mail-mta/ssmtp (which might have been installed as the default when a program requested a mail server to be installed) with this command:

root #emerge --deselect mail-mta/ssmtp

Installation

USE flags

USE flags for mail-mta/exim A highly configurable, drop-in replacement for sendmail

X Add support for X11
arc Adds support for Authenticated Receive Chain (ARC)
berkdb Add support for sys-libs/db (Berkeley DB for MySQL)
dane Adds support for DNS-based Authentication of Named Entities
dcc Adds support for Distributed Checksum Clearinghouse (DCC)
dkim Adds support for DomainKeys Identified Mail (DKIM)
dlfunc Install local_scan.h header to compile separate dlfunc libraries
dmarc Adds support for DMARC
dnsdb Adds support for a DNS search for a record whose domain name is the supplied query
doc Add extra documentation (API, Javadoc, etc). It is recommended to enable per package instead of globally
dovecot-sasl Adds support for Dovecot's authentication
dsn Adds support for Delivery Status Notifications (DSN)
gdbm Add support for sys-libs/gdbm (GNU database libraries)
gnutls Prefer net-libs/gnutls as SSL/TLS provider (ineffective with USE=-ssl)
idn Enable support for Internationalized Domain Names
ipv6 Add support for IP version 6
ldap Add LDAP support (Lightweight Directory Access Protocol)
lmtp Adds support for lmtp
maildir Add support for maildir (~/.maildir) style mail spools
mbx Adds support for UW's mbx format
mysql Add mySQL Database support
nis Support for NIS/YP services
pam Add support for PAM (Pluggable Authentication Modules) - DANGEROUS to arbitrarily flip
perl Add optional support/bindings for the Perl language
pkcs11 Require pkcs11 support in net-libs/gnutls with USE=gnutls
postgres Add support for the postgresql database
prdr Adds support for Per-Recipient Data Response
proxy Add support for being behind a proxy, such as HAProxy
radius Add support for RADIUS authentication
redis Adds support for querying dev-db/redis
sasl Add support for the Simple Authentication and Security Layer
selinux !!internal use only!! Security Enhanced Linux support, this must be set by the selinux profile or breakage will occur
socks5 Add support for the socks5 proxy
spf Adds support for Sender Policy Framework
sqlite Add support for sqlite - embedded sql database
srs Adds support for Sender Rewriting Scheme
ssl Add support for SSL/TLS connections (Secure Socket Layer / Transport Layer Security)
syslog Enable support for syslog
tcpd Add support for TCP wrappers
tdb Use sys-libs/tdb for internal database storage (such as hints database)
tpda Adds support for Transport Post-Delivery Actions

For a typical install, install mail-mta/exim with the following use flags:

FILE /etc/portage/package.use/exim
mail-mta/exim dkim exiscan-acl maildir prdr spf ssl syslog

To use dovecot as your POP/IMAP server and want to make exim use dovecot's authentication services, add a use flag for dovecot-sasl.

FILE /etc/portage/package.use/exim
mail-mta/exim dovecot-sasl dkim exiscan-acl maildir prdr spf ssl syslog

To use a database to manage what mailboxes and what aliases exist, add a use flag for the database to use, e.g. postgres, sqlite, etc.

FILE /etc/portage/package.use/exim
mail-mta/exim dkim exiscan-acl maildir prdr spf sqlite ssl syslog

Emerge

Finally, install exim with:

root #emerge --ask mail-mta/exim

Complete mailserver

For a complete mailserver, you'll typically also want to install a POP/IMAP server, such as dovecot or courier-imap. These so-called MDAs tightly integrate with Exim (your MTA) and possibly your database.

Also, you might want to install a webmail server, such as Squirrelmail or Roundcube, which will also integrate with your MTA, MDA and database.

Configuration

The exim ebuild creates a small number of configuration files in /etc/exim, namelyː auth_conf.sub exim.conf exim.conf.exiscan-acl and system_filter.exim. Of these files, the main configuration file is /etc/exim/exim.conf.

This exim.conf file can include several sectionsː

  • General options, setting up parameters such as hostname and domains, hooking up ACL's, SSL/TLS settings, ports and logging.
  • ACL, where specific rules can be written for different phases of the SMTP protocol for incoming e-mail.
  • Routers, which determine based on the addresses of the e-mail to which exim-transport the message should be routed
  • Transports, for actually delivering e-mails (either remotely, or locally)
  • Retry, because we might not want to give up after the first attempt
  • Rewrite, in case we want to touch content of the e-mail or its headers
  • Authenticators, as we only want to send e-mails out into the world if it was submitted by someone we trust
  • Local_scan, in case *really* specific scanning hook-ups are needed

Modern mail security (SPF/DKIM/DMARC)

With the rise of security in computing, mail security is also improving. After a number of solutions have been tried, the mailserver community seems to have settled on the combination of SPF, DKIM and DMARC.

SPF:

  • SPF focuses on Envelope-From (aka Return-Path, MAIL FROM, Bounce address)
  • SPF authenticates the IP of the sending server against SPF sending server policy
  • SPF does not require alignment between Header-From and Envelope-From
  • SPF does not survive forwarding and indirect mail-flows
  • SPF does not tell the receiving server what it should do with messages that fail SPF


DKIM:

  • DKIM focuses on asymmetrical crypto and control over DNS records
  • DKIM crypto-proves the d= domain signed the e-mail
  • DKIM crypto-proves the integrity of headers and/or body
  • DKIM does not require alignment between Header-From and d= domain
  • DKIM may survive forwarding and indirect mail-flows depending on what parts were signed
  • DKIM does not tell the receiving server what it should do with messages that fail DKIM


DMARC:

  • DMARC requires alignment between Header-From and Envelope-From used by SPF and/or d= domain field in DKIM signature
  • DMARC ignores the difference in SPF between hard fail (-all) and soft fail (~all) and treats both as fail
  • DMARC allows defining a policy that receivers can follow for messages that fail SPF and/or DKIM tests
  • DMARC allows reporting mailboxes to be defined

Example for SPF, DKIM with SQLite and Maildir

This configuration enables SPF and DKIM, but only adds warning headers, it doesn't quarantine or reject e-mails. Further it uses SQLite for managing what mailboxes and aliases exist, and stores mails in a special directory in Maildir format.

FILE /etc/exim/exim.conf
# Explicitly set our HELO name
primary_hostname = mailserver.example.com

# Define domain lists
domainlist local_domains = example.com : crossexample.net : @
hostlist   relay_from_hosts = 127.0.0.1 : localhost

# Connect access control lists to their hooks
acl_smtp_connect = acl_check_connect
acl_smtp_helo = acl_check_helo
acl_smtp_mail = acl_check_mail_from
acl_smtp_dkim = acl_check_dkim
acl_smtp_rcpt = acl_check_rcpt
acl_smtp_data = acl_check_data

# Allow any client to use TLS
tls_advertise_hosts = *

# Specify the location of the Exim server's TLS certificate and private key
tls_certificate = /etc/ssl/mailserver.example.com-signed.crt
tls_privatekey = /etc/ssl/private/mailserver.example.com.key

# Set modern ciphers (assuming gentoo default of exim with openssl)
tls_require_ciphers = TLS13-CHACHA20-POLY1305-SHA256:TLS13-AES-256-GCM-SHA384:TLS13-AES-128-GCM-SHA256:EECDH+CHACHA20:EECDH+AESGCM:EECDH+AES
tls_dhparam = /etc/ssl/private/dhparam.pem
openssl_options = +no_sslv2 +no_sslv3

# Support acting as smtp.example.com server for users outside our network
daemon_smtp_ports = 25 : 587
# tls_on_connect_ports = 465 (required for Microsoft clients)

# Add our domain to 'unqualified addresses', instead of our server name
qualify_domain = example.com

# Perform rDNS lookups for incoming connections, so we can confirm their HELO
host_lookup = *

# Advertise PRDR to accept/reject multi-recipient mails per-user
prdr_enable = true

# Specify what should be logged.
log_selector = +smtp_protocol_error +smtp_syntax_error \
        +tls_certificate_verified

# Prevent clogging the queue when mail delivery and return-to-sender both fail
ignore_bounce_errors_after = 2d
timeout_frozen_after = 7d

# If we don't set the following option, Exim will complain mildly
keep_environment =



###################
# ACL CONFIGURATION
###################
begin acl

# Work through the SMTP protocol

# Trigger on an incoming connection
acl_check_connect:

  # Record the current timestamp, in order to delay crappy senders
  warn
          set acl_m0  = $tod_epoch

  # Warn if reverse DNS lookup failed. Note: later we compare rDNS with HELO
  warn
          !verify     = reverse_host_lookup
          set acl_c1  = X-DNS-Warning: Couldn't lookup reverse DNS for ${sender_host_address}

  # Set up for finalisation: write to log
  warn
          condition   = ${if def:acl_c1 {true}{false} }
          logwrite    = $acl_c1

  # On error: accept but stall (good actors tend to be patient)
  accept
          set acl_m0  = ${if def:acl_c1 {${eval:20 + $acl_m0 - $tod_epoch} }{0} }
          delay       = ${if >{$acl_m0}{0}{$acl_m0}{0} }s

# Trigger on the HELO/EHLO of incoming connections.
acl_check_helo:

  # Record the current timestamp, in order to delay crappy senders
  warn
          set acl_m0  = $tod_epoch

  # Warn if sender did not provide a HELO/EHLO greeting
  warn
          condition   = ${if !def:sender_helo_name {true}{false} }
          set acl_c2  = X-HELO-Warning: Remote host $sender_host_address \
                        did not present HELO/EHLO greeting.

  # Warn if sender greeted with an IP address (technically correct, but crappy)
  warn
          condition   = ${if !def:acl_c2 {true}{false} }
          condition   = ${if isip {$sender_helo_name}{true}{false} }
          set acl_c2  = X-HELO-Warning: Remote host $sender_host_address \
                        used IP address instead of hostname in HELO/EHLO

  # Warn if sender pretended to be us (they may think that would trick us)
  warn
          condition   = ${if !def:acl_c2 {true}{false} }
          condition   = ${if match_domain{$sender_helo_name}\
                        {$primary_hostname:+local_domains}{true}{false} }
          set acl_c2  = X-HELO-Warning: Remote host $sender_host_address \
                        used our name in HELO/EHLO but it is not defined in \
                        our +local_domains

  # Warn if HELO verification fails, e.g. rDNS of IP != HELO domain
  warn
          condition   = ${if !def:acl_c2 {true}{false} }
          !verify     = helo
          set acl_c2  = X-HELO-Warning: Remote host ${if def:sender_host_name \
                            {$sender_host_name ($sender_host_address) }\
                            {$sender_host_address } }\
                        incorrectly presented itself as $sender_helo_name

  # Set up for finalisation: write to log
  warn
          condition   = ${if def:acl_c2 {true}{false} }
          logwrite    = $acl_c2

  # On error: accept but stall (good actors tend to be patient)
  accept
          set acl_m0  = ${if def:acl_c2 {${eval:20 + $acl_m0 - $tod_epoch} }{0} }
          delay       = ${if >{$acl_m0}{0}{$acl_m0}{0} }s

# Trigger on MAIL FROM: command. Here we perform SPF checks.
acl_check_mail_from:

  # Skip SPF checks for all authenticated connections (probably MUAs)
  accept
          authenticated = *

  # Record the current timestamp, in order to delay crappy senders
  warn
          set acl_m0  = $tod_epoch

  # Query if we can find an SPF policy for the sending domain
  warn
          spf         = none:temperror:permerror
          set acl_c3  = X-SPF-Warning: can't find SPF policy for \
                        $sender_address_domain

  # Query if the sending domain has set a useful SPF policy
  warn
          condition   = ${if !def:acl_c3 {true}{false} }
          condition   = ${if match {${lookup dnsdb{txt=$sender_address_domain}{$value} } }{\\+all} }
          set acl_c3  = X-SPF-Warning: $sender_address_domain allows +all in SPF policy

  # Query the SPF policy whether sender was authorized to deliver this message
  # Note: fail = '-all', softfail = '~all', neutral = '?all'
  warn
          spf         = fail:softfail:neutral
          condition   = ${if !def:acl_c3 {true}{false} }
          set acl_c3  = X-SPF-Warning: $sender_host_address is not allowed to \
                        send mail for $sender_address_domain

  # Add a SPF-Received: line to the message header (regardless of SPF status)
  warn
          add_header  = $spf_received

  # Set up for finalisation: add all headers and write to log
  warn
          condition   = ${if def:acl_c3 {true}{false} }
          logwrite    = $acl_c3
          add_header  = $acl_c1
          add_header  = $acl_c2
          add_header  = $acl_c3

  # On error: accept but stall (good actors tend to be patient)
  accept
          set acl_m0  = ${if or { {def:acl_c1}{def:acl_c2}{def:acl_c3} } {${eval:20 + $acl_m0 - $tod_epoch} }{0} }
          delay       = ${if >{$acl_m0}{0}{$acl_m0}{0} }s


# This access control list is used to process DKIM status.
acl_check_dkim:

  # Skip DKIM checks for all authenticated connections (probably MUAs)
  accept
          authenticated = *

  # Record the current timestamp, in order to delay crappy senders
  warn
          set acl_m0  = $tod_epoch

  # Warn no DKIM
  warn
          dkim_status = none
          set acl_c4  = X-DKIM-Warning: No signature found

  # RFC 8301 requires 'permanently failed evaluation' for DKIM signatures signed with 'historic algorithms (currently, rsa-sha1)'
  # @SEE: https://www.exim.org/exim-html-current/doc/html/spec_html/ch-dkim_and_spf.html
  warn
          condition              = ${if !def:acl_c4 {true}{false} }
          condition              = ${if eq {$dkim_verify_status}{pass} }
          condition              = ${if eq {${length_3:$dkim_algo} }{rsa} }
          condition              = ${if or { {eq {$dkim_algo}{rsa-sha1} } \
                                    {< {$dkim_key_length}{1024} } } }
          set acl_c4             = X-DKIM-Warning: forced DKIM failure (weak hash or short key)
          set dkim_verify_status = fail
          set dkim_verify_reason = hash too weak or key too short

  # RFC6376 requires that verification fail if the From: header is not included in the signature
  # @SEE: https://www.exim.org/exim-html-current/doc/html/spec_html/ch-dkim_and_spf.html
  warn
          condition   = ${if !def:acl_c4 {true}{false} }
          condition   = ${if !inlisti{from}{$dkim_headernames}{true}{false} }
          set acl_c4  = X-DKIM-Warning: From: header not included in the \
                        signature, this defies the purpose of DKIM

  # Warn invalid or failed signatures
  warn
          condition   = ${if !def:acl_c4 {true}{false} }
          dkim_status = fail:invalid
          set acl_c4  = X-DKIM-Warning: verifying signature of $dkim_cur_signer \
                        failed for $sender_address because $dkim_verify_reason

  # Add a DKIM-Received: line to the message header (regardless of DKIM status)
  warn
          add_header  = Received-DKIM: $dkim_verify_status ${if \
                        def:dkim_cur_signer {($dkim_cur_signer with \
                        $dkim_algo for $dkim_headernames)} }

  # Set up for finalisation: add header and write to log
  warn
          condition   = ${if def:acl_c4 {true}{false} }
          add_header  = $acl_c4
          logwrite    = $acl_c4

  # On error: accept but stall (good actors tend to be patient)
  accept
          set acl_m0  = ${if def:acl_c4 {${eval:20 + $acl_m0 - $tod_epoch} }{0} }
          delay       = ${if >{$acl_m0}{0}{$acl_m0}{0} }s


# Trigger on RCPT of incoming message.
acl_check_rcpt:

  # Test empty sending host field to accept local SMTP (i.e. not over network)
  accept
          hosts         = :
          control       = dkim_disable_verify

  # Strange non-alphanumeric characters may indicate a user trying to trick us;
  # 1/2: incoming mail address may not begin with . or contain @ % ! / or |
  deny
          message	= Restricted characters in address
          domains	= +local_domains
          local_parts   = ^[.] : ^.*[@%!/|]

  # 2/2: outgoing mail address may not begin with . / or | and neither may it
  #      contain @ % ! or the sequence /../ (this is slightly looser than 1/2)
  deny
          message	= Restricted characters in address
          domains	= !+local_domains
          local_parts   = ^[./|] : ^.*[@%!] : ^.*/\\.\\./

  # Deny unless we have a route to deliver mail back to the sender address
  require
          verify        = sender

  # Skip other checks for all authenticated connections (probably MUAs), set
  # submission mode and assume MUAs send address in From: that includes @domain
  accept
          authenticated = *
          control	= submission/sender_retain
          control	= dkim_disable_verify
          add_header    = X-Authenticated-Sender: ${sender_address}
          set acl_c_authenticated = 1

  # Insist that a HELO/EHLO was accepted.
  require
          message	= nice hosts say HELO first
          condition     = ${if def:sender_helo_name}

  # Insist that any other recipient address is in one of our local domains
  require
          message       = relay not permitted
          domains       = +local_domains

  # Ensure we can deliver the message before we accept it
  require
          verify        = recipient

  # If it got through to this point, accept it without further checks
  accept


# Message has been received, now we can check its contents (headers, body, etc)
# Note: here we can invoke external virus or spam scanners, if available
acl_check_data:

  # Deny if the message contains an overlong line, as per the standards
  deny
          message       = maximum allowed line length is 998 octets, \
                          got $max_received_linelength
          condition     = ${if > {$max_received_linelength}{998} }

  # Deny if the headers contain badly-formed addresses.
  deny
          !verify       = header_syntax
          message       = header syntax
          logwrite      = header syntax ($acl_verify_message)

  # Deny if the message contains a virus. Before enabling this check, you
  # must install a virus scanner and set the av_scanner option above.
  # deny
  #         malware     = *
  #         message     = This message contains a virus ($malware_name).

  # Add headers to a message if it is judged to be spam. Before enabling this,
  # you must install SpamAssassin. You may also need to set the spamd_address
  # option above.
  # warn
  #         spam        = nobody
  #         add_header  = X-Spam_score: $spam_score\n\
  #                       X-Spam_score_int: $spam_score_int\n\
  #                       X-Spam_bar: $spam_bar\n\
  #                       X-Spam_report: $spam_report

  # Accept the message.
  accept
            logwrite    = Received message from $sender_address_domain



#########
# ROUTERS
#########

begin routers

# ---
# Routers that handle addresses in remote domains (! +local_domains).
# ---

# Route addresses by doing a DNS lookup on the domain name.
# Domains that resolve to 0.0.0.0 or to loopback (127.0.0.0/8) are treated as 
# if they have no DNS entry: lookup fails, 'no_more' intervenes and the 
# address is handled as unrouteable.
dnslookup:
  driver = dnslookup
  driver = dnslookup
  domains = ! +local_domains
  transport = remote_smtp
  ignore_target_hosts = 0.0.0.0 : 127.0.0.0/8
  no_more

# ---
# Routers that handle addresses in local domains ("domainlist local_domains").
# ---
# If none of these routers picks up the message, "Unknown user" is returned to
# sender.

# This section is heavily dependent on your setup, especially how you wish to
# manage the 'local_parts', i.e. what aliases exist, what mailboxes exist and
# where mailboxes are stored.
# Traditionally answers to these questions were files that have existed since
# dawn of Unix tradition, but nowadays it's typical to put this in a database.
# If you already administer one or more databases, you can use it here as well.
# But if you don't, you can consider sqlite or flat files, so you don't need to
# learn about database-upgrades, etc. This example uses an sqlite database.

# If recepient is an alias, redirect it to the appropriate mailbox
aliases:
  debug_print = "Redir: virtual_aliases for ${local_part}@${domain}"
  driver = redirect
  domains = +local_domains
  local_part_suffix = -*
  local_part_suffix_optional
# Change the following two lines to reflect your database setup
#  condition = ${if eq{${local_part} }{${lookup sqlite {/path/to/db.sqlite SELECT alias FROM aliases WHERE domain='${quote_sqlite:$domain}' AND alias='${quote_sqlite:$local_part}'; }  }
#  data = /path/to/mailboxes/${domain}/${lookup sqlite {/path/to/db.sqlite SELECT userid FROM aliases WHERE domain='${quote_sqlite:$domain}' AND alias='${quote_sqlite:$local_part}'; } }
  file_transport = redir_virtual_directory
  directory_transport = redir_virtual_directory

# If recepient is a mailbox on its own, deliver the message.
# This router treats local parts with suffixes introduced by "-" as if the 
# suffixes did not exist. E.g. xxxx-foo@your.domain will be treated
# in the same way as xxxx@your.domain by this router.
users:
  debug_print = "Acc: userid for ${local_part}@${domain}"
  driver = accept
  no_check_local_user
  domains = +local_domains
  local_part_suffix = -*
  local_part_suffix_optional
# Change the following line to reflect your database setup
#  condition = ${if eq{${local_part} }{${lookup sqlite {/path/to/db.sqlite SELECT userid FROM users WHERE domain='${quote_sqlite:$domain}' AND userid='${quote_sqlite:$local_part}'; }  }
  transport = accept_virtual_directory
  cannot_route_message = Unknown user



############
# TRANSPORTS
############
# Transports can only be called by a router picking up the message

begin transports

# ---
# Transport for delivering over SMTP (e.g. outgoing e-mail)
# ---

# Refuse messages with over-long lines as per standard
# Note: the use of message_size_limit is a red herring.
remote_smtp:
  driver = smtp
  message_size_limit = ${if > {$max_received_linelength}{998} {1}{0} }
  dkim_domain = ${sender_address_domain}
  dkim_selector = dkim
  dkim_private_key = /etc/ssl/private/dkim-mailserver.example.com.key
  dkim_canon=relaxed

# ---
# Transport for delivering to local mailbox
# ---

# This transport is used for local delivery to user mailboxes w/ Maildir
accept_virtual_directory:
  driver = appendfile
  maildir_format
  user = vmail
  group = vmail
  mode = 0600
  directory = /path/to/mailboxes/${domain}/${local_part}/
  delivery_date_add

redir_virtual_directory:
  driver = appendfile
  maildir_format
  user = vmail
  group = vmail
  mode = 0600



#######
# RETRY
#######

begin retry

# Because we're not going to give up after our first attempt
# Address or Domain    Error       Retries
  *                    *           F,2h,15m; G,16h,1h,1.5; F,4d,6h



################
# AUTHENTICATION
################

begin authenticators

# Authentication can be finicky to set up, but will also be needed by the
# POP/IMAP server you will probably also be setting up. This example makes use
# of dovecot to handle authentication for our SMTP clients.
dovecot_login:
  driver = dovecot
  public_name = LOGIN
  server_socket = /var/run/dovecot/auth-client
  server_set_id = $auth1

dovecot_plain:
  driver = dovecot
  public_name = PLAIN
  server_socket = /var/run/dovecot/auth-client
  server_set_id = $auth1

Example for SpamAssasin, ClamAV with PostgreSQL and Maildir

This possibly outdated configuration uses SpamAssasin to filter spam, ClamAV to detect if e-mails contain virusses, PostgreSQL for managing what mailboxes exist, and stores mails in users' home directories in Maildir format.

FILE /etc/exim/exim.conf
# Domain list configuration, each domain must be separated by ":"
domainlist local_domains = example.com:otherdomain.com:anotherdomain.com:@

# On initial configuration we do not want to relay any other domain
domainlist relay_to_domains =
hostlist   relay_from_hosts = 127.0.0.1 : ::::1/128

# ACL we want to be checked
local_from_check = true

acl_smtp_mail = acl_check_mail
acl_smtp_rcpt = acl_check_rcpt
acl_smtp_connect = acl_check_host
acl_smtp_data = acl_check_data
acl_smtp_helo = acl_check_helo

# Postgresql Database connection
hide pgsql_servers = localhost/db_maik/dbusername/dbpassword:

# Set maximum parallel remote smtp
remote_max_parallel = 24

# Define clamav configuration
av_scanner = clamd:127.0.0.1 3310

# Spam configuration
spamd_address = 127.0.0.1 783

# Allow use of TLS, we can have files here.
tls_advertise_hosts = *

# Set the key and cert to use
tls_certificate = /etc/ssl/mail/mycert.pem
tls_privatekey = /etc/ssl/mail/myprivate.key

# Our default qualify domain
qualify_domain = example.com

begin acl
# Force authentication for send email
acl_check_mail:
  warn  set acl_c_auth_deny = no
        set acl_c_deny_msg = Checking User

  accept sender_domains = !+local_domains: 

  deny  sender_domains = +local_domains
        !authenticated = *
        set acl_c_auth_deny = yes
        set acl_c_deny_msg  = Authentication Needed for Send Mail
        message = Authentication Needed for Send Mail

  accept
acl_check_rcpt:
  deny  condition = $acl_c_auth_deny
        message = $acl_c_deny_msg

  deny  hosts = :

  deny    message       = Restricted characters in address
          domains       = +local_domains
          local_parts   = ^[.] : ^.*[@%!/\|]

  deny    message       = Restricted characters in address
          domains       = !+local_domains
          local_parts   = ^[./|] : ^.*[@%!] : ^.*/\\.\\./

  accept  local_parts   = postmaster
          domains       = +local_domains

  require verify        = sender

  accept  authenticated = *
          control       = submission/sender_retain

  require message = relay not permitted
          domains = +local_domains : +relay_to_domains

  require verify = recipient

  accept

# Verify the host against black lists
acl_check_host:
    deny hosts    = !+relay_from_hosts
         message  = Host is listed in $dnslist_domain.
         dnslists = \
         cbl.abuseat.org : \
         virbl.dnsbl.bit.nl : \
         bl.spamcop.net : \
         sbl.spamhaus.org : \
         xbl.spamhaus.org

    accept

# Check that the hello does not pretend to come from our servers
acl_check_helo:
   accept hosts =  +relay_from_hosts

   deny condition = ${if or { \
                         {eq {${lc:$sender_helo_name} }{example.com} } \
                         {eq {${lc:$sender_helo_name} }{10.100.0.100} } \
                         {eq {${lc:$sender_helo_name} }{127.0.0.1} } \
                         {eq {${lc:$sender_helo_name} }{localhost} } \
                        } {true}{false} }

  accept
# ACL fot data
acl_check_data:
  deny    condition  = ${if > {$max_received_linelength}{998} }

   deny    malware    = *
           message    = This message contains a virus ($malware_name).
   warn    spam       = nobody
           add_header = X-Spam_score: $spam_score\n\
                        X-Spam_score_int: $spam_score_int\n\
                        X-Spam_bar: $spam_bar\n\
                        X-Spam_report: $spam_report

   deny    message    = Mensaje clasificado como SPAM
           spam       = nobody:true
           condition  = ${if >{$spam_score_int}{60}{1}{0} }

begin routers
dnslookup:
  driver = dnslookup
  domains = ! +local_domains
  transport = remote_smtp
  ignore_target_hosts = 0.0.0.0 : 127.0.0.0/8 : ::::1/128
  no_more

# Virtual aliases
virtual_aliases:
  driver = redirect
  allow_fail
  allow_defer
  data = ${lookup{$local_part}lsearch{/etc/mail/valiases/$domain} }
  file_transport = address_file
  pipe_transport = address_pipe

system_aliases:
  driver = redirect
  allow_fail
  allow_defer
  data = ${lookup{$local_part}lsearch{/etc/mail/aliases} }
# user = exim
  file_transport = address_file
  pipe_transport = address_pipe

userforward:
  driver = redirect
  check_local_user
# local_part_suffix = +* : -*
# local_part_suffix_optional
  file = $home/.forward
# allow_filter
  no_verify
  no_expn
  check_ancestor
  file_transport = address_file
  pipe_transport = address_pipe
  reply_transport = address_reply

localuser:
  driver = accept
  check_local_user
# local_part_suffix = +* : -*
# local_part_suffix_optional
  # Without dovecot
  #transport = local_delivery
  # With dovecot
  transport = dovecot_virtual_delivery
  cannot_route_message = Unknown user

user_vacation:
  driver = accept
  check_local_user
  # do not reply to errors or lists
  condition = "${if or { {match {$h_precedence:} {(?i)junk|julk|list} } {eq {$sender_address} {} } } {no} {yes} }"
  no_expn
  require_files = ${lookup pgsql{select concat(user_home,'/.vacation.msg') from mail_users where user_uid = '${local_part}' or    user_uid = '${local_part}@${domain}'}{$value}fail}
  # do not reply to errors and bounces or lists
  senders = " ! ^.*-request@.*:\
              ! ^owner-.*@.*:\
              ! ^postmaster@.*:\
              ! ^listmaster@.*:\
              ! ^mailer-daemon@.*\
              ! ^root@.*"
  transport = vacation_reply
  unseen
  user = ${local_part}
  no_verify  

begin transports

remote_smtp:
  driver = smtp
  delivery_date_add
  dkim_domain = $sender_address_domain
  dkim_selector = thisis
  dkim_private_key = /etc/ssl/exim/dkim.private.key
  dkim_canon = relaxed

local_delivery:
  driver = appendfile
  directory = ${lookup pgsql{select concat(user_home,'/.maildir') from mail_users where user_uid = '${local_part}' or user_uid = '${local_part}@${domain}'}{$value}fail}
  maildir_format
  delivery_date_add
  envelope_to_add
  return_path_add

dovecot_virtual_delivery:
        driver = pipe
        command = /usr/libexec/dovecot/dovecot-lda -d $local_part@$domain  -f $sender_address
        # v1.1+: command = /usr/local/libexec/dovecot/dovecot-lda -d $local_part@$domain  -f $sender_address -a    $original_local_part@$original_domain
        message_prefix =
        message_suffix =
        delivery_date_add
        envelope_to_add
        return_path_add
        log_output
        temp_errors = 64 : 69 : 70: 71 : 72 : 73 : 74 : 75 : 78
address_pipe:
  driver = pipe
  return_output


address_file:
  driver = appendfile
  delivery_date_add
  envelope_to_add
  return_path_add

address_reply:
  driver = autoreply

vacation_reply:
  driver = autoreply

begin retry
# Address or Domain    Error       Retries
# -----------------    -----       -------

*                      *           F,2h,15m; G,16h,1h,1.5; F,4d,6h

begin rewrite

# This rule allow to have more than one user with same email
*@* "${lookup pgsql{select user_alias||'@'||user_qualify_domain from mail_alias where user_uid = '$1@$2'}{$value}fail}" Ffrw

begin authenticators
AUTH_CRAM_MD5=yes
AUTH_SPA=yes

CRAM:
  driver = cram_md5
  server_set_id = $auth1
  public_name = CRAM-MD5
  server_secret = ${lookup pgsql{select user_pass from mail_users where user_uid = '$auth1' or user_uid = '$auth1$auth3'}{$value}fail}
  #server_secret = ${if eq{$auth1}{ph10}{secret1}fail}
SPA:
  driver = spa
  server_set_id = $auth1
  public_name = NTLM
  server_password = ${lookup pgsql{select user_pass from mail_users where user_uid = '$auth1' or user_uid = '$auth1$auth3'}{$value}fail}

Testing Exim

Once the configuration file is ready we can test the the file with the following command:

root #exim -bV

If everything goes right we can now start exim

root #/etc/init.d/exim start

Now we must be able to test how exim will route some addresses

root #exim -bt someuser
root #exim -bt bunnyfoofoo@gmail.com
root #exim -bt someuser@example.com

Finally we can do the bunny test

root #echo "test" | sendmail bunnyfoofoo@gmail.com

References

  1. Mail (MX) Server Survey, as stated in part of the Internet Research Reports by Canadian consulting firm E-Soft Inc., retrieved March 22, 2019