EximConfig

installation

just

aptitude install exim4 exim4-daemon-heavy clamav clamav-daemon spamassassin spamc

the usage of maildir should be answered in

dpkg-reconfigure exim4-config

with yes. also select the multiple file configuration layout. anyhow, a more detailed listing which answers you should give.

virus scanning with clamav

enable clamd.

systemctl enable clamav-daemon

add following to your /etc/exim4/conf.d/main/00_local-config_macros.

# enable clamav for scanning mails
av_scanner = clamd:/var/run/clamav/clamd.ctl

# local ACLs for virus scanning (not only)
CHECK_DATA_LOCAL_ACL_FILE=/etc/exim4/local_acl_data

add following to your /etc/exim4/local_acl_data.

# Reject messages containing malware.
  deny
    message = This message was detected as possible malware ($malware_name).
    malware = *

before restarting clamav, we need to be sure that all of the access rights are in place so that the scans actually happen. the best way to handle this is to add the clamav user to the Debian-exim group. Either manually edit /etc/group, or simple run:

adduser clamav Debian-exim

a restart of clamav is necessary for the changes to take effect:

service clamav-daemon restart

for testing just

aptitude install clamav-testfiles swaks

the testfiles can be found in /usr/share/clamav-testfiles. test it by sending a mail with attachment to local user. don't use mutt for testing.

swaks --from root@<FQHN> --to tux@<FQHN> --server localhost --attach /usr/share/clamav-testfiles/clam.7z 
[...]
<** 550 This message contains a virus (Clamav.Test.File-6) and has been rejected

you will find following output in /var/log/exim4/mainlog

2018-02-09 15:06:54 1ek9KI-00014U-NX H=localhost (arverner.smtp.at) [::1] F=<root@arverner.smtp.at> rejected after DATA: This message contains a virus (Clamav.Test.File-6) and has been rejected

spam-tagging w/ spamassassin

we'll do it like on Adding SpamAssassin and ExiscanExamples - Exim Wiki.

Exiscan-ACL's "spam" condition passes the message through SpamAssassin, and triggers if these indicate that the message is junk. By default, it connects to a SpamAssassin daemon (spamd) running on localhost. The host address and port can be changed by adding a spamd_address setting in /etc/exim4/conf.d/main/00_local-config_macros.

# enable spamassassin
spamd_address = 127.0.0.1 783

enable spamd.

systemctl enable spamassassin

In our implementation, we are going to reject messages classified as spam. However, we would like to keep a copy of such messages in a separate mail folder, at least for the time being. This is so that the user can periodically scan for False Positives.

Exim offers controls that can be applied to a message that is accepted, such as freeze. The Exiscan-ACL patch adds one more of these controls, namely fakereject. This causes the following SMTP response:

550-FAKEREJECT id=message-id
550-Your message has been rejected but is being kept for evaluation.
550 If it was a legit message, it may still be delivered to the target recipient(s).

the headers we'd like to have look like following (standard spamassassin headers):

X-Spam-Flag: YES
X-Spam-Checker-Version: SpamAssassin 3.2.5 (2008-06-10) on bogdan.kmp.or.at
X-Spam-Level: ******
X-Spam-Status: Yes, score=6.5 required=5.0 tests=BAYES_99,HTML_MESSAGE,
        MIME_HTML_ONLY autolearn=no version=3.2.5

We can incorporate this feature into our implementation, by inserting the following snippet in /etc/exim4/local_acl_data.

# Reject messages that are spam.
  # Invoke SpamAssassin to obtain $spam_score and $spam_report.
  # Depending on the classification, $acl_m9 is set to "ham" or "spam".
  # If the message is classified as spam, pretend to reject it.
  warn
    condition = ${if <{$message_size}{4096k}{1}{0}}
    set acl_m9 = ham
    spam = mail/defer_ok
    set acl_m9 = spam
    control = fakereject
    logwrite = Rejected spam: $spam_report
    add_header = X-ieBoh2ah-Spam-Flag: YES

  # Add an appropriate X-Spam-Status: header to the message.
  warn
    condition = ${if <{$message_size}{4096k}{1}{0}}
    add_header = X-ieBoh2ah-Spam-Level: $spam_bar\n\
              X-ieBoh2ah-Spam-Status: ${if eq {$acl_m9}{spam}{Yes}{No}}, $spam_report

  # Add an appropriate X-Spam-Scanned: header if message is too big.
  warn
    condition = ${if >={$message_size}{4096k}{1}{0}}
    add_header = X-ieBoh2ah-Spam-Scanned: No, Message bigger than 4096 KiB

In this example, $acl_m9 is initially set to "ham". Then SpamAssassin is invoked as the user mail. If the message is classified as spam, then $acl_m9 is set to "spam", and the FAKEREJECT response above is issued. Finally, some X-Spam-*: headers are added to the message. The idea is that the Mail Delivery Agent or the recipient's Mail User Agent can use this header to filter junk mail into a separate folder.

to ensure our X-Spam headers are unique add following snippet to /etc/exim4/system.filter.

if first_delivery then
  if $h_X-ieBoh2ah-Spam-Status is not "" or $h_X-ieBoh2ah-Spam-Scanned is not "" then
    headers remove X-Spam-Flag:X-Spam-Level:X-Spam-Status:X-Spam-Scanned
  endif

  if $h_X-ieBoh2ah-Spam-Flag is not "" then
    headers add "X-Spam-Flag: $h_X-ieBoh2ah-Spam-Flag"
  endif

  if $h_X-ieBoh2ah-Spam-Level is not "" then
    headers add "X-Spam-Level: $h_X-ieBoh2ah-Spam-Level"
  endif

  if $h_X-ieBoh2ah-Spam-Status is not "" then
    headers add "X-Spam-Status: $h_X-ieBoh2ah-Spam-Status"
  endif

  if $h_X-ieBoh2ah-Spam-Scanned is not "" then
    headers add "X-Spam-Scanned: $h_X-ieBoh2ah-Spam-Scanned"
  endif

  headers remove X-ieBoh2ah-Spam-Flag:X-ieBoh2ah-Spam-Level:X-ieBoh2ah-Spam-Status:X-ieBoh2ah-Spam-Scanned
endif

enable this system-filter by setting in /etc/exim/conf.d/main/00_local-config_macros

# The name of the file that contains the system filter must be specified
# by setting system_filter. If you want the filter to run under a uid
# and gid other than root, you must also set system_filter_user and
# system_filter_group as appropriate.
system_filter = /etc/exim4/system.filter

By default, SpamAssassin presents its report in a verbose, table-like format, mainly suitable for inclusion in or attachment to the message body. In our case, we want a terse report, suitable for the X-Spam-Status: header in the example above. To do this, we add the following snippet in its site specific configuration file /etc/spamassassin/local.cf.

# report template
clear_report_template
report score=_SCORE_ required=_REQD_ tests=_TESTS_ autolearn=_AUTOLEARN_ version=_VERSION_ hostname=<FQHN>

For these changes to take effect, you have to restart the SpamAssassin daemon.

service spamassassin restart

for testing spamassassin just have a look on SpamAssassin: The GTUBE.

echo "XJS*C4JDBQADN1.NSBN3*2IDNEN*GTUBE-STANDARD-ANTI-UBE-TEST-EMAIL*C.34X" | swaks --from root@<FQHN> --to tux@<FQHN> --server localhost --body -

later, when we switch to dovecots LDA we also can sort into Spam folder based on these X-Spam headers.

Multiple Domain Alias Files

The steps below are used to enable support for having multiple virtual domains each with its own alias file.

Exim will need to have the alias files for each domain.

Create the /etc/exim4/virtual-domains.d directory.

For each virtual domain, create a file that contains the aliases to be used named as the domain.

For example, if example.com was one of my domains, I'd do the following:

Create the /etc/exim4/virtual-domains.d/example.com file.

If my system users were sys1, sys2, and sys3, and their email addresses were to be joe, john, jason, I'd put the following into the domain alias file:

joe:    sys1@localhost
john:   sys2@localhost
jason:  sys3@localhost

If john was also to get all mail addressed to info@example.com, you would add this entry:

info:   sys2@localhost

If you wanted all mail to user1@example.com to go to another email account outside of this domain, you would enter:

user1:  a.user@some.domain

If you wanted all mail directed at any address other than what is defined in the alias file to go to joe, you'd enter:

*:      sys1@localhost

In the above examples, the "@localhost" suffix to the user names forces the delivery to a system user. I found that if you do not include this in the alias files and your machine's host name is within one of the domains handled by exim, every system user would need an entry in the machine's domain in order to be delivered corectly. For instance, if your host name was mail.example1.com and example1.com was handled by this server this would be needed. This would allow delivery to all the system user names at example1.com. The reason is simple, and I will try to illustrate it for you here:

  1. exim receives a message delivered to joe.blow@example3.com

  2. The alias file for this domain has joe.blow: jblow in it.
  3. This would translate to jblow@domain-of-the-system
  4. The process would be repeated using jblow@domain-of-the-system
  5. If there was no entry in the domain-of-the-system alias file for jblow, the message would be undeliverable (or non-routable)

You could even have special redirects like the following:

script: "| /path/to/some/script"
prev:   :fail: $local_part left!
kill:   :blackhole:

or you even can have regular expressions:

^klaus[-.](m|maria)\.pfei[f]{1,2}er$    klaus
^(kmp|klaus)[+-.][a-zA-Z0-9+-.]*$       klaus
^(kmp|klaus)$                           klaus

Edit /etc/exim4/conf.d/main/00_local-config_macros by adding the following lines:

# List of domains considered local for exim. Domains not listed here
# need to be deliverable remotely.
MAIN_LOCAL_DOMAINS = @:localhost:dsearch;/etc/exim4/virtual-domains.d

Create /etc/exim4/conf.d/router/370_local-config_virtual_domains_aliases with the following content:

virtual_domain_aliases:
  debug_print = "R: virtual_domain_aliases for $domain"
  driver = redirect
  allow_defer
  allow_fail
  domains = dsearch;/etc/exim4/virtual-domains.d
  data = ${lookup{$local_part}nwildlsearch{/etc/exim4/virtual-domains.d/$domain}}
  retry_use_local_part
  pipe_transport = address_pipe
  file_transport = address_file

Now, regenerate your exim4 config:

update-exim4.conf

If there were no errors, restart exim4:

service exim4 restart

increasing logging

just add to your /etc/exim4/conf.d/main/00_local-macros:

# always log all
MAIN_LOG_SELECTOR = +all -pid

no rewrite on /etc/email-addresses

just imagine, you've two distinguishable users with folowing adresses:

john@example1.com
john@example2.com

first john has login john, second john has login millerj on your machine. so, your /etc/email-addresses looks like:

john:    john@example1.com
millerj: john@example2.com

due to a rewriteing in /etc/exim4/conf.d/rewrite/31_exim4-config_rewriting all emails from john@example2.com will appear as from john@example1.com.

just disabling this by adding the follwoing to your /etc/exim4/conf.d/main/00_local-macros:

# don't rewrite from on base of /etc/email-addresses
NO_EAA_REWRITE_REWRITE=1

enabling TLS

do it like as described in 2.2.2. Enabling TLS support for Exim as server in /usr/share/doc/exim4-base/README.Debian.gz. also have a look at Exim Notes - Waikato Linux Users Group.

you need only two steps to go with TLS. first generate your self signed certificates:

/usr/share/doc/exim4-base/examples/exim-gencert

second add follwoing to your /etc/exim4/conf.d/main/00_local-macros:

# enable TLS
MAIN_TLS_ENABLE=1

if you'd like to or have to enable smtps/ssmtp, just add the follwoing to your /etc/exim4/conf.d/main/00_local-config_macros:

# listen on 25 (smtp), 465 (smtps/ssmtp), 587 (submission)
daemon_smtp_port = 25:465:587
tls_on_connect_ports = 465

enable submission ... /etc/exim4/conf.d/main/00_local-config_macros

# hook in my own RCPT ACLs
CHECK_RCPT_LOCAL_ACL_FILE=/etc/exim4/local_acl_rcpt

/etc/exim4/local_acl_rcpt

# submission on port 587 only authenticated
  # rejected non authenticated on port 587
  accept
    condition = ${if eq{$interface_port}{587}{1}{0}}
    endpass
    message = SMTP AUTH required for submission on port 587
    authenticated = *

disable SSLv3 in /etc/exim4/conf.d/main/00_local-config_macros and add same snippet at end of /etc/exim4/conf.d/transport/30_exim4-config_remote_smtp and 30_exim4-config_remote_smtp_smarthost

# disable SSLv3 on incoming connections according to
# https://lists.exim.org/lurker/message/20141017.093614.e5c38176.en.html
#tls_require_ciphers = NORMAL:!VERS-SSL3.0
tls_require_ciphers = NORMAL:!ARCFOUR-128:!VERS-SSL3.0

letsencrypt

install deb pkg dehydrated and dehydrated-apache2.

run dehydrated as unprivileged user.

adduser --system --home /var/lib/dehydrated --no-create-home --group --disabled-login letsencrypt

/etc/dehydrated/conf.d/local-config.sh

# Which user should dehydrated run as? This will be implictly enforced when running as root
DEHYDRATED_USER=letsencrypt

# Which group should dehydrated run as? This will be implictly enforced when running as root
DEHYDRATED_GROUP=letsencrypt

# E-mail to use during the registration (default: <unset>)
CONTACT_EMAIL=certmaster@<FQDN>

# Program or function called in certain situations
#
# After generating the challenge-response, or after failed challenge (in this case altname is empty)
# Given arguments: clean_challenge|deploy_challenge altname token-filename token-content
#
# After successfully signing certificate
# Given arguments: deploy_cert domain path/to/privkey.pem path/to/cert.pem path/to/fullchain.pem
#
# BASEDIR and WELLKNOWN variables are exported and can be used in an external program
# default: <unset>
HOOK=/usr/local/bin/dehydrated-hook.sh

change umask that group letsencryp is able to read certs and keys. dehydrated is a way too paranoid. :-)

/etc/dehydrated/conf.d/umask.sh

umask 027

chmod -R g=rwX /var/lib/dehydrated/
chgrp -R letsencrypt /var/lib/dehydrated/

cp /usr/share/doc/dehydrated/examples/hook.sh /usr/local/bin/dehydrated-hook.sh

/usr/local/bin/dehydrated-hook.sh

deploy_cert() {
    local DOMAIN="${1}" KEYFILE="${2}" CERTFILE="${3}" FULLCHAINFILE="${4}" CHAINFILE="${5}" TIMESTAMP="${6}"

    chmod g+r $KEYFILE $CERTFILE $FULLCHAINFILE $CHAINFILE

    if [[ "$DOMAIN" =~ ^mail\..* ]]; then
        sudo systemctl restart exim4.service
        sudo systemctl restart dovecot.service
    else
        sudo systemctl restart apache2.service
    fi

/etc/sudoers.d/letsencrypt

letsencrypt     ALL = NOPASSWD: /bin/systemctl restart apache2.service, /bin/systemctl restart exim4.service, /bin/systemctl restart dovecot.service

/etc/dehydrated/domains.txt

<FQDN>

dehydrated --register --accept-terms

dehydrated -c

adduser Debian-exim letsencrypt

its only matter of defining certs ... in /etc/exim4/conf.d/main/00_local-config_macros

# Full paths to Certificate and Private Key. The Private Key file
# must be kept 'secret' and should be owned by root.Debian-exim mode
# 640 (-rw-r-----). exim-gencert takes care of these prerequisites.
# Normally, exim4 looks for certificate and key in different files:
#   MAIN_TLS_CERTIFICATE - path to certificate file,
#                          CONFDIR/exim.crt if unset
#   MAIN_TLS_PRIVATEKEY  - path to private key file
#                          CONFDIR/exim.key if unset
# You can also configure exim to look for certificate and key in the
# same file, set MAIN_TLS_CERTKEY to that file to enable. This takes
# precedence over all other settings regarding certificate and key file.
MAIN_TLS_CERTIFICATE = /var/lib/dehydrated/certs/<FQDN>/fullchain.pem
MAIN_TLS_PRIVATEKEY = /var/lib/dehydrated/certs/<FQDN>/privkey.pem

authentication

against system passwords

If you want to authenticate against system passwords (e.g. /etc/shadow) the easiest way is to use saslauthd in the Debian package sasl2-bin. You have to add the exim-user (currently Debian-exim) to the sasl group, to give exim permission to use the saslauthd service.

aptitude install sasl2-bin
adduser Debian-exim sasl

create /etc/exim4/conf.d/auth/20_local-config_sasl_auth

# Authenticate against local passwords using sasl2-bin
# Requires exim_uid to be a member of sasl group, see README.Debian.gz
plain_saslauthd_server:
  driver = plaintext
  public_name = PLAIN
  server_condition = ${if saslauthd{{$auth2}{$auth3}}{1}{0}}
  server_set_id = $auth2
  server_prompts = :
  .ifndef AUTH_SERVER_ALLOW_NOTLS_PASSWORDS
  server_advertise_condition = ${if eq{$tls_in_cipher}{}{}{*}}
  .endif

login_saslauthd_server:
  driver = plaintext
  public_name = LOGIN
  server_condition = ${if saslauthd{{$auth1}{$auth2}}{1}{0}}
  server_set_id = $auth1
  server_prompts = "Username:: : Password::"
  .ifndef AUTH_SERVER_ALLOW_NOTLS_PASSWORDS
  server_advertise_condition = ${if eq{$tls_in_cipher}{}{}{*}}
  .endif

edit /etc/default/saslauthd and set START=yes.

then restart saslauthd.

service saslauthd restart 

disabling IDENT

just add in your /etc/exim4/conf.d/main/00_local-config_macros:

# disable IDENT
rfc1413_hosts = *
rfc1413_query_timeout = 0s

relaying

/etc/exim4/conf.d/main/00_local-config_macros

# List of sender networks (IP addresses) to _unconditionally_ relay
# _for_. If you intend to be SMTP AUTH server, you do not need to enter
# anything here.
MAIN_RELAY_NETS = 192.168.1.0/24 : 127.0.0.1 : ::::1

# List of recipient domains to relay _to_. Use this list if you're -
# for example - fallback MX or mail gateway for domains.
MAIN_RELAY_TO_DOMAINS = some.other.host.com

Dovecot as LDA

add some more headers in pipe delivery. /etc/exim4/conf.d/transport/30_exim4-config_address_pipe

  delivery_date_add
  envelope_to_add
  return_path_add

we do it just via .forward and not that elegant as in https://wiki2.dovecot.org/LDA/Exim.

| "/usr/lib/dovecot/dovecot-lda"

install dovecot

aptitude install dovecot-imapd dovecot-managesieved

/etc/dovecot/conf.d/10-auth.conf

# Disable LOGIN command and all other plaintext authentications unless
# SSL/TLS is used (LOGINDISABLED capability). Note that if the remote IP
# matches the local IP (ie. you're connecting from the same computer), the
# connection is considered secure and plaintext authentication is allowed.
# See also ssl=required setting.
disable_plaintext_auth = yes

/etc/dovecot/conf.d/10-logging.conf

# Log file to use for error messages. "syslog" logs to syslog,
# /dev/stderr logs to stderr.
log_path = /var/log/dovecot/error.log

# Log file to use for informational messages. Defaults to log_path.
info_log_path = /var/log/dovecot/info.log

# Log file to use for debug messages. Defaults to info_log_path.
debug_log_path = /var/log/dovecot/debug.log

# Log unsuccessful authentication attempts and the reasons why they failed.
auth_verbose = yes

/etc/dovecot/conf.d/10-mail.conf

#mail_location = mbox:~/mail:INBOX=/var/mail/%u
mail_location = maildir:~/Maildir
}

execute 
{{{
/usr/share/dovecot/mkcert.sh

to create temporary SSL certs.

/etc/dovecot/conf.d/10-ssl.conf

# SSL/TLS support: yes, no, required. <doc/wiki/SSL.txt>
#ssl = no
ssl = yes

# PEM encoded X.509 SSL/TLS certificate and private key. They're opened before
# dropping root privileges, so keep the key file unreadable by anyone but
# root. Included doc/mkcert.sh can be used to easily generate self-signed
# certificate, just make sure to update the domains in dovecot-openssl.cnf
ssl_cert = </etc/dovecot/dovecot.pem
ssl_key = </etc/dovecot/private/dovecot.pem

# SSL protocols to use
#ssl_protocols = !SSLv3
ssl_protocols = !SSLv3

or with letsencrypt /etc/dovecot/conf.d/10-ssl.conf

# PEM encoded X.509 SSL/TLS certificate and private key. They're opened before
# dropping root privileges, so keep the key file unreadable by anyone but
# root. Included doc/mkcert.sh can be used to easily generate self-signed
# certificate, just make sure to update the domains in dovecot-openssl.cnf
ssl_cert = </var/lib/dehydrated/certs/<FQDN>/fullchain.pem
ssl_key = </var/lib/dehydrated/certs/<FQDN>/privkey.pem

dont forget following command that dovecot can read its certs.

adduser dovecot letsencrypt

/etc/dovecot/conf.d/15-lda.conf

# Address to use when sending rejection mails.
# Default is postmaster@<your domain>. %d expands to recipient domain.
postmaster_address = postmaster@<FQDN>

# Hostname to use in various parts of sent mails (e.g. in Message-Id) and
# in LMTP replies. Default is the system's real hostname@domain.
hostname = <FQHN>

# Header where the original recipient address (SMTP's RCPT TO: address) is taken
# from if not available elsewhere. With dovecot-lda -a parameter overrides this. 
# A commonly used header for this is X-Original-To.
lda_original_recipient_header = Envelope-to

# Should saving a mail to a nonexistent mailbox automatically create it?
lda_mailbox_autocreate = yes

# Should automatically created mailboxes be also automatically subscribed?
lda_mailbox_autosubscribe = yes

protocol lda {
  # Space separated list of plugins to load (default is global mail_plugins).
  mail_plugins = $mail_plugins sieve
  # seperate logfile for lda
  log_path = /var/log/dovecot/lda-error.log
  info_log_path = /var/log/dovecot/lda-info.log
  debug_log_path = /var/log/dovecot/lda-debug.log
}

in /etc/dovecot/conf.d/20-managesieve.conf enable protocols, managesieve-login and managesieve.

/etc/dovecot/conf.d/90-sieve.conf

  # Multiple script locations can be specified by appending an increasing number
  # to the setting name. The Sieve scripts found from these locations are added
  # to the script execution sequence in the specified order. Reading the
  # numbered sieve_before settings stops at the first missing setting, so no
  # numbers may be skipped.
  sieve_before = /etc/dovecot/sieve/before/

  # Specifies what envelope sender address is used for redirected messages.
  # The following values are supported for this setting:
  sieve_redirect_envelope_from = orig_recipient

/etc/dovecot/sieve/before/10-spam.sieve

require ["regex","fileinto"];

# rule:[Spam]
if allof (header :regex "X-Spam-Status" "^Yes")
{
        fileinto "Spam";
}

/etc/dovecot/sieve/before/20-Mailer-Daemon.sieve

require ["fileinto"];

# rule:[Mailer-Daemon]
if allof (address "From" "Mailer-Daemon@<FQHN>")
{
        fileinto "INBOX";
}

Adding Authentication-Results Headers

http://www.debian-administration.org/users/lee/weblog/46

/etc/exim4/conf.d/main/00_local-config_macros

# Adding Authentication-Results headers with Exim
# http://www.debian-administration.org/users/lee/weblog/46
AUTHSERV_ID = primary_hostname

# Defines the access control list that is run when an
# DKIM signature is verified
.ifndef MAIN_ACL_CHECK_DKIM
MAIN_ACL_CHECK_DKIM = acl_check_dkim
.endif
acl_smtp_dkim = MAIN_ACL_CHECK_DKIM

/etc/exim4/local_acl_rcpt

  # Adding Authentication-Results headers
  # http://www.debian-administration.org/users/lee/weblog/46
  #  iprev policy which merely checks if the reverse DNS of the sending server has been properly configured
  warn
    #hosts = !condition = ${lookup dnsdb{ptr=$sender_host_address}{true}{}}
    !condition = ${lookup dnsdb{ptr=$sender_host_address}{true}{}}
    set acl_c_iprev = permerror
    set acl_m_authresults = $acl_m_authresults;\n    iprev=permerror (no ptr) policy.iprev=$sender_host_address
 
  warn
    verify = reverse_host_lookup
    set acl_m_authresults = $acl_m_authresults;\n    iprev=pass policy.iprev=$sender_host_address ($sender_host_name)

  warn
    !condition = ${if eq{$acl_c_iprev}{permerror}{true}}
    condition = ${if and{{def:sender_host_address}{!def:sender_host_name}} {yes}{no}}
    set acl_m_authresults = $acl_m_authresults;\n    iprev=${if eq{$host_lookup_failed}{1}{fail}{temperror}} policy.iprev=$sender_host_address

  # spf
  warn
    condition = ${run{/usr/bin/spfquery.mail-spf-perl --ip \
                   ${quote:$sender_host_address} --identity \
                   ${if def:sender_address_domain \
                       {--scope mfrom  --identity ${quote:$sender_address}}\
                       {--scope helo --identity ${quote:$sender_helo_name}} }}\
                   {yes}{yes}}
    set acl_c_spf = ${if eq {$runrc}{0}{pass}\
                     {${if eq {$runrc}{1}{fail}\
                      {${if eq {$runrc}{2}{softfail}\
                       {${if eq {$runrc}{3}{neutral}\
                        {${if eq {$runrc}{4}{permerror}\
                         {${if eq {$runrc}{5}{temperror}\
                          {${if eq {$runrc}{6}{none}{error}} }} }} }} }} }} }}
    add_header = :at_start:Received-SPF: $acl_c_spf \
                                         client-ip=$sender_host_address; \
                                         ${if def:sender_address_domain \
                                            {envelope-from=${sender_address}; }{}}\
                                         helo=$sender_helo_name
#    logwrite = SPF: client-ip=$sender_host_address \
#                           ${if def:sender_address_domain \
#                              {envelope-from=${sender_address} }{}}\
#                           helo=$sender_helo_name [$acl_c_spf]
    set acl_m_authresults = $acl_m_authresults;\n    spf=$acl_c_spf \
                            ${if def:sender_address_domain \
                               {smtp.mailfrom=${sender_address} }{}}\
                            smtp.helo=$sender_helo_name

#  warn
#    condition = ${if def:authenticated_id}
#    set acl_m_authresults = $acl_m_authresults;\n    auth=pass smtp.auth=$authenticated_id

/etc/exim4/local_acl_data

# Adding Authentication-Results headers
# http://www.debian-administration.org/users/lee/weblog/46

  # adding SPF log entry only if its not spam and we have authresults
  warn
    #condition = ${if def:acl_m_authresults {true}}
    condition = ${if and { {!eq {$acl_m9}{spam}} {def:acl_m_authresults}} }
    logwrite = SPF: client-ip=$sender_host_address \
                       ${if def:sender_address_domain \
                          {envelope-from=${sender_address} }{}}\
# ${if def:received_for {received-for=${received_for} }{}}\
                       helo=$sender_helo_name [$acl_c_spf]

  # auth for mails via submission
  warn
    condition = ${if def:authenticated_id}
    set acl_m_authresults = $acl_m_authresults;\n    auth=pass smtp.auth=$authenticated_id

  # adding the header
  warn
    !condition = ${if def:acl_m_authresults {true}}
    set acl_m_authresults = ; none

  warn
    add_header = :at_start:Authentication-Results: ${AUTHSERV_ID}${acl_m_authresults}

/etc/exim4/conf.d/acl/35_local-config_check_dkim

acl_check_dkim:

  # Adding Authentication-Results headers
  # http://www.debian-administration.org/users/lee/weblog/46
  warn
    dkim_status = fail
    set acl_m_authresults = $acl_m_authresults;\n    dkim=fail ($dkim_verify_reason) \
      header.${if eq{$dkim_identity}{}{d}{i}}=$dkim_cur_signer

  warn
    dkim_status = invalid
    set acl_m_authresults = $acl_m_authresults;\n    dkim=neutral ($dkim_verify_reason) \
      header.${if eq{$dkim_identity}{}{d}{i}}=$dkim_cur_signer

  warn
    dkim_status = pass
    set acl_m_authresults = $acl_m_authresults;\n    dkim=pass ${if eq{$dkim_key_testing}{1}{(test mode) }}\
      header.${if eq{$dkim_identity}{}{d}{i}}=$dkim_cur_signer

  accept

check SPF

aptitude install spf-tools-perl

/etc/exim4/conf.d/main/00_local-config_macros

# Use spfquery to perform a pair of SPF checks (for details, see
# http://www.openspf.org/)
#CHECK_RCPT_SPF = 1
CHECK_RCPT_SPF_DOMAIN = 1

/etc/exim4/local_acl_rcpt

  # Use spfquery to perform a pair of SPF checks (for details, see
  # http://www.openspf.org/)
  #
  # This is quite costly in terms of DNS lookups (~6 lookups per mail).  Do not
  # enable if that's an issue.  Also note that if you enable this, you must
  # install "spf-tools-perl" which provides the spfquery command.
  # Missing spf-tools-perl will trigger the "Unexpected error in
  # SPF check" warning.
  .ifdef CHECK_RCPT_SPF_DOMAIN
  deny
    message = [SPF] $sender_host_address is not allowed to send mail from \
              ${if def:sender_address_domain {$sender_address_domain}{$sender_helo_name}}.  \
              Please see \
              http://www.openspf.org/Why?scope=${if def:sender_address_domain \
              {mfrom}{helo}};identity=${if def:sender_address_domain \
              {$sender_address}{$sender_helo_name}};ip=$sender_host_address
    log_message = SPF check failed.
    !acl = acl_local_deny_exceptions
    condition = ${run{/usr/bin/spfquery.mail-spf-perl --ip \
                   ${quote:$sender_host_address} --identity \
                   ${if def:sender_address_domain \
                       {--scope mfrom  --identity ${quote:$sender_address}}\
                       {--scope helo --identity ${quote:$sender_helo_name}} }}\
                   {no}{${if eq {$runrc}{1}{yes}{no}} }}
    domains = partial-lsearch;CONFDIR/check_rcpt_spf

  defer
    message = Temporary DNS error while checking SPF record.  Try again later.
    !acl = acl_local_deny_exceptions
    condition = ${if eq {$runrc}{5}{yes}{no}}
    domains = partial-lsearch;CONFDIR/check_rcpt_spf

#  warn
#    condition = ${if <={$runrc}{6}{yes}{no}}
#    add_header = Received-SPF: ${if eq {$runrc}{0}{pass}\
#                                {${if eq {$runrc}{2}{softfail}\
#                                 {${if eq {$runrc}{3}{neutral}\
#                                  {${if eq {$runrc}{4}{permerror}\
#                                   {${if eq {$runrc}{6}{none}{error}} }} }} }} }\
#                                } client-ip=$sender_host_address; \
#                                ${if def:sender_address_domain \
#                                   {envelope-from=${sender_address}; }{}}\
#                                helo=$sender_helo_name

  warn
    log_message = Unexpected error in SPF check.
    condition = ${if >{$runrc}{6}{yes}{no}}
    domains = partial-lsearch;CONFDIR/check_rcpt_spf

  .endif

add all the receiving domains you'd like to check and reject based on SPF into /etc/exim4/check_rcpt_spf

hubbed smarthosts

/etc/exim4/conf.d/router/160_local-config_hubbed_smarthosts

# router/160_local-config_hubbed_smarthosts
#################################

# route specific domains manually via smarthost

hubbed_smarthosts:
  debug_print = "R: hubbed_smarthosts for $domain"
  driver = manualroute
  domains = "${if exists{CONFDIR/hubbed_smarthosts}\
                   {partial-lsearch;CONFDIR/hubbed_smarthosts}\
              fail}"
  #senders = *@somedomain.org : *@otherlocaldomain.org
  same_domain_copy_routing = yes
  route_data = ${lookup{$domain}partial-lsearch{CONFDIR/hubbed_smarthosts}}
  transport = remote_smtp_smarthost

/etc/exim4/hubbed_smarthosts

# route specific domains manually to smarthost
# domain        smarthost
#gmail.com:     mx.fahrzurhoelle.org::587

don't forget the entry in /etc/exim4/passwd.client

DKIM

/etc/exim4/conf.d/main/00_local-config_macros

# Enable DKIM
DKIM_DOMAIN = ${sender_address_domain}
#DKIM_DOMAIN = ${lc:${domain:$h_from:}}
DKIM_SELECTOR = ${lookup{DKIM_DOMAIN}lsearch*{CONFDIR/dkim.d/dkim_selectors}}
DKIM_PRIVATE_KEY = ${if exists{CONFDIR/dkim.d/DKIM_DOMAIN.DKIM_SELECTOR.key}{CONFDIR/dkim.d/DKIM_DOMAIN.DKIM_SELECTOR.key}{0}}
DKIM_CANON = relaxed
# we just use the same as Heiko Schlittermann schlittermann.de
DKIM_SIGN_HEADERS = In-Reply-To:Content-Type:MIME-Version:References:Message-ID:Subject:To:From:Date:Cc:Content-Description:Content-ID:Content-Transfer-Encoding:Resent-To

you also will have DKIM towards smarthost. /etc/exim4/conf.d/transport/30_exim4-config_remote_smtp_smarthost

.ifdef DKIM_DOMAIN
dkim_domain = DKIM_DOMAIN
.endif
.ifdef DKIM_SELECTOR
dkim_selector = DKIM_SELECTOR
.endif
.ifdef DKIM_PRIVATE_KEY
dkim_private_key = DKIM_PRIVATE_KEY
.endif
.ifdef DKIM_CANON
dkim_canon = DKIM_CANON
.endif
.ifdef DKIM_STRICT
dkim_strict = DKIM_STRICT
.endif
.ifdef DKIM_SIGN_HEADERS
dkim_sign_headers = DKIM_SIGN_HEADERS
.endif

mkdir /etc/exim4/dkim.d

create dkim keys.

DOMAIN=$1
SELECTOR=dflt
KEYSIZE=2048
DKIMDIR=dkim.d
cd /etc/exim4
mkdir $DKIMDIR
openssl genrsa -out $DKIMDIR/$DOMAIN.$SELECTOR.key $KEYSIZE
openssl rsa -in $DKIMDIR/$DOMAIN.$SELECTOR.key -out $DKIMDIR/$DOMAIN.$SELECTOR.pub -pubout -outform PEM
echo -e "$DOMAIN:\t$SELECTOR" >> $DKIMDIR/dkim_selectors
chown -R root:Debian-exim $DKIMDIR
chmod 640 $DKIMDIR/*.key
chmod 644 $DKIMDIR/*.pub $DKIMDIR/dkim_selectors
chmod 755 $DKIMDIR

other nice stuff

/etc/exim4/conf.d/main/00_local-config_macros

# hard code hostname
MAIN_HARDCODE_PRIMARY_HOSTNAME = <FQHN>

# This option limits the number of delivery processes that Exim starts
# automatically when receiving messages via SMTP, whether via the daemon
# or by the use of -bs or -bS. If the value of the option is greater
# than zero, and the number of messages received in a single SMTP
# session exceeds this number, subsequent messages are placed on the
# queue, but no delivery processes are started. This helps to limit the
# number of Exim processes when a server restarts after downtime and
# there is a lot of mail waiting for it on other systems. On large
# systems, the default should probably be increased, and on dial-in
# client systems it should probably be set to zero (that is, disabled).
#
# this setting is necessary for mailman and reflects also in mailmans
# config.
smtp_accept_queue_per_connection = 30

# Google is way behind the times on IPv6 and tends to reject mail from
# new v6 addrs:
#dns_ipv4_lookup = *.gmail.com : *.google.com

# If TLS cannot be
# used for some reason, you can set AUTH_SERVER_ALLOW_NOTLS_PASSWORDS to
# advertise unencrypted clear text password based authenticators on all
# connections. As this is severely reducing security, using TLS is
# preferred over allowing clear text password based authenticators on
# unencrypted connections.
#AUTH_SERVER_ALLOW_NOTLS_PASSWORDS = 1

# enable sender verification http://www.exim.org/exim-html-current/doc/html/spec_html/ch-access_control_lists.html#SECTaddressverification 
#CHECK_RCPT_VERIFY_SENDER = 1

# Critical Exim Security Vulnerability: disable chunking CVE-2017-16944
chunking_advertise_hosts =

# Warn if the sender host does not have valid reverse DNS.
CHECK_RCPT_REVERSE_DNS = 1