- Abraxas - http://www.effinger.org/blog -

Dovecot, Exim, OpenLDAP und getmail unter Ubuntu – (3) Exim

Nach der Einrichtung von OpenLDAP im ersten Teil [1], der Anpassung und Konfiguration von dovecot im zweiten Teil [2] befasst sich der dritte Teil mit der Konfiguration von Exim. Obwohl fast alles im Blog detailliert beschrieben ist, empfehle ich, die Konfigurationsdateien für das Mailsystem herunterzuladen [3].

Installation von Exim

Da wir bei exim die LDAP-Unterstützung benötigen, müssen wir die entsprechende Exim-Version mit einem

sudo apt-get install exim4-daemon-heavy

installieren. Dabei wird notwendigerweise auch postfix entfernt, das von Ubuntu standardmäßig als Message Transfer Agent (MTA) eingesetzt wird.

Konfiguration von Exim

Damit exim auf die SSL-Zertifikate zur Verschlüsselung der SMTP-Verbindungen zugreifen kann, müssen wir den exim-Benutzer zur Gruppe ssl-cert hinzufügen

sudo adduser Debian-exim ssl-cert

Zur Konfiguration von exim kann man unter Ubuntu/Debian ein spezielles Konfigurationspaket namens exim4-config verwenden. Dieses erzeugt aus einzelnen Dateien im Verzeichnis /etc/exim4/conf.d eine finale Konfigurationsdatei (nähere Informationen zu exim4-config auf der Debian-Seite [4]). Ich empfehle, dieses Konfigurationspaket zu verwenden und die Grundkonfiguration mit

sudo dpkg-reconfigure exim4-config

zu starten. Im anschließenden Konfigurationsdialog wählen wir Nur lokale Mailzustellung; keine Netzwerkverbindung. Dann geben wir den korrekten DNS-Namen des Servers an, den wir auch schon bei der Erzeugung der Schlüsselzertifikate verwendet haben (hier im Beispiel myserver). Der nächste Punkt ist eigentlich selbsterklärend, wenn der SMTP-Server über alle Interfaces erreichbar sein soll, das Feld leer lassen, ansonsten die gewünschte IP-Addresse eintragen. Der nächste Schritt fordert uns auf, mögliche weitere Domains für den lokalen Mailempfang anzugeben (im Zweifelsfall leer lassen). Der Punkt, ob DNS-Anfragen minimiert werden sollen, beantworten wir mit Nein. Als Speicherformat für lokale Mails wählen wir mbox (wird aber später modifiziert). Die Einstellungen sollen in kleine Dateien aufgeteilt werden. Nun machen wir uns an die Anpassung. Wir editieren die Datei /etc/exim4/update-exim4.conf.conf und ändern die letzte Zeile mit dem Eintrag

dc_localdelivery='mail_spool'

in

dc_localdelivery='dovecot_delivery'

ab. Im nächsten Schritt löschen wir den Inhalt des Verzeichnisses /etc/exim4/conf.d und kopieren die Dateien aus dem Konfigurationspaket [3] dorthin mit

sudo rm -rf /etc/exim4/conf.d
sudo cp -R /path/to/configfiles/exim/conf.d /etc/exim4

In der Datei /etc/exim4/conf.d/main/00_local_macros passen wir folgende Zeilen an:

ldap_default_servers = myserver
MAIN_TLS_CERTIFICATE = /etc/ssl/certs/myserver.crt
MAIN_TLS_PRIVATEKEY = /etc/ssl/private/myserver.key

Wir ersetzen hier myserver jeweils durch den DNS-Namen des Servers. Nun erzeugen wir die finale Konfigurationsdatei für exim und starten exim anschließend neu mit dem Befehl

sudo update-exim4.conf && sudo /etc/init.d/exim4 restart

Das Programm erzeugt so eine finale Konfigurationsdatei, die unter /var/lib/exim4/config.autogenerated abgelegt wird. Für diejenigen, die das Debiankonfigurationssystem nicht nutzen können/wollen, ist hier deren Inhalt aufgeführt:

#########
# WARNING WARNING WARNING
# WARNING WARNING WARNING
# WARNING WARNING WARNING
# WARNING WARNING WARNING
# WARNING WARNING WARNING
# This file is generated dynamically from the files in
# the conf.d/ directory, or from exim4.conf.template respectively.
# Additional information is read from update-exim4.conf.conf
# This version of the file was created from the directory /etc/exim4
# Any changes you make here will be lost.
# See /usr/share/doc/exim4-base/README.Debian.gz and update-exim4.conf(8)
# for instructions of customization.
# WARNING WARNING WARNING
# WARNING WARNING WARNING
# WARNING WARNING WARNING
# WARNING WARNING WARNING
# WARNING WARNING WARNING
#########

FIRST_USER_ACCOUNT_UID = 1000

acl_not_smtp_start = acl_check_not_smtp

ldap_default_servers = myserver

LDAP_BASE = ou=users,o=effinger

SEC_MAIL_USER=secmail
IS_SENDER_SECMAIL = eq{$originator_uid}{${extract{2}{:}{${lookup{SEC_MAIL_USER}lsearch{/etc/passwd}{$value}}}}}
received_header_text = ${if !IS_SENDER_SECMAIL {Received: ${if def:sender_rcvhost {from $sender_rcvhost\n\t}{${if def:sender_ident {from ${quote_local_part:$sender_ident} }}${if def:sender_helo_name {(helo=$sender_helo_name)\n\t}}}}by $primary_hostname ${if def:received_protocol {with $received_protocol}} ${if def:tls_cipher {($tls_cipher)\n\t}}(Exim $version_number)\n\t${if def:sender_address {(envelope-from <$sender_address>)\n\t}}id $message_exim_id${if def:received_for {\n\tfor $received_for}}}{}}
LOCAL_DELIVERY_SECMAIL = dovecot_delivery_secmail

MAIN_TLS_ENABLE = yes
MAIN_TLS_CERTIFICATE = /etc/ssl/certs/myserver.crt
MAIN_TLS_PRIVATEKEY = /etc/ssl/private/myserver.key
MAIN_TLS_VERIFY_CERTIFICATES = /etc/ssl/certs/ca.crt

SENDER_EXTRACT_UID = ${sg{${lc:$sender_address}} \
	{\N^([a-zA-Z0-9_.-]+)@([a-zA-Z0-9_.-]+@[a-zA-Z0-9_.-]+)$\N}{\$1}}

SENDER_EXTRACT_REAL_MAIL = ${sg{${lc:$sender_address}} \
	{\N^([a-zA-Z0-9_.-]+)@([a-zA-Z0-9_.-]+@[a-zA-Z0-9_.-]+)$\N}{\$2}}

IS_SENDER_REMOTE = ${lookup ldap \
		{ldap:///uid=${quote_ldap_dn:SENDER_EXTRACT_UID},LDAP_BASE??sub?(&(dcSubMailAddress=${quote_ldap:SENDER_EXTRACT_REAL_MAIL})(dcAccountStatus=active))} \
		{yes}fail}

IS_LOCAL_PART_VALID = ${lookup ldap \
		{ldap:///uid=${quote_ldap_dn:${lc:$local_part}},LDAP_BASE??base?(objectClass=dcMailUser)} \
		{yes}{no}}

GET_UID_FOR_RCPT = ${tr \
	{${lookup ldapm \
		{ldap:///LDAP_BASE??sub?${if match_domain{$domain}{+local_domains}{(mail=${quote_ldap:$local_part})}{(&(dcSubMailAddress=${quote_ldap:$local_part@$domain})(dcAccountStatus=active))}}} \
		{${sg{${lc:$value}}{\N(?m)^.*uid="(.*?)".*$\N}{\$1}}} \
	}} \
	{\n}{,} \
}

GET_ALIAS_FOR_RCPT = ${tr \
	{${lookup ldapm \
		{ldap:///LDAP_BASE?mail,dcSubMailAddress?sub?(|(dcMailAlias=${quote_ldap:$local_part${if match_domain{$domain}{+local_domains}{}{@$domain}}})(&(dcMailAlternateAddress=${quote_ldap:$local_part@$domain})(dcAccountStatus=active)))} \
		{${sg{${lc:$value}}{\N(?m)^.*(mail|dcsubmailaddress)="(.*?)".*$\N}{\$2}}} \
	}} \
	{\n}{,} \
}

GET_ALIAS_FOR_AUTH = ${tr \
	{${lookup ldapm \
		{ldap:///LDAP_BASE?dcMailAlias,dcMailAlternateAddress?sub?(|(mail=${quote_ldap:AUTH_SERVER_MAIL})(&(dcSubMailAddress=${quote_ldap:AUTH_SERVER_MAIL})(dcAccountStatus=active)))} \
		{${sg{${lc:$value}}{\N(?m)^.*(dcmailalias|dcmailalternateaddress)="(.*?)".*$\N}{\$2}}} \
	}} \
	{\n}{,} \
}

GET_COMPLETE_MAIL_FOR_LOCAL_PART_UID = ${lookup ldap \
		{ldap:///uid=${quote_ldap_dn:${lc:$local_part}},LDAP_BASE?mail?base?(objectClass=dcMailUser)} \
		{${local_part}@${value}}}

GET_LOCAL_MAIL = ${if match_domain{$parent_domain}{+local_domains} \
	{GET_COMPLETE_MAIL_FOR_LOCAL_PART_UID} \
	{${lookup ldap \
		{ldap:///uid=${quote_ldap_dn:${lc:$local_part}},LDAP_BASE?dcSubMailAddress?sub?(&(dcSubMailAddress=${quote_ldap:$parent_local_part@$parent_domain})(dcAccountStatus=active))} \
		{$local_part@$parent_local_part@$parent_domain} \
		{GET_COMPLETE_MAIL_FOR_LOCAL_PART_UID} \
	}} \
}

IS_AUTH_REMOTE = ${lookup ldap { \
	user="uid=${quote_ldap_dn:AUTH_SERVER_UID},LDAP_BASE" \
    	pass=${quote:AUTH_SERVER_PASSWORD} \
	ldap:///LDAP_BASE??sub?(&(dcSubMailAddress=${quote_ldap:AUTH_SERVER_MAIL})(dcAccountStatus=active)(dcSMTPServer=*)(dcSMTPLogin=*)(dcSMTPPassword=*))} \
	{yes}fail}

AUTH_REMOTE_SERVER = ${lookup ldap { \
	user="uid=${quote_ldap_dn:AUTH_SERVER_UID},LDAP_BASE" \
    	pass=${quote:AUTH_SERVER_PASSWORD} \
	ldap:///LDAP_BASE?dcSMTPServer?sub?(&(dcSubMailAddress=${quote_ldap:AUTH_SERVER_MAIL})(dcAccountStatus=active)(dcSMTPServer=*)(dcSMTPLogin=*)(dcSMTPPassword=*))} \
	{$value}{}}

AUTH_REMOTE_LOGIN = ${lookup ldap {\
	user="uid=${quote_ldap_dn:AUTH_SERVER_UID},LDAP_BASE" \
    	pass=${quote:AUTH_SERVER_PASSWORD} \
	ldap:///LDAP_BASE?dcSMTPLogin?sub?(&(dcSubMailAddress=${quote_ldap:AUTH_SERVER_MAIL})(dcAccountStatus=active)(dcSMTPServer=*)(dcSMTPLogin=*)(dcSMTPPassword=*))} \
	{$value}{}}

AUTH_REMOTE_PASSWORD = ${lookup ldap {\
	user="uid=${quote_ldap_dn:AUTH_SERVER_UID},LDAP_BASE" \
    	pass=${quote:AUTH_SERVER_PASSWORD} \
	ldap:///LDAP_BASE?dcSMTPPassword?sub?(&(dcSubMailAddress=${quote_ldap:AUTH_SERVER_MAIL})(dcAccountStatus=active)(dcSMTPServer=*)(dcSMTPLogin=*)(dcSMTPPassword=*))} \
	{$value}{}}

AUTH_SERVER_PLAIN_AUTH = ${if ldapauth \
    {user="uid=${quote_ldap_dn:${sg{${lc:$auth2}} \
 	{\N^([a-zA-Z0-9_.-]+)@([a-zA-Z0-9_@.-]+)$\N}{\$1}}},LDAP_BASE" \
    pass=${quote:$auth3} \
    ldap:///}{yes}{no}}

AUTH_SERVER_LOGIN_AUTH = ${if ldapauth \
    {user="uid=${quote_ldap_dn:${sg{${lc:$auth1}} \
 	{\N^([a-zA-Z0-9_.-]+)@([a-zA-Z0-9_@.-]+)$\N}{\$1}}},LDAP_BASE" \
    pass=${quote:$auth2} \
    ldap:///}{yes}{no}}

AUTH_SERVER_MAIL = ${sg{${lc:${extract{1}{\/}{$authenticated_id}}}}{\N^([a-zA-Z0-9_.-]+)@([a-zA-Z0-9_@.-]+)$\N}{\$2}}

AUTH_SERVER_UID = ${sg{${lc:${extract{1}{\/}{$authenticated_id}}}}{\N^([a-zA-Z0-9_.-]+)@([a-zA-Z0-9_@.-]+)$\N}{\$1}}

AUTH_SERVER_PASSWORD = ${extract{2}{\/}{$authenticated_id}}

IS_SENDER_BAD = ${if match {AUTH_SERVER_MAIL}{\N^[a-zA-Z0-9_.-]+$\N} \
	{${if match {${lc:AUTH_SERVER_MAIL}} {${lc:$sender_address_local_part}} {no} \
		{${if match {${lc:$sender_address_local_part}} \
			{${lc:${tr{GET_ALIAS_FOR_AUTH}{,}{::}}}} \
			{no}{yes}}}\
	}} \
	{${if match_address {${lc:AUTH_SERVER_MAIL}} {${lc:$sender_address}} {no} \
		{${if match_address {${lc:$sender_address}} \
			{${lc:${tr{GET_ALIAS_FOR_AUTH}{,}{::}}}} \
			{no}\
			{${if and{ \
					{match_domain{$sender_address_domain}{+local_domains}} \
					{match_local_part{$sender_address_local_part}{SEC_MAIL_USER}} \
					{match_ip{$sender_host_address}{@[]}} \
				}\
				{no}\
				{yes}\
			}}\
		}}\
	}}}

exim_path = /usr/sbin/exim4

.ifndef CONFDIR
CONFDIR = /etc/exim4
.endif

UPEX4CmacrosUPEX4C = 1
##############################################
# the following macro definitions were created
# dynamically by /usr/sbin/update-exim4.conf
.ifndef MAIN_PACKAGE_VERSION
MAIN_PACKAGE_VERSION=4.69-5ubuntu2
.endif
.ifndef MAIN_LOCAL_DOMAINS
MAIN_LOCAL_DOMAINS=@:localhost
.endif
.ifndef MAIN_RELAY_TO_DOMAINS
MAIN_RELAY_TO_DOMAINS=empty
.endif
.ifndef ETC_MAILNAME
ETC_MAILNAME=myserver
.endif
.ifndef LOCAL_DELIVERY
LOCAL_DELIVERY=dovecot_delivery
.endif
.ifndef MAIN_RELAY_NETS
MAIN_RELAY_NETS=: 127.0.0.1 : ::::1
.endif
.ifndef DCreadhost
DCreadhost=empty
.endif
.ifndef DCsmarthost
DCsmarthost=empty
.endif
.ifndef DC_eximconfig_configtype
DC_eximconfig_configtype=local
.endif
.ifndef DCconfig_local
DCconfig_local=1
.endif
##############################################

domainlist local_domains = MAIN_LOCAL_DOMAINS

domainlist relay_to_domains = MAIN_RELAY_TO_DOMAINS

hostlist relay_from_hosts = MAIN_RELAY_NETS

.ifndef MAIN_PRIMARY_HOSTNAME_AS_QUALIFY_DOMAIN
.ifndef MAIN_QUALIFY_DOMAIN
qualify_domain = ETC_MAILNAME
.else
qualify_domain = MAIN_QUALIFY_DOMAIN
.endif
.endif

.ifdef MAIN_LOCAL_INTERFACES
local_interfaces = MAIN_LOCAL_INTERFACES
.endif

.ifndef LOCAL_DELIVERY
LOCAL_DELIVERY=mail_spool
.endif

gecos_pattern = ^([^,:]*)
gecos_name = $1

.ifndef CHECK_RCPT_LOCAL_LOCALPARTS
CHECK_RCPT_LOCAL_LOCALPARTS = ^[.] : ^.*[@%!/|`#&?]
.endif

.ifndef CHECK_RCPT_REMOTE_LOCALPARTS
CHECK_RCPT_REMOTE_LOCALPARTS = ^[./|] : ^.*[@%!`#&?] : ^.*/\\.\\./
.endif

.ifndef MAIN_LOG_SELECTOR
MAIN_LOG_SELECTOR = +tls_peerdn
.endif

.ifndef MAIN_ACL_CHECK_MAIL
MAIN_ACL_CHECK_MAIL = acl_check_mail
.endif
acl_smtp_mail = MAIN_ACL_CHECK_MAIL

.ifndef MAIN_ACL_CHECK_RCPT
MAIN_ACL_CHECK_RCPT = acl_check_rcpt
.endif
acl_smtp_rcpt = MAIN_ACL_CHECK_RCPT

.ifndef MAIN_ACL_CHECK_DATA
MAIN_ACL_CHECK_DATA = acl_check_data
.endif
acl_smtp_data = MAIN_ACL_CHECK_DATA

.ifdef MESSAGE_SIZE_LIMIT
message_size_limit = MESSAGE_SIZE_LIMIT
.endif

.ifdef MAIN_ALLOW_DOMAIN_LITERALS
allow_domain_literals
.endif

.ifndef DC_minimaldns
.ifndef MAIN_HOST_LOOKUP
MAIN_HOST_LOOKUP = *
.endif
host_lookup = MAIN_HOST_LOOKUP
.endif

.ifdef MAIN_HARDCODE_PRIMARY_HOSTNAME
primary_hostname = MAIN_HARDCODE_PRIMARY_HOSTNAME
.endif

.ifdef MAIN_SMTP_ACCEPT_MAX_NOMAIL_HOSTS
smtp_accept_max_nonmail_hosts = MAIN_SMTP_ACCEPT_MAX_NOMAIL_HOSTS
.endif

.ifndef MAIN_FORCE_SENDER
local_from_check = false
local_sender_retain = true
untrusted_set_sender = *
.endif

.ifndef MAIN_IGNORE_BOUNCE_ERRORS_AFTER
MAIN_IGNORE_BOUNCE_ERRORS_AFTER = 2d
.endif
ignore_bounce_errors_after = MAIN_IGNORE_BOUNCE_ERRORS_AFTER

.ifndef MAIN_TIMEOUT_FROZEN_AFTER
MAIN_TIMEOUT_FROZEN_AFTER = 7d
.endif
timeout_frozen_after = MAIN_TIMEOUT_FROZEN_AFTER

.ifndef MAIN_FREEZE_TELL
MAIN_FREEZE_TELL = postmaster
.endif
freeze_tell = MAIN_FREEZE_TELL

.ifndef SPOOLDIR
SPOOLDIR = /var/spool/exim4
.endif
spool_directory = SPOOLDIR

.ifndef MAIN_TRUSTED_USERS
MAIN_TRUSTED_USERS = uucp
.endif
trusted_users = MAIN_TRUSTED_USERS
.ifdef MAIN_TRUSTED_GROUPS
trusted_groups = MAIN_TRUSTED_GROUPS
.endif

.ifdef MAIN_TLS_ENABLE
.ifndef MAIN_TLS_ADVERTISE_HOSTS
MAIN_TLS_ADVERTISE_HOSTS = *
.endif
tls_advertise_hosts = MAIN_TLS_ADVERTISE_HOSTS

.ifdef MAIN_TLS_CERTKEY
tls_certificate = MAIN_TLS_CERTKEY
.else
.ifndef MAIN_TLS_CERTIFICATE
MAIN_TLS_CERTIFICATE = CONFDIR/exim.crt
.endif
tls_certificate = MAIN_TLS_CERTIFICATE

.ifndef MAIN_TLS_PRIVATEKEY
MAIN_TLS_PRIVATEKEY = CONFDIR/exim.key
.endif
tls_privatekey = MAIN_TLS_PRIVATEKEY
.endif

.ifndef MAIN_TLS_VERIFY_CERTIFICATES
MAIN_TLS_VERIFY_CERTIFICATES = ${if exists{/etc/ssl/certs/ca-certificates.crt}\
                                    {/etc/ssl/certs/ca-certificates.crt}\
				    {/dev/null}}
.endif
tls_verify_certificates = MAIN_TLS_VERIFY_CERTIFICATES

.ifdef MAIN_TLS_VERIFY_HOSTS
tls_verify_hosts = MAIN_TLS_VERIFY_HOSTS
.endif

.ifndef MAIN_TLS_TRY_VERIFY_HOSTS
MAIN_TLS_TRY_VERIFY_HOSTS = *
.endif
tls_try_verify_hosts = MAIN_TLS_TRY_VERIFY_HOSTS

.endif

.ifdef MAIN_LOG_SELECTOR
log_selector = MAIN_LOG_SELECTOR
.endif

begin acl

acl_check_not_smtp:
  accept
	condition=${if IS_SENDER_SECMAIL {yes}{no}}
	control=suppress_local_fixups

  accept

acl_local_deny_exceptions:
  accept
    hosts = ${if exists{CONFDIR/host_local_deny_exceptions}\
                 {CONFDIR/host_local_deny_exceptions}\
                 {}}
  accept
    senders = ${if exists{CONFDIR/sender_local_deny_exceptions}\
                   {CONFDIR/sender_local_deny_exceptions}\
                   {}}
  accept
    hosts = ${if exists{CONFDIR/local_host_whitelist}\
                 {CONFDIR/local_host_whitelist}\
                 {}}
  accept
    senders = ${if exists{CONFDIR/local_sender_whitelist}\
                   {CONFDIR/local_sender_whitelist}\
                   {}}

  .ifdef LOCAL_DENY_EXCEPTIONS_LOCAL_ACL_FILE
  .include LOCAL_DENY_EXCEPTIONS_LOCAL_ACL_FILE
  .endif

  .ifdef WHITELIST_LOCAL_DENY_LOCAL_ACL_FILE
  .include WHITELIST_LOCAL_DENY_LOCAL_ACL_FILE
  .endif

acl_check_mail:
  .ifdef CHECK_MAIL_HELO_ISSUED
  deny
    message = no HELO given before MAIL command
    condition = ${if def:sender_helo_name {no}{yes}}
  .endif

  deny
    message = bad sender
    log_message = bad sender (auth_id=AUTH_SERVER_MAIL mismatches sender=$sender_address)
    condition = IS_SENDER_BAD
  accept

acl_check_rcpt:

  .ifdef CHECK_RCPT_LOCAL_LOCALPARTS
  deny
    domains = +local_domains
    local_parts = CHECK_RCPT_LOCAL_LOCALPARTS
    message = restricted characters in address
  .endif

  .ifdef CHECK_RCPT_REMOTE_LOCALPARTS
  deny
    domains = !+local_domains
    local_parts = CHECK_RCPT_REMOTE_LOCALPARTS
    message = restricted characters in address
  .endif

  accept
    .ifndef CHECK_RCPT_POSTMASTER
    local_parts = postmaster
    .else
    local_parts = CHECK_RCPT_POSTMASTER
    .endif
    domains = +local_domains : +relay_to_domains

  .ifdef CHECK_RCPT_VERIFY_SENDER
  deny
    message = Sender verification failed
    !acl = acl_local_deny_exceptions
    !verify = sender
  .endif

  deny
    !acl = acl_local_deny_exceptions
    senders = ${if exists{CONFDIR/local_sender_callout}\
                         {CONFDIR/local_sender_callout}\
                   {}}
    !verify = sender/callout

  require
    verify = recipient

  accept
    authenticated = *
    condition = IS_SENDER_REMOTE

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

  deny
    !acl = acl_local_deny_exceptions
    recipients = ${if exists{CONFDIR/local_rcpt_callout}\
                            {CONFDIR/local_rcpt_callout}\
                      {}}
    !verify = recipient/callout

  deny
    message = sender envelope address $sender_address is locally blacklisted here. If you think this is wrong, get in touch with postmaster
    !acl = acl_local_deny_exceptions
    senders = ${if exists{CONFDIR/local_sender_blacklist}\
                   {CONFDIR/local_sender_blacklist}\
                   {}}

  deny
    message = sender IP address $sender_host_address is locally blacklisted here. If you think this is wrong, get in touch with postmaster
    !acl = acl_local_deny_exceptions
    hosts = ${if exists{CONFDIR/local_host_blacklist}\
                 {CONFDIR/local_host_blacklist}\
                 {}}

  .ifdef CHECK_RCPT_REVERSE_DNS
  warn
    message = X-Host-Lookup-Failed: Reverse DNS lookup failed for $sender_host_address (${if eq{$host_lookup_failed}{1}{failed}{deferred}})
     condition = ${if and{{def:sender_host_address}{!def:sender_host_name}}\
                      {yes}{no}}
  .endif

  .ifdef CHECK_RCPT_SPF
  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 --ip \"$sender_host_address\" --mail-from \"$sender_address\" --helo \"$sender_helo_name\"}\
                     {no}{${if eq {$runrc}{1}{yes}{no}}}}

  defer
    message = Temporary DNS error while checking SPF record.  Try again later.
    condition = ${if eq {$runrc}{5}{yes}{no}}

  warn
    message = Received-SPF: ${if eq {$runrc}{0}{pass}{${if eq {$runrc}{2}{softfail}\
                                 {${if eq {$runrc}{3}{neutral}{${if eq {$runrc}{4}{unknown}{${if eq {$runrc}{6}{none}{error}}}}}}}}}}
    condition = ${if <={$runrc}{6}{yes}{no}}

  warn
    log_message = Unexpected error in SPF check.
    condition = ${if >{$runrc}{6}{yes}{no}}

  warn
    message = X-SPF-Guess: ${run{/usr/bin/spfquery --ip \"$sender_host_address\" --mail-from \"$sender_address\" \ --helo \"$sender_helo_name\" --guess true}\
                                {pass}{${if eq {$runrc}{2}{softfail}{${if eq {$runrc}{3}{neutral}{${if eq {$runrc}{4}{unknown}\
                                {${if eq {$runrc}{6}{none}{error}}}}}}}}}}
    condition = ${if <={$runrc}{6}{yes}{no}}

  defer
    message = Temporary DNS error while checking SPF record.  Try again later.
    condition = ${if eq {$runrc}{5}{yes}{no}}
  .endif

  .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)
    dnslists = CHECK_RCPT_IP_DNSBLS
  .endif

  .ifdef CHECK_RCPT_DOMAIN_DNSBLS
  warn
    message = X-Warning: $sender_address_domain is listed at $dnslist_domain ($dnslist_value: $dnslist_text)
    log_message = $sender_address_domain is listed at $dnslist_domain ($dnslist_value: $dnslist_text)
    !senders = ${if exists{CONFDIR/local_domain_dnsbl_whitelist}\
                    {CONFDIR/local_domain_dnsbl_whitelist}\
                    {}}
    dnslists = CHECK_RCPT_DOMAIN_DNSBLS
  .endif

  .ifdef CHECK_RCPT_LOCAL_ACL_FILE
  .include CHECK_RCPT_LOCAL_ACL_FILE
  .endif

  accept
    domains = +relay_to_domains
    endpass
    verify = recipient

  accept

acl_check_data:

  .ifdef CHECK_DATA_VERIFY_HEADER_SYNTAX
  deny
    message = Message headers fail syntax check
    !acl = acl_local_deny_exceptions
    !verify = header_syntax
  .endif

  .ifdef CHECK_DATA_VERIFY_HEADER_SENDER
  deny
    message = No verifiable sender address in message headers
    !acl = acl_local_deny_exceptions
    !verify = header_sender
  .endif

  .ifdef CHECK_DATA_LOCAL_ACL_FILE
  .include CHECK_DATA_LOCAL_ACL_FILE
  .endif

  accept

begin routers

.ifdef MAIN_ALLOW_DOMAIN_LITERALS
domain_literal:
  debug_print = "R: domain_literal for $local_part@$domain"
  driver = ipliteral
  domains = ! +local_domains
  transport = remote_smtp
.endif

hubbed_hosts:
  debug_print = "R: hubbed_hosts for $domain"
  driver = manualroute
  domains = "${if exists{CONFDIR/hubbed_hosts}\
                   {partial-lsearch;CONFDIR/hubbed_hosts}\
              fail}"
  same_domain_copy_routing = yes
  route_data = ${lookup{$domain}partial-lsearch{CONFDIR/hubbed_hosts}}
  transport = remote_smtp

system_aliases:
  debug_print = "R: system_aliases for $local_part@$domain"
  driver = redirect
  domains = +local_domains
  allow_fail
  allow_defer
  data = ${lookup{$local_part}lsearch{/etc/aliases}}
  .ifdef SYSTEM_ALIASES_USER
  user = SYSTEM_ALIASES_USER
  .endif
  .ifdef SYSTEM_ALIASES_GROUP
  group = SYSTEM_ALIASES_GROUP
  .endif
  .ifdef SYSTEM_ALIASES_FILE_TRANSPORT
  file_transport = SYSTEM_ALIASES_FILE_TRANSPORT
  .endif
  .ifdef SYSTEM_ALIASES_PIPE_TRANSPORT
  pipe_transport = SYSTEM_ALIASES_PIPE_TRANSPORT
  .endif
  .ifdef SYSTEM_ALIASES_DIRECTORY_TRANSPORT
  directory_transport = SYSTEM_ALIASES_DIRECTORY_TRANSPORT
  .endif

.ifndef FIRST_USER_ACCOUNT_UID
FIRST_USER_ACCOUNT_UID = 0
.endif

.ifndef DEFAULT_SYSTEM_ACCOUNT_ALIAS
DEFAULT_SYSTEM_ACCOUNT_ALIAS = :fail: no mail to system accounts
.endif

COND_SYSTEM_USER_AND_REMOTE_SUBMITTER = "\
               ${if and{{! match_ip{$sender_host_address}{:@[]}}\
                        {<{$local_user_uid}{FIRST_USER_ACCOUNT_UID}}}\
                    {1}{0}\
		}"

lowuid_aliases:
  debug_print = "R: lowuid_aliases for $local_part@$domain (UID $local_user_uid)"
  check_local_user
  driver = redirect
  allow_fail
  domains = +local_domains
  condition = COND_SYSTEM_USER_AND_REMOTE_SUBMITTER
  data = ${if exists{/etc/exim4/lowuid-aliases}\
              {${lookup{$local_part}lsearch{/etc/exim4/lowuid-aliases}\
              {$value}{DEFAULT_SYSTEM_ACCOUNT_ALIAS}}}{DEFAULT_SYSTEM_ACCOUNT_ALIAS}}

local_user_secmail:
  debug_print = "R: local_user_secmail for $local_part@$domain"
  driver = accept
  domains = +local_domains
  local_parts = ! root
  condition = ${if IS_SENDER_SECMAIL {IS_LOCAL_PART_VALID}{no}}
  transport = LOCAL_DELIVERY_SECMAIL

local_user:
  debug_print = "R: local_user for $local_part@$domain"
  driver = accept
  domains = +local_domains
  local_parts = ! root
  condition = IS_LOCAL_PART_VALID
  transport = LOCAL_DELIVERY
  cannot_route_message = Unknown user

ldap_uid_aliases:
  debug_print = "R: ldap_uid_alias for $local_part@$domain"
  driver = redirect
  data = GET_UID_FOR_RCPT
  check_ancestor

ldap_aliases:
  debug_print = "R: ldap_alias for $local_part@$domain"
  driver = redirect
  data = GET_ALIAS_FOR_RCPT
  check_ancestor

.ifdef DCconfig_satellite
hub_user:
  debug_print = "R: hub_user for $local_part@$domain"
  driver = redirect
  domains = +local_domains
  data = ${local_part}@DCreadhost
  check_local_user

hub_user_smarthost:
  debug_print = "R: hub_user_smarthost for $local_part@$domain"
  driver = manualroute
  domains = DCreadhost
  transport = remote_smtp_smarthost
  route_list = * DCsmarthost byname
  host_find_failed = defer
  same_domain_copy_routing = yes
  check_local_user
.endif

smarthost_auto:
   condition = IS_AUTH_REMOTE
   driver = manualroute
   domains = ! +local_domains
   route_data = AUTH_REMOTE_SERVER
   transport = remote_smtp_smarthost_auto

mail4root:
  debug_print = "R: mail4root for $local_part@$domain"
  driver = redirect
  domains = +local_domains
  data = /var/mail/mail
  file_transport = address_file
  local_parts = root
  user = mail
  group = mail

begin transports

.ifdef HIDE_MAILNAME
REMOTE_SMTP_HEADERS_REWRITE=*@+local_domains $1@DCreadhost frs : *@ETC_MAILNAME $1@DCreadhost frs
REMOTE_SMTP_RETURN_PATH=${if match_domain{$sender_address_domain}{+local_domains}{${sender_address_local_part}@DCreadhost}{${if match_domain{$sender_address_domain}{ETC_MAILNAME}{${sender_address_local_part}@DCreadhost}fail}}}
.endif

.ifdef REMOTE_SMTP_HELO_FROM_DNS
REMOTE_SMTP_HELO_DATA=${lookup dnsdb {ptr=$sending_ip_address}{$value}{$primary_hostname}}
.endif

address_file:
  debug_print = "T: address_file for $local_part@$domain"
  driver = appendfile
  delivery_date_add
  envelope_to_add
  return_path_add

address_pipe:
  debug_print = "T: address_pipe for $local_part@$domain"
  driver = pipe
  return_fail_output

address_reply:
  debug_print = "T: autoreply for $local_part@$domain"
  driver = autoreply

dovecot_delivery:
  debug_print = "T: dovecot_delivery_pipe for $local_part@$domain translates to GET_LOCAL_MAIL"
  driver = pipe
  command = /usr/lib/dovecot/deliver -d "GET_LOCAL_MAIL"
  message_prefix =
  message_suffix =
  delivery_date_add
  envelope_to_add
  return_path_add
  log_output
  user = secmail
  group = secmail

dovecot_delivery_secmail:
  debug_print = "T: dovecot_delivery_pipe_secmail for $local_part@$domain translates to GET_LOCAL_MAIL"
  driver = pipe
  command = /usr/lib/dovecot/deliver -d "GET_LOCAL_MAIL"
  message_prefix =
  message_suffix =
  delivery_date_add = false
  envelope_to_add = false
  return_path_add = false
  log_output
  user = secmail
  group = secmail

mail_spool:
  debug_print = "T: appendfile for $local_part@$domain"
  driver = appendfile
  file = /var/mail/$local_part
  delivery_date_add
  envelope_to_add
  return_path_add
  group = mail
  mode = 0660
  mode_fail_narrower = false

maildir_home:
  debug_print = "T: maildir_home for $local_part@$domain"
  driver = appendfile
  .ifdef MAILDIR_HOME_MAILDIR_LOCATION
  directory = MAILDIR_HOME_MAILDIR_LOCATION
  .else
  directory = $home/Maildir
  .endif
  .ifdef MAILDIR_HOME_CREATE_DIRECTORY
  create_directory
  .endif
  .ifdef MAILDIR_HOME_CREATE_FILE
  create_file = MAILDIR_HOME_CREATE_FILE
  .endif
  delivery_date_add
  envelope_to_add
  return_path_add
  maildir_format
  .ifdef MAILDIR_HOME_DIRECTORY_MODE
  directory_mode = MAILDIR_HOME_DIRECTORY_MODE
  .else
  directory_mode = 0700
  .endif
  .ifdef MAILDIR_HOME_MODE
  mode = MAILDIR_HOME_MODE
  .else
  mode = 0600
  .endif
  mode_fail_narrower = false

maildrop_pipe:
  debug_print = "T: maildrop_pipe for $local_part@$domain"
  driver = pipe
  path = "/bin:/usr/bin:/usr/local/bin"
  command = "/usr/bin/maildrop"
  return_path_add
  delivery_date_add
  envelope_to_add

procmail_pipe:
  debug_print = "T: procmail_pipe for $local_part@$domain"
  driver = pipe
  path = "/bin:/usr/bin:/usr/local/bin"
  command = "/usr/bin/procmail"
  return_path_add
  delivery_date_add
  envelope_to_add

remote_smtp:
  debug_print = "T: remote_smtp for $local_part@$domain"
  driver = smtp
.ifdef REMOTE_SMTP_HOSTS_AVOID_TLS
  hosts_avoid_tls = REMOTE_SMTP_HOSTS_AVOID_TLS
.endif
.ifdef REMOTE_SMTP_HEADERS_REWRITE
  headers_rewrite = REMOTE_SMTP_HEADERS_REWRITE
.endif
.ifdef REMOTE_SMTP_RETURN_PATH
  return_path = REMOTE_SMTP_RETURN_PATH
.endif
.ifdef REMOTE_SMTP_HELO_FROM_DNS
  helo_data=REMOTE_SMTP_HELO_DATA
.endif

remote_smtp_smarthost:
  debug_print = "T: remote_smtp_smarthost for $local_part@$domain"
  driver = smtp
  hosts_try_auth = <; ${if exists{CONFDIR/passwd.client} \
        {\
        ${lookup{$host}nwildlsearch{CONFDIR/passwd.client}{$host_address}}\
        }\
        {} \
      }
.ifdef REMOTE_SMTP_SMARTHOST_HOSTS_AVOID_TLS
  hosts_avoid_tls = REMOTE_SMTP_SMARTHOST_HOSTS_AVOID_TLS
.endif
.ifdef REMOTE_SMTP_HEADERS_REWRITE
  headers_rewrite = REMOTE_SMTP_HEADERS_REWRITE
.endif
.ifdef REMOTE_SMTP_RETURN_PATH
  return_path = REMOTE_SMTP_RETURN_PATH
.endif
.ifdef REMOTE_SMTP_HELO_FROM_DNS
  helo_data=REMOTE_SMTP_HELO_DATA
.endif

remote_smtp_smarthost_auto:
  debug_print = "T: remote_smtp_smarthost_auto for $local_part@$domain from user AUTH_SERVER_MAIL"
  driver = smtp
  hosts_require_auth = AUTH_REMOTE_SERVER

address_directory:
  debug_print = "T: address_directory for $local_part@$domain"
  driver = appendfile
  delivery_date_add
  envelope_to_add
  return_path_add
  check_string = ""
  escape_string = ""
  maildir_format

begin retry

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

begin rewrite

.ifndef NO_EAA_REWRITE_REWRITE
*@+local_domains "${lookup{${local_part}}lsearch{/etc/email-addresses}\
                   {$value}fail}" Ffrs
*@ETC_MAILNAME "${lookup{${local_part}}lsearch{/etc/email-addresses}\
                   {$value}fail}" Ffrs
.endif

begin authenticators

plain_ldapauth_server:
  driver = plaintext
  public_name = PLAIN
  .ifndef AUTH_SERVER_ALLOW_NOTLS_PASSWORDS
    server_advertise_condition = ${if eq{$tls_cipher}{}{}{*}}
  .endif
  server_condition = AUTH_SERVER_PLAIN_AUTH
  server_set_id = $auth2/$auth3
  server_prompts = :

login_ldapauth_server:
  driver = plaintext
  public_name = LOGIN
  server_prompts = Username:: : Password::
  .ifndef AUTH_SERVER_ALLOW_NOTLS_PASSWORDS
    server_advertise_condition = ${if eq{$tls_cipher}{}{}{*}}
  .endif
  server_condition = AUTH_SERVER_LOGIN_AUTH
  server_set_id = $auth1/$auth2

cram_md5_client:
     driver = cram_md5
     public_name = CRAM-MD5
     client_name = AUTH_REMOTE_LOGIN
     client_secret = AUTH_REMOTE_PASSWORD

plain_client:
      driver = plaintext
      public_name = PLAIN
      client_send = <|^AUTH_REMOTE_LOGIN^AUTH_REMOTE_PASSWORD

login_client:
      driver = plaintext
      public_name = LOGIN
      client_send = <| | AUTH_REMOTE_LOGIN | AUTH_REMOTE_PASSWORD

Die Konfigurationsdatei ist auf den ersten Blick sehr umfangreich, allerdings gibt es eine exzellente Dokumentation von Exim [5]. Dort sind insbesondere die Abschnitte zu Lookups [6] (wegen LDAP) und String Expansions [7] interessant. Allgemeine Konfigurationshinweise für Exim unter Debian/Ubuntu [8] hat Justin Koivisto zusammengestellt. Den Beitrag von Wolfgang Hennerbichler zur Einrichtung der TLS-Verschlüsselung der SMTP-Verbindung [9] zusammen mit der Dokumentation von exim4-config zu diesem Thema [10] empfand ich ebenfalls hilfreich wie auch den Forumsthread, der die Verwendung von $authenticated_id und die Anpassung des received_header_text dokumentiert [11]. Die Mailingliste von exim enthält einen sehr guten Beitrag, mit ausführlicher Beispielkonfiguration, wie E-Mails via Authentifizierung über externe Mailserver verschickt [12] werden können. Wie man mehrere Smarthosts mit Exim verwendt [13], beschreibt auch Thorsten Gunkel.
Nun testen wir die Exim-Konfiguration, ob auch alles wie gewünscht funktioniert.

exim -bt paul@myserver

sollte folgendes ausgeben

R: system_aliases for paul@myserver
R: local_user_secmail for paul@myserver
R: local_user for paul@myserver
paul@myserver
  router = local_user, transport = dovecot_delivery

analog sollte ein

exim -bt paulpanzer@gmx.de

zu folgendem Resultat führen

R: ldap_uid_alias for paulpanzer@gmx.de
R: system_aliases for paul@myserver
R: local_user_secmail for paul@myserver
R: local_user for paul@myserver
paul@myserver
    <-- paulpanzer@gmx.de
  router = local_user, transport = dovecot_delivery

Im nächsten Schritt können wir auch eine SMTP-Session testen. Dazu erzeugen wir mit dem Befehl

echo -ne '\0paul@paulpanzer@gmx.de\0test' | base64

eine Zeichenfolge zur Authentifizierung, wobei paul für den Benutzer steht, paulpanzer@gmx.de für die externe Mailaddresse und test für das Passwort von paul (es besteht keine Verbindung zu den Passwörtern von paulpanzer@gmx.de). Ich erhalte hier AHBhdWxAcGF1bHBhbnplckBnbXguZGUAdGVzdA==. Wir starten mit

openssl s_client -starttls smtp -crlf -connect myserver:25

eine verschlüsselte SMTP-Session und verschicken eine Testmail an paul@myserver, indem wir folgende Befehle eingeben (alle Zeilen, die nicht mit einer Zahl beginnen)

250 HELP
ehlo client
250-myserver Hello myserver [127.0.1.1]
250-SIZE 52428800
250-PIPELINING
250-AUTH PLAIN LOGIN
250 HELP
AUTH PLAIN AHBhdWxAcGF1bHBhbnplckBnbXguZGUAdGVzdA==
235 Authentication succeeded
mail from: paulpanzer@gmx.de
250 OK
rcpt to: paul@myserver
250 Accepted
data
354 Enter message, ending with "." on a line by itself
From: Paul Panzer <paulpanzer@gmx.de>
To: Paul <paul@myserver>
Subject: Testing SMTP
This is a test body.
.
250 OK id=1Ll9N3-0001hf-6J
quit
221 myserver closing connection
closed

Wer im Detail nachlesen möchte, was die einzelnen Befehle bewirken, dem sei die Anleitung zum Testen von SMTP per Telnet von John M. Simpson [14] empfohlen. Als Resultat des Tests sollten wir im Verzeichnis /home/paul/mail/paul/maildir/INBOX/new/ eine Datei haben, die in etwa folgenden Inhalt hat

Return-path: <paulpanzer@gmx.de>
Envelope-to: paul@myserver
Delivery-date: Sat, 21 Mar 2009 23:18:09 +0100
Received: from myserver ([127.0.1.1] helo=la)
	by myserver with esmtpsa (TLS1.0:DHE_RSA_AES_256_CBC_SHA1:32)
	(Exim 4.69)
	(envelope-from <paulpanzer@gmx.de>)
	id 1Ll9Vy-0001jC-N8
	for paul@myserver; Sat, 21 Mar 2009 23:18:09 +0100
From: Paul Panzer <paulpanzer@gmx.de>
To: Paul <paul@myserver>
Subject: Testing SMTP 

This is a test body.

Fehlerquellen in der Konfiguration finden

Falls exim nicht das gewünschte Resultat liefert oder bei der Auslieferung an eine bestimmte Addresse ein Fehler auftritt, hilft es, Exim im Debug-Modus zu starten. Beispielsweise kann man eine derartige Batch-SMTP-Session über folgenden Befehl starten

sudo exim4 -C /var/lib/exim4/config.autogenerated -v -d+all -bs

Um zu testen, wie Exim intern die Mailaddressen routet, können wir mit

sudo exim -v -d-all+lookup -bt paulpanzer@gmx.de

alle zugehörigen lookups von exim verfolgen. Durch das „-all+lookup“ werden nur Informationen ausgeben, die mit lookups zu tun haben.

Umgang mit Exim

Exim besitzt eine Vielzahl an Kommandozeilenoptionen. Sich in der man-page zurechtzufinden ist daher nicht einfach, allerdings habe ich eine sehr gute Zusammenstellung einzelner Exim-Befehle [15] gefunden. Ein Befehl fehlt dabei jedoch. Mit

sudo exim -qff

kann man das erneute Auslierfern von „frozen messages“, also als unzustellbar marktierten Mails, erzwingen.

Der vierte Teil der Konfiguration beschäftigt sich mit der Einrichtung von getmail zum Abruf externer Mails via POP3/IMAP [16].