Kategorien

Rechtsklick in Firefox macht Probleme

Dass Firefox manchmal bei einem Rechtsklick willkürlich die verschiedensten Befehle ausführt, sei es nun das Anzeigen des Quelltextes, der Seiteninformationen oder das Zurückspringen zur letzten Seite, ist ein bekannter Bug. Als Workaround habe ich nun herausgefunden, dass die Mouse Gesture Redox Erweiterung Abhilfe schafft. Nach der Installation der Erweiterung konfiguriert man sie so, dass nirgends ein Häkchen gesetzt ist. Seit der Installation der Erweiterung traten bei mir die oben geschilderten Probleme nicht mehr auf.

VirtualBox Guest Additions und die Zeit

Leider musste ich mittlerweile einen Nachteil bei der Installation der Gasterweiterungen feststellen, der mich sogar dazu brachte, sie komplett zu deinstallieren: Die Gasterweiterungen synchronisieren in unregelmäßigem Abstand die Zeit des Gastsystems mit dem Hostsystem. Wenn man als Host Windows hat und als Gast ein Linux-System mit ntpd, dann ist das nicht gerade toll. Wie also wird man die Gasterweiterungen wieder los? Nach einer Anleitung im Virtualbox-Forum muss man das aufgrund des Fehlens einer uninstall Option von Hand folgendermaßen bewerkstelligen:

sudo find /etc -name "*vboxadd*" -exec rm {} \;
sudo find /etc -name "*vboxvfs*" -exec rm {} \;
sudo rm -r /usr/src/vboxadd-*
sudo rm -r /usr/src/vboxvfs-*
sudo rm /usr/sbin/vboxadd-timesync
sudo rm /lib/modules/`uname -r`/misc/vboxadd.ko
sudo rm /lib/modules/`uname -r`/misc/vboxvfs.ko

Nach einem Neustart mit

sudo reboot

sollte sich das Zeitproblem erledigt haben. Vielleicht hätte es auch einfach gereicht, die Datei /usr/sbin/vboxadd-timesync umzubenennen und stattdessen einen Link auf /dev/null zu erzeugen. Aber sicher ist sicher 😉 Wer trotz der Entfernung der Gasterweiterungen Probleme hat, sollte bei älteren Kernel-Versionen eventuell die Bootparameter entsprechend ändern.

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

Nachdem wir in den drei ersten Teilen zunächst OpenLDAP, Dovecot und Exim eingerichtet haben, geht es nun daran, getmail so einzurichten, dass Mails von einem externen Server via POP3 oder IMAP abgerufen werden und dann dem entsprechenden lokalen Benutzer zugestellt werden. Obwohl fast alles im Blog detailliert beschrieben ist, empfehle ich, die Konfigurationsdateien für das Mailsystem herunterzuladen.

Installation

Wir installieren getmail und das benötigte python-Modul durch den Befehl

sudo apt-get install getmail4 python-ldap

Konfiguration zum Mailabruf von externen Servern

Der Benutzer secmail wird für uns alle Mails abholen und an den jeweiligen Nutzer zustellen. Für diesen Zweck habe ich das Python-Skript getmail-ldap.py geschrieben. Es liest zunächst die Login-Daten aller externen Mail-Accounts aus dem LDAP-Verzeichnis und erzeugt für jeden Mailaccount eine entsprechende Konfigurationsdatei zur Verwendung mit getmail. Anschließend ruft es getmail auf und benachrichtigt im Falle einer Fehlermeldung den Administrator per E-Mail. Die folgenden Schritte führen wir unter dem Benutzer secmail durch, damit die Dateien mit der entsprechenden Berechtigung erzeugt werden. Dazu rufen wir sudo auf:

sudo -u secmail -s

Den folgenden Inhalt

#!/usr/bin/python
# File: getmail-ldap.py
try:
	import errno
	import string
	import logging
	import logging.handlers
	import ldap
	import ConfigParser
	import ldif
	import threading
	from StringIO import StringIO
	from ldap.cidict import cidict
	from os.path import os
	from subprocess import Popen,PIPE
except ImportError:
	print """Cannot find all required libraries please install them and try again"""
	raise SystemExit

config_file_location = '/home/secmail/getmail-ldap.cfg'

def pid_exists(pid):
    """Is there a process with PID pid?"""
    if pid < 0:
        return False

    exist = False
    try:
        os.kill(pid, 0)
        exist = 1
    except OSError, x:
        if x.errno != errno.ESRCH:
            raise

    return exist

def get_search_results(results):
    """Given a set of results, return a list of LDAPSearchResult
    objects.
    """
    res = []

    if type(results) == tuple and len(results) == 2 :
        (code, arr) = results
    elif type(results) == list:
        arr = results

    if len(results) == 0:
        return res

    for item in arr:
        res.append( LDAPSearchResult(item) )

    return res

class LDAPSearchResult:
    """A class to model LDAP results.
    """

    dn = ''

    def __init__(self, entry_tuple):
        """Create a new LDAPSearchResult object."""
        (dn, attrs) = entry_tuple
        if dn:
            self.dn = dn
        else:
            return

        self.attrs = cidict(attrs)

    def get_attributes(self):
        """Get a dictionary of all attributes.
        get_attributes()->{'name1':['value1','value2',...],
				'name2: [value1...]}
        """
        return self.attrs

    def set_attributes(self, attr_dict):
        """Set the list of attributes for this record.

        The format of the dictionary should be string key, list of
        string alues. e.g. {'cn': ['M Butcher','Matt Butcher']}

        set_attributes(attr_dictionary)
        """

        self.attrs = cidict(attr_dict)

    def has_attribute(self, attr_name):
        """Returns true if there is an attribute by this name in the
        record.

        has_attribute(string attr_name)->boolean
        """
        return self.attrs.has_key( attr_name )

    def get_attr_values(self, key):
        """Get a list of attribute values.
        get_attr_values(string key)->['value1','value2']
        """
        return self.attrs[key]

    def get_attr_names(self):
        """Get a list of attribute names.
        get_attr_names()->['name1','name2',...]
        """
        return self.attrs.keys()

    def get_dn(self):
        """Get the DN string for the record.
        get_dn()->string dn
        """
        return self.dn

    def pretty_print(self):
        """Create a nice string representation of this object.

        pretty_print()->string
        """
        str = "DN: " + self.dn + "\n"
        for a, v_list in self.attrs.iteritems():
            str = str + "Name: " + a + "\n"
            for v in v_list:
                str = str + "  Value: " + v + "\n"
        str = str + "========"
        return str

    def to_ldif(self):
        """Get an LDIF representation of this record.

        to_ldif()->string
        """
        out = StringIO()
        ldif_out = ldif.LDIFWriter(out)
        ldif_out.unparse(self.dn, self.attrs)
        return out.getvalue()

class RetrieveMails(threading.Thread):
	def __init__(self, getmail_binary, config_filename, config_data_dir):
		threading.Thread.__init__(self)
		self.getmail_binary, self.config_filename, self.config_data_dir = \
			getmail_binary, config_filename, config_data_dir
	def run(self):
		try:
			command = [self.getmail_binary, \
				#'--quiet', \
				'--rcfile=' + self.config_filename, \
				'--getmaildir=' + self.config_data_dir]
			self.pid_filename = self.config_filename + '.pid'
			# Check for a pidfile to see if the daemon already runs
			try:
				pid_file = file(self.pid_filename,'r')
				pid_number = pid = int(pid_file.read().strip())
				pid_file.close()
			except IOError:
				pid = None
			# Check whether process is really running
			if pid:
				pid = pid_exists(pid)
			if not pid:
				getmail_process = Popen(command, shell=False,stdout=PIPE,stderr=PIPE)
				try:
					file(self.pid_filename,'w+').write("%s\n" % getmail_process.pid)
					getmail_process.wait()
				finally:
					os.remove(self.pid_filename)
					# Zur Sicherheit die erstellte Konfigurationsdatei loeschen (Login-Daten!)
					os.remove(self.config_filename)
				stderr_output=string.join(getmail_process.stderr.readlines())
				if getmail_process.returncode <> 0 or len(stderr_output.strip())>0 :
					raise Exception, "Getmail command failed for " + " ".join(command) \
						+"\nStdErr: \n" + string.join(stderr_output.strip()) \
						+"\nStdOut: \n" + string.join(getmail_process.stdout.readlines())
			else:
				log_object.info("Command " + " ".join(command) +\
					" not executed, existing pid " + str(pid_number) + " found")
		except:
			log_object.exception("An error occured!")

class RetrieveAccount:
	account_name = None
	account_type = None
	login = None
	password = None
	server = None
	def __init__(self, account_name=None, account_type=None, server=None, login=None, password=None):
		self.account_name, self.account_type, self.login, self.password, self.server = \
			account_name, account_type, login, password, server

class GetmailConfigFile(ConfigParser.SafeConfigParser):
	output_filename = None
	def __init__(self, defaults, default_config_filename=None, output_filename=None):
		ConfigParser.SafeConfigParser.__init__(self, defaults)
		if default_config_filename is not None:
			self.read(default_config_filename)
		self.output_filename = output_filename
	def set_pop3_account(self, newRetrieveAccount):
		self.set('retriever','server',newRetrieveAccount.server)
		self.set('retriever','type',newRetrieveAccount.account_type)
		self.set('retriever','username',newRetrieveAccount.login)
		self.set('retriever','password',newRetrieveAccount.password)
		self.set('destination','arguments','("'+newRetrieveAccount.account_name+'",)')
	def write(self):
		if self.output_filename is not None:
			"""try:
				output_file = open(self.output_filename, 'wb')
			except:
				raise Exception, "Unable to open " + \
					self.output_filename + "for writing"
			finally:
				output_file.close()
			"""
			os.umask(0077)
			output_file = open(self.output_filename, 'wb')
			ConfigParser.SafeConfigParser.write(self, output_file)
		else:
			raise Exception, "No output file for configuration defined"

# Konfigurationsdatei lesen
config_object = ConfigParser.SafeConfigParser()
config_object.read(config_file_location)

# Set-up Logging
log_object = logging.getLogger("getmail-ldap")
log_object.setLevel(logging.DEBUG)

# This handler writes everything to a log file.
log_file_handler = logging.FileHandler(config_object.get('Logging','LogFile'))
log_file_formatter = logging.Formatter("%(levelname)s %(asctime)s %(funcName)s %(lineno)d %(message)s")
log_file_handler.setFormatter(log_file_formatter)
log_file_handler.setLevel(logging.DEBUG)
log_object.addHandler(log_file_handler)

# This handler emails anything that is an error or worse.
log_smtp_handler = logging.handlers.SMTPHandler(\
	config_object.get('Logging','MailServer'),\
	config_object.get('Logging','MailFrom'),\
	config_object.get('Logging','MailTo').split(','),\
	config_object.get('Logging','MailSubject'))
log_smtp_handler.setLevel(logging.ERROR)
log_smtp_handler.setFormatter(log_file_formatter)
log_object.addHandler(log_smtp_handler)

def main_call():

	## first you must open a connection to the LDAP server
	ldap_object = ldap.open(config_object.get('LDAP','LDAPServer'))
	ldap_object.simple_bind_s(\
		config_object.get('LDAP','BindDN'),\
		config_object.get('LDAP','BindPassword'))
	# searching doesn't require a bind in LDAP V3.
	# If you're using LDAP v2, set the next line appropriately
	# and do a bind as shown in the above example.
	# you can also set this to ldap.VERSION2 if you're using a v2 directory
	# you should  set the next option to ldap.VERSION2 if you're using a v2 directory
	ldap_object.protocol_version = ldap.VERSION3	

	## The next lines will also need to be changed to support your search requirements and directory
	## retrieve all attributes - again adjust to your needs - see documentation for more options

	if config_object.get('LDAP','SearchScope').upper() == "SUB":
            search_scope = ldap.SCOPE_SUBTREE
        elif config_object.get('LDAP','SearchScope').upper() == "ONE":
            search_scope = ldap.SCOPE_ONELEVEL
        else:
            search_scope = ldap.SCOPE_BASE

	ldap_result_id = ldap_object.search( \
		config_object.get('LDAP','SearchDN'), \
		search_scope,
		config_object.get('LDAP','SearchFilter'), \
		None)

	ldap_results = []

	while 1:
		result_type, result_data = ldap_object.result(ldap_result_id, 0)
		if (result_data == []):
			break
		else:
			## here you don't have to append to a list
			## you could do whatever you want with the individual entry
			## The appending to list is just for illustration.
			if result_type == ldap.RES_SEARCH_ENTRY:
				ldap_results += get_search_results(result_data)
	for ldap_result in ldap_results:
		account = RetrieveAccount( \
			# Account Name \
			ldap_result.get_attr_values(\
				config_object.get('LDAP','RelevantAttributes').split(',')[0])[0] ,\
			# Account Type \
			ldap_result.get_attr_values(\
				config_object.get('LDAP','RelevantAttributes').split(',')[1])[0],\
			# Server \
			ldap_result.get_attr_values(\
				config_object.get('LDAP','RelevantAttributes').split(',')[2])[0],\
			# Login \
			ldap_result.get_attr_values(\
				config_object.get('LDAP','RelevantAttributes').split(',')[3])[0],\
			# Password \
			ldap_result.get_attr_values(\
				config_object.get('LDAP','RelevantAttributes').split(',')[4])[0]\
		)
		config_output_filename = os.path.join(\
			config_object.get('Main','ConfigFileOutputDir'), \
			"getmail_" + \
			account.account_name + \
			".cfg")
		config_file = GetmailConfigFile(None, \
			config_object.get('Main','DefaultGetmailConfigFile'), config_output_filename)
		config_file.set_pop3_account(account)
		log_object.info("Writing Account Configuration for " + account.account_name + \
				" to file " + config_output_filename)
		config_file.write()
		RetrieveMails(\
			config_object.get('Main','GetmailBinary'), \
			config_output_filename, \
			config_object.get('Main','GetmailDir')\
		).start()
		#print config_output_filename
		#print "Name " + account.account_name
		#print "Type " + account.account_type
		#print "Server " + account.server
		#print "Login " + account.login
		#print "Password " + account.password
		#print "-----------------"
		#print ldap_result.pretty_print()

if __name__ == "__main__":
	try:
		main_call();
	except:
		log_object.exception("An error occured!")

speichern wir als /home/secmail/getmail-ldap.py und machen die Datei durch ein

chmod 750 getmail-ldap.py

ausführbar. Das Skript besitzt eine Konfigurationsdatei unter /home/secmail/getmail-ldap.cfg mit dem Inhalt

[Main]
# Path to getmail
GetmailBinary=/usr/bin/getmail
# Directory that should be used as a storage by getmail
GetmailDir=/home/secmail/getmail_data
# Read default values for getmail from this file
DefaultGetmailConfigFile=/home/secmail/getmailrc_template.cfg
# Save the final configuration files which include the LDAP details to this directory
ConfigFileOutputDir=/home/secmail/getmail_config

[Logging]
# Write messages to the following log file
LogFile=/var/log/getmail-ldap.log
# If a severe error occures a mail goes to the admin
# SMTP-Server to use for sending this error notification
MailServer=localhost
# Mail address of the sender of this error notification
MailFrom=secmail@myserver
# Recipients of this error notification
# separate multiple recipients by comma
MailTo=root@myserver
# Subject of the error notification
MailSubject=Getmail-LDAP Error

[LDAP]
# Read LDAP information from this server
LDAPServer=myserver
# Authenticate with the following DN
BindDN=uid=secmail, ou=users, o=effinger
# Authenticate with the following password
BindPassword=mysecmailpassword
# Restrict search of external mail accounts to this DN
SearchDN=ou=users, o=effinger
# Scope of search for external mail accounts
# Possible values include SUB, ONE and BASE
SearchScope=SUB
# Identify external mail accounts with the following filter
SearchFilter=(&(dcSubMailAddress=*)(objectClass=dcExternalMailAccount)(dcAccountStatus=active)(dcRetrieveType=*)(dcRetrieveLogin=*)(dcRetrievePassword=*))
# List of LDAP-Attributes used to determine the following variables
# 	1. Name for resulting getmail configuration file (must be unique)
#	2. Type for mail collection e.g. BrokenUIDLPOP3Retriever
#	3. Mail server to collect mails from
#	4. Login for mail server
# 	5. Password for mail server
# separate by comma
RelevantAttributes=dcSubMailAddress,dcRetrieveType,dcRetrieveServer,dcRetrieveLogin,dcRetrievePassword

Die Konfigurationsoptionen habe ich durch Kommentare dokumentiert. In jedem Fall muss in dieser Datei im Abschnitt [LDAP] der LDAPServer von myserver auf den jeweiligen DNS-Eintrag des OpenLDAP-Servers angepasst werden. Auch die Zeile mit BindPassword müssen wir ändern, so dass sie das secmail Passwort enthält. Da diese Datei mit dem Passwort sensible Informationen enthält, die es einem Angreifer erlauben würden, aus dem LDAP-Verzeichnis alle Login-Informationen der externen Mail-Accounts zu lesen, setzen wir die Berechtigung für die Datei so, dass nur secmail darauf zugreifen kann:

chmod 640 getmail-ldap.cfg

Anschließend erzeugen wir die referenzierte Datei /home/secmail/getmailrc_template.cfg mit dem Inhalt

[retriever]
type =
server =
username =
password = 

[destination]
type = MDA_external
path = /usr/sbin/exim4
arguments = ("user@mailhost.tld",)

[options]
# for testing do not delete mails
#delete = false
delete = true
message_log = /var/log/getmail.log
read_all = true
# do not manipulate the header
delivered_to = false
received = false

Die einzelnen Konfigurationsoptionen werden in der Dokumentation von getmail detailliert erläutert. Wichtig ist hier zu wissen, dass das Python-Skript diese Datei als Vorlage nimmt und dann in der Sektion [retriever] die Werte für type, server, username und password aus dem LDAP-Verzeichnis einträgt. In der Sektion [destination] wird der Wert arguments so abgeändert, dass die Mail an den lokalen Benutzer geht. Das Zusammenspiel von getmail und exim wird in einem Forumsbeitrag näher erläutert.
Empfehlung: Zu Beginn ist es sicherlich sinnvoll, im Abschnitt [options] den Wert von delete auf false zu setzen. So werden die Mails vom externen Server zwar heruntergeladen, aber nicht gelöscht. Wenn alles einwandfrei funktioniert, kann man hier den Wert wieder auf true setzen.
Auch hier setzen wir die Berechtigungen für die Datei entsprechend:

chmod 640 getmailrc_template.cfg

Nun erzeugen wir noch ein Verzeichnis, welches getmail benötigt und eines zum Ablegen der finalen Konfigurationsdateien mit den Berechtigungen, so dass nur secmail darauf zugreifen kann.

mkdir -m 750 /home/secmail/getmail_data /home/secmail/getmail_config

Dann erzeugen wir die Logdateien im Verzeichnis /var/log und setzen die Berechtigung so, dass auch secmail in diese Dateien schreiben kann.

sudo touch /var/log/getmail{-ldap,}.log
sudo chown root.secmail /var/log/getmail{-ldap,}.log
sudo chmod 660 /var/log/getmail{-ldap,}.log

Testen des Mailabrufs

Mit dem Aufruf des Pythonskripts durch ein

sudo -u secmail -s
/home/secmail/getmail-ldap.py

und das anschließende Inspizieren der Log-Dateien /var/log/getmail-ldap.log und /var/log/getmail.log können wir testen, ob die Mails heruntergeladen werden. Ob die Zustellung an den lokalen Benutzer geklappt hat, sieht man an neuen Dateien im jeweiligen maildir (hier: /home/paul/mail/paulpanzer@gmx.de/maildir/INBOX/new) bzw. durch Abruf der Mails mit einem Client z.B. per IMAP.

Regelmäßiges Zustellen externer Mails

Damit die Mails regelmäßig von dem externen Server abgerufen werden, richten wir einen Cron-Job ein, der alle fünf Minuten prüft, ob neue Mails vorhanden sind. Dazu führen wir als secmail User

crontab -e

aus und tragen dort die Zeile

*/5 * * * * /home/secmail/getmail-ldap.py

ein. Bei Adam Kane kann man nachlesen, was ein Cron-Job ist.

Feintuning – Logdateien mit Logrotate verwalten

Nachdem nun alles soweit eingerichtet ist, kümmern wir uns noch darum, dass die Logdateien ordentlich aufgeräumt werden. Wir erzeugen deshalb im Verzeichnis /etc/logrotate.d/ die Datei dovecot mit dem Inhalt

# Logrotate Konfiguration für dovecot
/var/log/dovecot.log {
        daily
        missingok
        rotate 14
        compress
        delaycompress
        notifempty
        create 600 root root
}
/var/log/dovecot-deliver.log {
        daily
        missingok
        rotate 14
        compress
        delaycompress
        notifempty
        create 600 root secmail
}

und ebenso die Datei getmail mit diesem Inhalt

# Logrotate Konfiguration für getmail und getmail-ldap
# siehe /home/secmail/getmail-ldap.py
/var/log/getmail.log {
        daily
        missingok
        rotate 14
        compress
        delaycompress
        notifempty
        create 660 root secmail
}
/var/log/getmail-ldap.log {
        daily
        missingok
        rotate 14
        compress
        delaycompress
        notifempty
        create 660 root secmail
}

Wir korrigieren außerdem noch einen kleinen Bug im exim-Paket, indem wir die Zeile

        create 640 Debian-exim adm

in den beiden Dateien exim-base und exim-paniclog im selben Verzeichnis durch folgende Zeile ersetzen

        create 640 Debian-exim root

Tip zum Logging des OpenLDAP-Servers von der OpenLDAP-Mailingliste: Standardmäßig wird alles in die syslog geschrieben. Wenn man der Übersichtlichkeit halber eine eigene Logdatei für OpenLDAP haben möchte, muss man OpenLDAP mitteilen, dass es beim Loggen einen eigenen Selektor (hier:local4) verwenden soll. Dazu muss die Datei /etc/default/slapd die folgende Zeile enthalten:

SLAPD_OPTIONS="-l local4"

Nun konfigurieren wir syslog so, dass es alle Informationen mit diesem Selektor in eine eigene Datei schreibt. Bei Verwendung von sysklogd ergänzen wir in der Datei /etc/syslog.conf folgende Zeile

# Log openldap to separate file
local4.*			-/var/log/slapd.log

Bei Verwendung von rsyslog erzeugen wir die Datei /etc/rsyslog.d/40-slapd.conf mit folgendem Inhalt

# Log openldap to separate file
local4.*			-/var/log/slapd.log
& ~

Außerdem legen wir eine entsprechende Datei namens /etc/logrotate.d/slapd mit dem folgenden Inhalt an.

/var/log/slapd.log {
        daily
        missingok
        rotate 14
        compress
        delaycompress
        notifempty
        create 660 root openldap
}

Damit die Änderungen Wirkung zeigen, müssen wir anfangs eine Logdatei erzeugen und anschließend Syslog und OpenLDAP neu starten bzw. die Konfiguration neu laden.

sudo touch /var/log/slapd.log
sudo chown root.openldap /var/log/slapd.log
sudo chmod 660 /var/log/slapd.log
sudo /etc/init.d/sysklogd reload
sudo /etc/init.d/slapd restart

Links zum getmail-ldap Python-Skript

Bei der Erstellung des Python-Skripts waren einige Webseiten sehr hilfreich, die deshalb hier aufgeführt werden, obowhl sie für die Einrichtung des Mailservers ohne Bedeutung sind.

Weitere Konfigurationsschritte

Die Schritte zur Einrichtung der im ersten Teil angesprochenen Komponenten Roundcube als Webmaildienst, LDAP zur Verwaltung von Addressen und Spamassassin zum Filtern von Spam-Mails sowie eine Anleitung zum Einrichten eins Mail-Clients werden aufgrund von Zeitmangel leider erst in einigen Wochen verfügbar sein.

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

Nach der Einrichtung von OpenLDAP im ersten Teil, der Anpassung und Konfiguration von dovecot im zweiten Teil 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.

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). 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 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. Dort sind insbesondere die Abschnitte zu Lookups (wegen LDAP) und String Expansions interessant. Allgemeine Konfigurationshinweise für Exim unter Debian/Ubuntu hat Justin Koivisto zusammengestellt. Den Beitrag von Wolfgang Hennerbichler zur Einrichtung der TLS-Verschlüsselung der SMTP-Verbindung zusammen mit der Dokumentation von exim4-config zu diesem Thema empfand ich ebenfalls hilfreich wie auch den Forumsthread, der die Verwendung von $authenticated_id und die Anpassung des received_header_text dokumentiert. Die Mailingliste von exim enthält einen sehr guten Beitrag, mit ausführlicher Beispielkonfiguration, wie E-Mails via Authentifizierung über externe Mailserver verschickt werden können. Wie man mehrere Smarthosts mit Exim verwendt, 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 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 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.

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

Nachdem die Grundvorraussetzungen im ersten Teil geschaffen wurden, wenden wir uns in diesem Teil der Anpassung, Installation und Konfiguration von dovecot zu. Obwohl fast alles im Blog detailliert beschrieben ist, empfehle ich, die Konfigurationsdateien für das Mailsystem herunterzuladen.

Installation von Dovecot

Nach einem

sudo apt-get install dovecot-common dovecot-imapd dovecot-pop3d

sind die notwendigen Dovecot-Pakete installiert.

Anpassung der Installation

Dovecot verwendet zur Zustellung von Mails den Dovecot Local Delivery Agent (LDA). Damit dieser die Mails in allen Verzeichnissen der Benutzer ablegen kann, benötigt er root-Rechte, die wir über das Setuid-Bit vergeben. Zum Mailversand setzen wir jedoch exim als  Message Transfer Agent (MTA) ein, so dass Mails nie direkt via Dovecot-LDA, sondern immer indirekt über exim zugestellt werden. Da deshalb nur exim den Dovecot LDA aufrufen muss, wäre es sicherheitstechnisch bedenklich, könnte jeder Benutzer den Dovecot LDA mit Setuid-Bit aufrufen (eine Sicherheitslücke im Dovecot LDA würde dann sofort das gesamte System kompromittieren). Deshalb richten wir einen speziellen Benutzer namens secmail ein, der diese Rechte besitzt und dessen sich exim dann bedienen soll.

sudo adduser --system --group --disabled-login --shell /bin/false --home /home/secmail secmail

Anschließend müssen wir für unsere Konfiguration das setuid-Bit für Dovecots LDA setzen,  damit dieser zur Zustellung jeweils auf die jeweilige Benutzer-ID wechseln kann. Durch den Aufruf von dpkg-statoverride werden die Berechtigungen auch bei Paketaktualisierungen beibehalten, obwohl der Einsatz auch einige Nachteile mit sich bringt.

sudo dpkg-statoverride --update --add root secmail 4710 /usr/lib/dovecot/deliver

Konfiguration von dovecot

Zunächst kopieren wir die im ersten Teil der Anleitung erzeugten Dateien des Client-Zertifikats für dovecot in das Konfigurationsverzeichnis und passen die Berechtigungen an.

sudo cp /etc/ssl/easy-rsa/keys/dovecot-client.key /etc/ssl/easy-rsa/keys/dovecot-client.crt /etc/dovecot
sudo chown root.dovecot /etc/dovecot/dovecot-client.{key,crt}
sudo chmod 640 /etc/dovecot/dovecot-client.key

Die Konfiguration von dovecot ist im Vergleich zu anderen Mailservern wie qmail, courier-imap oder cyrus einfach und gut dokumentiert. Meine um Kommentare und Leerzeilen verkürzte Version sieht so aus:

protocols = imap imaps managesieve
log_path = /var/log/dovecot.log
log_timestamp = "%Y-%m-%d %H:%M:%S "
ssl_cert_file = /etc/ssl/certs/myserver.crt
ssl_key_file = /etc/ssl/private/myserver.key
ssl_ca_file = /etc/ssl/certs/ca.crt
ssl_verify_client_cert = yes
login_user = dovecot
login_greeting = Ready.
mail_privileged_group =
protocol imap {
  login_executable = /usr/lib/dovecot/imap-login
  mail_executable = /usr/lib/dovecot/imap
}
protocol pop3 {
  pop3_uidl_format = %08Xu%08Xv
}
protocol managesieve {
  login_executable = /usr/lib/dovecot/managesieve-login
  mail_executable = /usr/lib/dovecot/managesieve
  sieve=~/.dovecot.sieve
  sieve_storage=~/sieve
}
protocol lda {
  postmaster_address = paul@myserver
  mail_plugins = cmusieve
  mail_plugin_dir = /usr/lib/dovecot/modules/lda
  log_path = /var/log/dovecot-deliver.log
  auth_socket_path = /var/run/dovecot/auth-master
}
auth_executable = /usr/lib/dovecot/dovecot-auth
auth_username_chars = abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890.-_@
auth default {
  mechanisms = plain
  passdb ldap {
    args = /etc/dovecot/dovecot-ldap-passdb.conf
  }
  userdb ldap {
    args = /etc/dovecot/dovecot-ldap-userdb.conf
  }
  user = root
  socket listen {
    master {
      path = /var/run/dovecot/auth-master
      mode = 0660
      group = secmail
    }
  }
}
dict {
}
plugin {
}

Eine entsprechende Datei kann man einfach durch ein

cat /etc/dovecot/dovecot.conf | egrep -v '(^\t* *#|^\t* *$)'

erzeugen. Hier ist der Inhalt der referenzierten Dateien dovecot-ldap-passdb.conf und dovecot-ldap-userdb.conf.

dovecot-ldap-passdb.conf:

hosts = myserver
tls = yes
tls_ca_cert_file = /etc/ssl/certs/ca.crt
tls_cert_file = /etc/dovecot/dovecot-client.crt
tls_key_file = /etc/dovecot/dovecot-client.key
tls_require_cert = demand
auth_bind = yes
auth_bind_userdn = uid=%Ln,ou=users,o=effinger
ldap_version = 3
base = uid=%Ln,ou=users,o=effinger

dovecot-ldap-userdb.conf:

hosts = myserver
tls = yes
tls_ca_cert_file = /etc/ssl/certs/ca.crt
tls_cert_file = /etc/dovecot/dovecot-client.crt
tls_key_file = /etc/dovecot/dovecot-client.key
tls_require_cert = demand
ldap_version = 3
base = uid=%Ln,ou=users,o=effinger
scope = subtree
user_attrs = dcMailMessageStore=home=%$/%Ld,uidNumber=uid,gidNumber=gid,=mail=maildir:~/maildir:INBOX=~/maildir/INBOX
user_filter = (|(mail=%d)(&(dcSubMailAddress=%d)(dcAccountStatus=active)))

Nach einem

sudo /etc/init.d/dovecot restart

sollte man den Login testen. Dazu kann man die Anleitung zum Testen von IMAP mit telnet von A. P. Lawrence verwenden. Das sieht dann beispielsweise so aus:

$ telnet localhost 143
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
* OK Ready.
as1 login paul@paul mypassword
as1 OK Logged in.
as2 logout
* BYE Logging out
as2 OK Logout completed.
Connection closed by foreign host.

Wichtig ist dabei, beide E-Mail-Addressen zu überprüfen (lokal und extern). Im Beispiel hier muss man also die logins paul@paul sowie paul@paulpanzer@gmx.de testen.

Zum Schluss möchte ich noch auf einige Seiten hinweisen, die bei der Konfiguration von dovecot hilfreich für mich waren

In Teil 3 befassen wir uns mit der Einrichtung von Exim als MTA (Message Transfer Agent).