Achtung: Dieser Beitrag ist schon älter und teilweise fehlerhaft. Unter dem Titel ISP Email Server mit Exim 4, Dovecot, MySQL und SpamAssassin auf Debian GNU/Linux etch habe ich eine ausführlichere und ausgereiftere Fassung veröffentlicht.
Ich möchte hier kurz meine Mailserver Konfiguration vorstellen, da ich es für gut möglich halte, dass diese Information auch für andere Betreiber von Mailservern von Intresse sein könnte.
Für den Betrieb meines Mailservers setze ich auf exim4 und Dovecot. Das ganze läuft unter Debian etch.
Unter Debian lassen sich die benötigten Pakete einfach mit folgendem Kommando installieren:
aptitude install dovecot-common dovecot-imapd dovecot-pop3d exim4-daemon-heavy mysql-server-5.0 pyzor razor spamassassin spamc dcc-client
Zu Dovecot ist eigentlich nichts weiter zu sagen, ausser das er wunderbar seinen Dienst verrichtet.
Exim greift auf eine MySQL Datenbank für die Überprüfung der Benutzer zurück, bindet SpamAssassin mit diversen Plugins ein, läuft sowohl auf IPv4 als auch IPv6 (via SixXS) und nimmt Mails auch via SMA/RFC2476 entgegen - natürlich alles auch über TLS.
Ein weiteres gute Beispiel für eine Exim4 Konfiguration gibt es hier.
Hier folgt jetzt meine kommentierte Exim 4 Konfiguration. Bei Fragen bitte die Kommentarfunktion des Blogs nutzen.
####################################### # MACROS #######################################
Zunächst werden einige Macros definiert um die gesamte Konfiguration übersichtlicher zu gestallten.
Am Anfang stehe die Zugangsdaten zum MySQL-Server und die Tabellennamen.
MYSQL_SERVER=localhost MYSQL_USER=user MYSQL_PASSWORD=pass MYSQL_DB=db MYSQL_EMAILTABLE=exim_emailtable MYSQL_DOMAINTABLE=exim_domains MYSQL_WHITETABLE=exim_whitelist MYSQL_BLACKTABLE=exim_blacklist
Dann folgt die Mailman Konfiguration. Dies wird weiter unten nochmal erläutert.
MAILMAN_HOME=/var/lib/mailman MAILMAN_WRAP=MAILMAN_HOME/mail/wrapper MAILMAN_UID=list MAILMAM_GID=list
Hier folgen die aktuelle verwendeten Blacklists, genauer: DNSRBL. Leider muss diese Liste in letzter Zeit oft angepasst werden da die BLs oft unter Beschuss stehen oder den Dienst einstellen.
BL_WARN=zen.spamhaus.org:ix.dnsbl.manitu.net BL_DENY=zen.spamhaus.org:ix.dnsbl.manitu.net
Nun zum komplizierten Teil: Den SQL Querys. Ich werde nicht auf jede einzelne Query eingehen, aber mit ein paar SQL Kentnissen sollten sie kein Problem sein.
MYSQL_Q_ISAWAY=SELECT domain FROM MYSQL_EMAILTABLE WHERE domain='${quote_mysql:$domain}' AND local_part='${quote_mysql:$local_part}' AND is_away='yes'
MYSQL_Q_AWAYTEXT=SELECT away_text FROM MYSQL_EMAILTABLE WHERE domain='${quote_mysql:$domain}' AND local_part='${quote_mysql:$local_part}'
MYSQL_Q_FORWARD=SELECT forward FROM MYSQL_EMAILTABLE WHERE domain='${quote_mysql:$domain}' AND local_part='${quote_mysql:$local_part}' AND forward != '' AND is_enabled = 'yes'
MYSQL_Q_CC=SELECT cc FROM MYSQL_EMAILTABLE WHERE domain='${quote_mysql:$domain}' AND local_part='${quote_mysql:$local_part}' AND is_enabled = 'yes'
MYSQL_Q_LOCAL=SELECT domain FROM MYSQL_EMAILTABLE WHERE domain='${quote_mysql:$domain}' AND local_part='${quote_mysql:$local_part}' AND forward = '' AND is_enabled = 'yes'
MYSQL_Q_WCLOCAL=SELECT domain FROM MYSQL_EMAILTABLE WHERE domain='${quote_mysql:$domain}' AND local_part='*' AND forward != '' AND is_enabled = 'yes'
MYSQL_Q_WCLOCFW=SELECT forward FROM MYSQL_EMAILTABLE WHERE domain='${quote_mysql:$domain}' AND local_part='*' AND forward != '' AND is_enabled = 'yes'
MYSQL_Q_DISABLED=SELECT domain FROM MYSQL_EMAILTABLE WHERE domain='${quote_mysql:$domain}' AND local_part='${quote_mysql:$local_part}' AND is_enabled = 'no'
MYSQL_Q_LDOMAIN=SELECT DISTINCT domain FROM MYSQL_DOMAINTABLE WHERE domain='$domain'
MYSQL_Q_RDOMAIN=SELECT DISTINCT domain FROM MYSQL_DOMAINTABLE WHERE domain='$domain'
MYSQL_Q_BOXPATH=SELECT CONCAT(domain,'/',local_part) AS boxpath FROM MYSQL_EMAILTABLE WHERE domain='${quote_mysql:$domain}' AND local_part='${quote_mysql:$local_part}'
MYSQL_Q_SPAMC=SELECT domain FROM MYSQL_EMAILTABLE WHERE domain='${quote_mysql:$domain}' AND local_part='${quote_mysql:$local_part}' AND spam_check='yes'
MYSQL_Q_SPAMPURGE=SELECT domain FROM MYSQL_EMAILTABLE WHERE domain='${quote_mysql:$domain}' AND local_part='${quote_mysql:$local_part}' AND spam_purge='yes'
MYSQL_Q_AUTHPLAIN=SELECT if(count(*), "1", "0") FROM MYSQL_EMAILTABLE WHERE CONCAT(local_part,'@',domain)='${quote_mysql:$2}' AND pwclear='${quote_mysql:$3}'
MYSQL_Q_AUTHLOGIN=SELECT if(count(*), "1", "0") FROM MYSQL_EMAILTABLE WHERE CONCAT(local_part,'@',domain)='${quote_mysql:$1}' AND pwclear='${quote_mysql:$2}'
MYSQL_Q_AUTHCRAM=SELECT pwclear FROM MYSQL_EMAILTABLE WHERE CONCAT(local_part,'@',domain)='$1'
MYSQL_Q_WHITELIST=SELECT DISTINCT MYSQL_WHITETABLE.address FROM MYSQL_WHITETABLE WHERE '${quote_mysql:$sender_address}' LIKE MYSQL_WHITETABLE.address
MYSQL_Q_BLACKLIST=SELECT DISTINCT MYSQL_BLACKTABLE.address FROM MYSQL_BLACKTABLE WHERE '${quote_mysql:$sender_address}' LIKE MYSQL_BLACKTABLE.address
# 'hide' damit diese Optionen nicht auf der Kommandozeile angezeigt werden.
hide mysql_servers = "MYSQL_SERVER/MYSQL_DB/MYSQL_USER/MYSQL_PASSWORD"
#######################################
# BASIC
#######################################
# Der primäre Hostname, sollte identisch mit dem RDNS Namen der IP sein.
primary_hostname = mail.gauner.org
# Welche Domains sind lokal
domainlist local_domains = localhost:gauner.org:lists.gauner.org:mysql;MYSQL_Q_LDOMAIN
domainlist relay_to_domains = mysql;MYSQL_Q_RDOMAIN
hostlist relay_from_hosts = 127.0.0.1
# Definiert zu welchen Zeiten der SMTP Sitzung welche ACLs ausgeführt werden.
# Nachdem das RCPT Kommando gesendet wurde
acl_smtp_rcpt = acl_check_rcpt
# Nachdem das MAIL FROM Kommando gesendet wurd
acl_smtp_mail = acl_check_from
qualify_domain = gauner.org
never_users = root
host_lookup = *
# Trusted Users wird für SpamAssasin benötigt.
trusted_users = mail
rfc1413_hosts = *
rfc1413_query_timeout = 15s
check_spool_space = 50M
check_log_space = 20M
return_size_limit = 20k
message_size_limit = 20M
ignore_bounce_errors_after = 2d
timeout_frozen_after = 7d
deliver_queue_load_max = 8
queue_only_load = 10
remote_max_parallel = 15
# TLS Konfiguration
tls_certificate = /etc/exim4/exim.cert
tls_privatekey = /etc/exim4/exim.key
tls_advertise_hosts = *
local_interfaces = < ; 127.0.0.1 ; aaa.bbb.ccc.ddd ;
2001:cafe:dead:beef::2
# Listen for SMTP on Port 25 and for SMA on Port 587
daemon_smtp_port = 25 : 587
#######################################
# ACL
#######################################
begin acl
acl_check_from:
# drop connections on the SMA Port that did not auth
drop condition = ${if={$interface_port}{587} {1}{0}}
!authenticated = *
# accept everything else (policy checks are in rcpt acl)
accept
acl_check_rcpt:
accept hosts = :
deny domains = +local_domains
local_parts = ^[.] : ^.*[@%!/|]
deny domains = !+local_domains
local_parts = ^[./|] : ^.*[@%!] : ^.*/../
accept local_parts = postmaster
domains = +local_domains
require verify = sender
accept authenticated = *
# Add a warning header if the sending host is in theses
# DNSBLs but accept the message
# see http://www.exim.org/howto/rbl.html
warn message = X-blacklisted-at: $dnslist_domain
dnslists = BL_WARN
# Reject messages from senders listed in these DNSBLs
deny dnslists = BL_DENY
# Consult "greylistd" to obtain greylisting status for this
# particulat peer/sender/recipient triplet.
#
# We do not greylist messages with a NULL sender,
# because sender callout verification would break (and we
# mitght not be able to send mail to a host that performs
# callouts).
#
defer
message = $sender_host_address is not yet authorized to
deliver mail from <$sender_address> to < $local_part@$domain>.
Please try later.
log_message = greylisted.
domains = +local_domains : +relay_to_domains
!senders = : postmaster@*
!hosts = : +relay_from_hosts :
${if exists {/etc/greylistd/whitelist-hosts}
{net-lsearch;/etc/greylistd/whitelist-hosts}{}} :
${if exists {/var/lib/greylistd/whitelist-hosts}
{net-lsearch;/var/lib/greylistd/whitelist-hosts}{}}
set acl_m9 = $sender_host_address $sender_address $local_part@$domain
set acl_m9 = ${readsocket{/var/run/greylistd/socket}{$acl_m9}{5s}{}{}}
condition = ${if eq {$acl_m9}{grey}{true}{false}}
accept domains = +local_domains
endpass
verify = recipient
accept domains = +relay_to_domains
endpass
verify = recipient
accept hosts = +relay_from_hosts
deny message = relay not permitted
#######################################
# ROUTERS
#######################################
# ORDER MATTERS!
#######################################
# Die Router. Hier ist es wichtig darauf zu achten in welcher
# Reihenfolge die Einträge angegeben werden, da eine Mail
# von oben nach unten an jeden Router übergeben wird
# bis sie von einem akzeptiert wird.
begin routers
# In der Datenbank deaktivierte Adressen werden gleich zu beginn rausgeworfen.
fail_router:
driver = redirect
domains = ${lookup mysql {MYSQL_Q_DISABLED}{$value}}
data = ":fail:"
allow_fail
# Hier werden Mailman Adressen behandelt
mailman_aliases:
driver = redirect
allow_fail
allow_defer
data = ${lookup{$local_part}lsearch{/etc/aliases.mailman}}
file_transport = address_file
pipe_transport = address_pipe
domains = lists.gauner.org
user = list
# DnsLookup sind externe Nachrichten, d.h. Mails von diesem System an andere.
dnslookup:
driver = dnslookup
domains = ! +local_domains
transport = remote_smtp
ignore_target_hosts = 0.0.0.0 : 127.0.0.0/8
no_more
blacklist_router:
driver = manualroute
senders = ${lookup mysql {MYSQL_Q_BLACKLIST}{$value}}
condition = "${if !def:h_X-Spam-Flag: {1}{0}}"
headers_add = X-Spam-Flag: YES
route_list = * localhost
self = pass
# System Aliase
system_aliases:
driver = redirect
allow_fail
allow_defer
data = ${lookup{$local_part}lsearch{/etc/aliases}}
file_transport = address_file
pipe_transport = address_pipe
# SpamAssassin Integration (Scannen und Markieren)
spamcheck_director:
driver = manualroute
domains = ${lookup mysql {MYSQL_Q_SPAMC}{$value}}
senders = ! ${lookup mysql {MYSQL_Q_WHITELIST}{$value}}
condition = ${if and {
{!eq {$received_protocol}{spam-scanned}}
{!eq {$received_protocol}{local}}
} {1}{0}}
headers_remove = X-Spam-Flag
route_list = "* localhost byname"
transport = spamcheck
verify = false
# SpamAssassin Integration (Spam löschen)
spampurge_director:
driver = manualroute
domains = ${lookup mysql {MYSQL_Q_SPAMPURGE}{$value}}
condition = "${if eq{$h_X-Spam-Flag:}{YES} {1}{0}}"
route_list = "* localhost byname"
transport = devnull_transport
verify = false
vacation_director:
driver = accept
domains = ${lookup mysql {MYSQL_Q_ISAWAY}{$value}}
transport = vacation_autoreply
unseen
virtual_cc_director:
driver = redirect
data = ${lookup mysql {MYSQL_Q_CC}{$value}}
unseen
virtual_forward_director:
driver = redirect
data = ${lookup mysql {MYSQL_Q_FORWARD}{$value}}
# Lokale Zustellung für Benutzer aus der Datenbank
virtual_local_mailbox:
driver = accept
domains = ${lookup mysql {MYSQL_Q_LOCAL}{$value}}
transport = virtual_local_md_delivery
virtual_wclocal_redirect:
driver = redirect
domains = ${lookup mysql {MYSQL_Q_WCLOCAL}{$value}}
data = ${lookup mysql {MYSQL_Q_WCLOCFW}{$value}}
local_user:
debug_print = "R: local_user for $local_part@$domain"
driver = accept
domains = +local_domains
check_local_user
local_parts = ! root
transport = local_delivery
#######################################
# TRANSPORTS
#######################################
# ORDER DOES NOT MATTER
#######################################
begin transports
# Remote Deliveries
remote_smtp:
driver = smtp
# Use Interface aaa.bbb.ccc.ddd for Outgoing Communiction
interface = aaa.bbb.ccc.ddd
devnull_delivery:
driver = appendfile
file = /dev/null
group = mail
address_pipe:
driver = pipe
return_output
address_file:
driver = appendfile
delivery_date_add
envelope_to_add
return_path_add
address_directory:
driver = appendfile
#no_from_hack
message_prefix = ""
message_suffix = ""
maildir_format
address_reply:
driver = autoreply
# SpamAssassin Integration
spamcheck:
driver = pipe
command = /usr/sbin/exim4 -oMr spam-scanned -bS
use_bsmtp = true
transport_filter = "/usr/bin/spamc -u $local_part@$domain"
home_directory = "/tmp"
current_directory = "/tmp"
user = mail
group = mail
log_output = true
return_fail_output = true
return_path_add = false
message_prefix =
message_suffix =
local_delivery:
driver = appendfile
directory = /home/users/${local_part}/Maildir
delivery_date_add
envelope_to_add
return_path_add
maildir_format
# Lokale Zustellung für Benutzer aus der Datenbank
virtual_local_md_delivery:
driver = appendfile
directory = /home/mail/${lookup mysql {MYSQL_Q_BOXPATH}{$value}}/Maildir
maildir_format
user = mail
group = mail
mode = 0660
directory_mode = 0770
check_string = ""
message_prefix = ""
message_suffix = ""
vacation_autoreply:
driver = autoreply
to = ${sender_address}
from = "vacation@${domain}"
subject = "Ihre Nachricht an ${local_part}@${domain}"
text = ${lookup mysql {MYSQL_Q_AWAYTEXT}{$value}}
disabled_bounce:
driver = autoreply
from = ${local_part}@${domain}
to = ${sender_address}
user = mail
subject = "Re $h_Subject:"
text = "Your message to ${local_part}@${domain} was rejected due to an
disabled account. Please try again later."
devnull_transport:
driver = appendfile
file = /dev/null
user = mail
#######################################
# RETRY
#######################################
begin retry
* * F,2h,15m; G,16h,1h,1.5; F,4d,6h
#######################################
# REWRITE
#######################################
begin rewrite
*@gauner.org ${lookup{$1}lsearch{/etc/email-addresses}
{$value}fail} frFs
#######################################
# AUTHENTICATORS
#######################################
begin authenticators
plain:
driver = plaintext
public_name = PLAIN
server_condition = ${lookup mysql{MYSQL_Q_AUTHPLAIN}}
server_set_id = $2
login:
driver = plaintext
public_name = LOGIN
server_prompts = "Username:: : Password::"
server_condition = ${lookup mysql{MYSQL_Q_AUTHLOGIN}}
server_set_id = $1
cram:
driver = cram_md5
public_name = CRAM-MD5
server_secret = ${lookup mysql{MYSQL_Q_AUTHCRAM}{$value}fail}
server_set_id = $1
Die passenden Datenbankschemas für MySQL:
-- ----------------------------------------------------------
-- Tabellenstruktur für Tabelle `exim_emailtable`
--
CREATE TABLE `exim_emailtable` (
`id` int(9) NOT NULL auto_increment,
`local_part` varchar(255) NOT NULL default '',
`domain` varchar(255) NOT NULL default '',
`forward` varchar(255) default NULL,
`cc` varchar(255) default NULL,
`name` varchar(255) NOT NULL default '',
`pwclear` varchar(255) NOT NULL default '',
`pwcrypt` varchar(255) NOT NULL default '',
`is_away` enum('yes','no') NOT NULL default 'no',
`away_text` text,
`spam_check` enum('yes','no') NOT NULL default 'no',
`spam_purge` enum('yes','no') NOT NULL default 'no',
`virus_check` enum('yes','no') NOT NULL default 'no',
`is_enabled` enum('yes','no') NOT NULL default 'yes',
`customer_id` int(9) NOT NULL default '0',
`created_at` int(16) NOT NULL default '0',
`updated_at` int(16) NOT NULL default '0',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 COMMENT='Alle virtuellen Mail-Accounts' AUTO_INCREMENT=1;
--
-- Tabellenstruktur für Tabelle `exim_domains`
--
CREATE TABLE `exim_domains` (
`id` int(9) NOT NULL auto_increment,
`domain` varchar(255) NOT NULL default '',
`is_enabled` enum('yes','no') NOT NULL default 'no',
`customer_id` int(9) NOT NULL default '0',
`created_at` int(16) NOT NULL default '0',
`updated_at` int(16) NOT NULL default '0',
PRIMARY KEY (`id`),
UNIQUE KEY `domain` (`domain`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 COMMENT='Tabelle aller von exim erkannten Domains' AUTO_INCREMENT=1;
wie konfigurierst du die user? hast du irgend eine spezielle GUI??
danke, mfg
stefan
Ja, ich habe etwas selbst geschriebenes für die Verwaltung der Benutzer.
servus,
kann es sein das die table whitelist / blacklist nicht angegeben ist? wie sehen die denn aus?
lg flo
Ja, das stimmt. Die Tabellen habe ich nicht angegeben weil ich sie eigentlich gar nicht verwende. Das ist jeweils nur eine Spalte vom Typ varchar und dem Namen address. Das wars.
Hi,
danke für die sehr gute Konfiguration, allerdings stört sich exim an default NULL in der Tabelle exim_emailtable für die Felder cc und forward. Wenn in den Feldern NULL erscheint ist nach virtual_cc_director schon Schluß. Ich hab die default Werte deshalb auf ” gesetzt.