В этой статье рассматривается расширенная настройка Exim, Dovecot и OpenLDAP для совместной работы на основе моего опыта с этими приложениями. Быть может, кто-то найдет для себя что-либо интересное и новое — в этом и была цель написания очередного howto на данную тему.
Почему Exim и OpenLDAP, а не Postfix и MySQL, например? Postfix отлично работает «из коробки», но если нужно что-то неординарное, то очень скоро Postfix превращается в неповоротливого монстра, обвешанного перл-скриптами, Exim же обладает чудовищным по силе мета-языком конфигурации и позволяет обойтись без сторонних скриптов и костылей. MySQL я посчитал избыточным для моих задач и заменил стандартным OpenLDAP, тем более для работы адресной книги используется LDAP. Dovecot очень шустрый и легкий в настройке плюс отлично интегрируется как с Exim, так и с OpenLDAP.
Итак, устанавливаем необходимый софт, тут все стандартно (apt-get, yum и тд). Я использовал Gentoo, поэтому emerge openldap dovecot exim (exim и dovecot должны иметь поддержку ldap).
Используемые USE флаги при сборке:
Первым в очереди будет OpenLDAP, в его базе будут храниться все почтовые аккаунты, группы и альясы, также OpenLDAP будет использоваться в качестве адресной книги для почтовых клиентов. Для простоты и удобства я не использую slapd-config, а храню все настройки в текстовом slapd.conf.
Так как стандартный OpenLDAP не имеет в наличии подходящей схемы для работы с почтой, то я использовал свою модифицированную версию phamm.schema и phamm-vacation.schema.
Опускаю первичную настройку, создание basedn dc=domain,dc=com и ssl сертификатов для OpenLDAP, поскольку тут все стандартно.
Обязательно настраиваем репликацию для OpenLDAP (опять же тут все стандартно, поэтому опускаю детали). Единственно, что мне пришлось собирать OpenLDAP руками для поддержки sssvlv (Server Side Sorting and Virtual List View) в адресной книге (./configure --enable-ipv6=no --enable-syncprov=yes --enable-sssvlv=yes --with-tls=yes).
Также для корректной работы sssvlv в аутлюке, пришлось патчить исходники OpenLDAP.
Второй сервер будет работать как адресная книга в режиме только для чтения.
Для администрирования LDAP базы я использую phpldapadmin, так как он легкий, удобный и имеет поддержку XML шаблонов, что позволяет гибко настраивать необходимые шаблоны для создания аккаунтов. К сожалению, проект давно заброшен автором и больше не развивается.
Например, пример шаблона для создания почтового аккаунта.
При создании я использую следующую схему LDAP базы:
ou=people,dc=domain,dc=com — контейнер для хранения почтовых аккаунтов;
ou=groups,dc=domain,dc=com — контейнер для хранения posix групп;
ou=services,dc=domain,dc=com — контейнер для хранения аккаунтов системных сервисов (posix аккаунты);
ou=aliases,dc=domain,dc=com — контейнер для хранения почтовых альясов.
При создании почтового аккаунта используются множество атрибутов модифицированной мною phamm схемы, применение которых выходит за рамки это статьи (например, createMaildir или Backup). Отмечу только те, которые используются в фильтрах поиска.
accountActive = TRUE|FALSE — позволяет временно включать/отключать аккаунт или альяс;
vacationInfo — содержит текст Out of Office сообщения;
vacationActive — позволяет включать/отключать OoO сообщение;
quota — тут и так понятно (например: quota = 4G)
Итак, OpenLDAP настроен и запущен, репликация работает и при помощи phpldapadmin создан первый тестовый аккаунт ipupkin c адресом почты ipupkin@domain.com. В дальнейшем будет использоваться только один домен domain.com, поэтому создаем в нашем контейнере ou=groups posix группу domain и добавляем ipupkin в эту группу.
В данном примере все сервисы работают на одном линукс сервере и логично использовать лдап базу для идентификации пользователя в системе, поэтому перекладываем это процедуру на плечи Name Service Switch (nss) или System Security Services Daemon (sssd). Также плюсом данного решения является легкая адаптация с Samba доменом, при необходимости.
Предварительно нужно убедиться что в системе установлен пакет nss_ldap (или libnss-ldapd).
В /etc/nsswitch.conf меняем строки с compat на ldap (или winbind в случае Samba домена):
passwd: files ldap
shadow: files ldap
group: files ldap
Создаем файл /etc/ldap.conf следующего содержания:
Проверяем, что nss работает и создана домашная директория (mkdir -m 700 /home ipupkin && chown ipupkin:domain /home/ipupkin).
>id ipupkin
uid=1057(ipupkin) gid=1000(domain) groups=1000(domain)
> ls -ld /home/ipupkin
drwx------ 3 ipupkin domain 4096 Jun 22 15:50 /home/ipupkin
Теперь, когда пользователь ipupkin распознается системой, нужно чтобы ipupkin смог получать и отсылать почту.
При настройке Dovecot я удалил все вложенные монструозные дефолтные конфиги и для удобства создал только два — dovecot.conf и dovecot-ldap.conf.
Проверяем что ошибок нет (dovecot -a) и запускаем dovecot.
Основной целью при настройке Exim был максимальный отказ от любых скриптов и реализация всего функционала только средствами Exim. Только как исключение используются дебиановский greylistd на питоне и перловый amavisd-new.
При настройке Exim также будут использоваться два файла — основной exim.conf и acl_smtp для ACL правил.
Также опционально в конфиге присутствует роутер и транспорт для mailman.
Орудие главного калибра Exim — это Access Control Lists.
Собственно, вся статья затевалась ради одной стройки в секции smtp_rcpt.
Вот, собственно, и все.
Запускаем Exim, отсылаем письмо ipupkin-у, смотрим логи…
Почему Exim и OpenLDAP, а не Postfix и MySQL, например? Postfix отлично работает «из коробки», но если нужно что-то неординарное, то очень скоро Postfix превращается в неповоротливого монстра, обвешанного перл-скриптами, Exim же обладает чудовищным по силе мета-языком конфигурации и позволяет обойтись без сторонних скриптов и костылей. MySQL я посчитал избыточным для моих задач и заменил стандартным OpenLDAP, тем более для работы адресной книги используется LDAP. Dovecot очень шустрый и легкий в настройке плюс отлично интегрируется как с Exim, так и с OpenLDAP.
Итак, устанавливаем необходимый софт, тут все стандартно (apt-get, yum и тд). Я использовал Gentoo, поэтому emerge openldap dovecot exim (exim и dovecot должны иметь поддержку ldap).
Используемые USE флаги при сборке:
net-nds/openldap-2.4.40-r3::x-overlay USE="berkdb crypt gnutls overlays samba sasl ssl syslog
mail-mta/exim-4.85::gentoo USE="dkim dnsdb dovecot-sasl dsn exiscan-acl gnutls ldap lmtp maildir pam pkcs11 prdr spf ssl syslog
net-mail/dovecot-2.2.18::gentoo USE="bzip2 caps ldap maildir managesieve pam sieve ssl zlib
Первым в очереди будет OpenLDAP, в его базе будут храниться все почтовые аккаунты, группы и альясы, также OpenLDAP будет использоваться в качестве адресной книги для почтовых клиентов. Для простоты и удобства я не использую slapd-config, а храню все настройки в текстовом slapd.conf.
Так как стандартный OpenLDAP не имеет в наличии подходящей схемы для работы с почтой, то я использовал свою модифицированную версию phamm.schema и phamm-vacation.schema.
Настройка OpenLDAP
Опускаю первичную настройку, создание basedn dc=domain,dc=com и ssl сертификатов для OpenLDAP, поскольку тут все стандартно.
Конфиг slapd.conf
include /etc/openldap/schema/core.schema
include /etc/openldap/schema/cosine.schema
include /etc/openldap/schema/corba.schema
include /etc/openldap/schema/inetorgperson.schema
include /etc/openldap/schema/nis.schema
include /etc/openldap/schema/misc.schema
# подключаем нужные схемы
include /etc/openldap/schema/phamm.schema
include /etc/openldap/schema/phamm-vacation.schema
pidfile /run/openldap/slapd.pid
argsfile /run/openldap/slapd.args
# Указываем путь к сертификатам для работы через TLS
TLSCACertificateFile /etc/openldap/ssl/cacert.pem
TLSCertificateFile /etc/openldap/ssl/newcert.pem
TLSCertificateKeyFile /etc/openldap/ssl/newkey.pem
TLSProtocolMin 3.1
TLSVerifyClient allow
database bdb # Используется стандартная bdb база, небыстрая, но надежная.
cachesize 100000
suffix "dc=domain,dc=com"
rootdn "uid=manager,dc=domain,dc=com" # Учетка админа
rootpw ****
directory /var/lib/openldap-data
checkpoint 32 30
idletimeout 120
writetimeout 120
loglevel none
overlay syncprov # Используется модуль репликации syncprov
syncprov-checkpoint 100 10
syncprov-sessionlog 100
# Определяем индексы
index uid,accountActive,vacationActive,createMaildir eq
index cn,givenName,sn,mail pres,eq,sub
index uidNumber,gidNumber,memberUid eq
index entryCSN,entryUUID eq
index objectClass,member,uniqueMember eq
# Определяем права доступа к базе
# Учетка, используемая для репликации
limits dn="uid=replicator,ou=services,dc=domain,dc=com"
size=unlimited
time=unlimited
# Учетка, используемая phpldapadmin
limits dn="uid=ldapadmin,ou=services,dc=domain,dc=com"
size=unlimited
time=unlimited
# Учетка exim
limits dn="uid=exim,ou=services,dc=domain,dc=com"
size=unlimited
time=unlimited
# Учетка nsswitch
limits dn="uid=proxyagent,ou=services,dc=domain,dc=com"
size=unlimited
time=unlimited
# Самое ценное в базе это пароли
access to attrs=userPassword
by dn.base="uid=ldapadmin,ou=services,dc=domain,dc=com" write
by dn.base="uid=replicator,ou=services,dc=domain,dc=com" read
by dn.base="uid=proxyagent,ou=services,dc=domain,dc=com" read
by anonymous auth
by self write
by * none
access to attrs=mail
by dn.base="uid=ldapadmin,ou=services,dc=domain,dc=com" write
by * read
access to *
by dn.base="uid=ldapadmin,ou=services,dc=domain,dc=com" write
by users read
by anonymous auth
Обязательно настраиваем репликацию для OpenLDAP (опять же тут все стандартно, поэтому опускаю детали). Единственно, что мне пришлось собирать OpenLDAP руками для поддержки sssvlv (Server Side Sorting and Virtual List View) в адресной книге (./configure --enable-ipv6=no --enable-syncprov=yes --enable-sssvlv=yes --with-tls=yes).
Также для корректной работы sssvlv в аутлюке, пришлось патчить исходники OpenLDAP.
Патч
--- servers/slapd/schema_prep.c 2011-11-25 20:52:29.000000000 +0200
+++ servers/slapd/schema_prep.c 2011-11-29 13:46:57.000000000 +0200
@@ -915,6 +915,7 @@
offsetof(struct slap_internal_schema, si_ad_name) },
{ "cn", "( 2.5.4.3 NAME ( 'cn' 'commonName' ) "
"DESC 'RFC4519: common name(s) for which the entity is known by' "
+ "ORDERING caseIgnoreOrderingMatch "
"SUP name )",
NULL, 0,
NULL, NULL,
@@ -924,6 +925,7 @@
"DESC 'RFC4519: user identifier' "
"EQUALITY caseIgnoreMatch "
"SUBSTR caseIgnoreSubstringsMatch "
+ "ORDERING caseIgnoreOrderingMatch "
"SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )",
NULL, 0,
NULL, NULL,
(END)
Второй сервер будет работать как адресная книга в режиме только для чтения.
Конфиг slapd.conf для второго LDAP сервера
include /etc/ldap/schema/corba.schema
include /etc/ldap/schema/core.schema
include /etc/ldap/schema/cosine.schema
include /etc/ldap/schema/inetorgperson.schema
include /etc/ldap/schema/misc.schema
include /etc/ldap/schema/nis.schema
# Все slapd сервера при репликации обязательно должны иметь идентичные схемы
include /etc/ldap/schema/phamm-vacation.schema
include /etc/ldap/schema/phamm.schema
# Load dynamic backend modules:
#modulepath /usr/lib/ldap
#moduleload back_hdb.so
#moduleload sssvlv.so #подключаем sssvlv модуль для адресной книги Outlook
idletimeout 120
threads 8
sizelimit 1000
pidfile /var/run/slapd/slapd.pid
argsfile /var/run/slapd/slapd.args
loglevel 0
# Указываем путь к сертификатам для работы через TLS
TLSCACertificateFile /etc/ldap/ssl/ca.pem
TLSCertificateFile /etc/ldap/ssl/ab.domain.com_crt.pem
TLSCertificateKeyFile /etc/ldap/ssl/ab.domain.com_key.pem
TLSProtocolMin 3.1
TLSVerifyClient allow
database hdb # Тут уже используется легковесная hdb
cachesize 100000
suffix "dc=domain,dc=com"
rootdn "cn=replicator,ou=services,dc=domain,dc=com"
rootpw *****
directory /var/lib/ldap
checkpoint 32 30
idletimeout 120
writetimeout 120
overlay sssvlv
# Настройка репликации через TLS, используя syncrepl
syncrepl rid=001 # ID репликации
provider=ldaps://domain.com # Используется ldaps:// , так как через starttls репликация работает нестабильно с self-signed сертификатами
type=refreshOnly
interval=00:00:10:00
searchbase="dc=domain,dc=com"
scope=sub
schemachecking=off
bindmethod=simple
binddn="uid=replicator,ou=services,dc=domain,dc=com" # Предварительно созданный posix аккаунт в ou=services, указанный в slapd.conf
credentials=****
tls_cacertdir=/etc/ssl/certs
tls_cacert=/etc/ldap/ssl/ca.pem
tls_cert=/etc/ldap/ssl/ab.domain.com_crt.pem
tls_key=/etc/ldap/ssl/ab.domain.com_key.pem
tls_reqcert=allow
index uid,accountActive,vacationActive eq
index cn,givenName,sn,mail pres,eq,sub
index uidNumber,gidNumber,memberUid eq
index entryCSN,entryUUID eq
index objectClass,member,uniqueMember eq
# Убираем ограничения для нормальной работы репликации
limits dn="uid=replicator,ou=services,dc=domain,dc=com"
size=unlimited
time=unlimited
# Убираем ограничения для нормальной работы адресной книги
limits users
size=unlimited
time=unlimited
# Определяем права доступа
access to attrs=userPassword
by dn.base="uid=ldapadmin,ou=services,dc=domain,dc=com" write
by dn.base="uid=replicator,ou=services,dc=domain,dc=com" write
by dn.base="uid=proxyagent,ou=services,dc=domain,dc=com" read
by anonymous auth
by self write
by * none
access to attrs=mail
by dn.base="uid=ldapadmin,ou=services,dc=domain,dc=com" write
by dn.base="uid=replicator,ou=services,dc=domain,dc=com" write
by * read
access to attrs=cn
by dn.base="uid=ldapadmin,ou=services,dc=domain,dc=com" write
by dn.base="uid=replicator,ou=services,dc=domain,dc=com" write
by * read
access to *
by dn.base="uid=ldapadmin,ou=services,dc=domain,dc=com" write
by dn.base="uid=replicator,ou=services,dc=domain,dc=com" write
by users read
by anonymous auth
Для администрирования LDAP базы я использую phpldapadmin, так как он легкий, удобный и имеет поддержку XML шаблонов, что позволяет гибко настраивать необходимые шаблоны для создания аккаунтов. К сожалению, проект давно заброшен автором и больше не развивается.
Например, пример шаблона для создания почтового аккаунта.
Код
<objectClasses>
<objectClass id="top"></objectClass>
<objectClass id="inetOrgPerson"></objectClass>
<objectClass id="posixAccount"></objectClass> # Базовый posix аккаунт
<objectClass id="VirtualMailAccount"></objectClass> # Указываем класс из нашей почтовой phamm схемы
<objectClass id="Vacation"></objectClass> # Указываем класс из phamm-vacation схемы
</objectClasses>
<attributes>
<attribute id="givenName">
<display>First Name</display>
<icon>ldap-uid.png</icon>
<order>1</order>
<page>1</page>
</attribute>
<attribute id="sn">
<display>Last Name</display>
<onchange>=autoFill(cn;%givenName% %sn%)</onchange>
<onchange>=autoFill(uid;%givenName|0-1/l%%sn/l%)</onchange>
<onchange>=autoFill(loginShell;/sbin/nologin)</onchange>
<onchange>=autoFill(FTPStatus;enabled)</onchange>
<order>2</order>
<page>1</page>
</attribute>
<attribute id="cn">
<display>Common Name</display>
<order>3</order>
</attribute>
<attribute id="uid">
<display>UID</display>
<onchange>=autoFill(homeDirectory;/home/%uid%)</onchange>
<onchange>=autoFill(mailbox;/home/%uid%/Maildir)</onchange>
<onchange>=autoFill(mail;%uid%@domain.com)</onchange>
<onchange>=autoFill(company;My Company)</onchange>
<order>4</order>
<spacer>1</spacer>
</attribute>
При создании я использую следующую схему LDAP базы:
ou=people,dc=domain,dc=com — контейнер для хранения почтовых аккаунтов;
ou=groups,dc=domain,dc=com — контейнер для хранения posix групп;
ou=services,dc=domain,dc=com — контейнер для хранения аккаунтов системных сервисов (posix аккаунты);
ou=aliases,dc=domain,dc=com — контейнер для хранения почтовых альясов.
При создании почтового аккаунта используются множество атрибутов модифицированной мною phamm схемы, применение которых выходит за рамки это статьи (например, createMaildir или Backup). Отмечу только те, которые используются в фильтрах поиска.
accountActive = TRUE|FALSE — позволяет временно включать/отключать аккаунт или альяс;
vacationInfo — содержит текст Out of Office сообщения;
vacationActive — позволяет включать/отключать OoO сообщение;
quota — тут и так понятно (например: quota = 4G)
Итак, OpenLDAP настроен и запущен, репликация работает и при помощи phpldapadmin создан первый тестовый аккаунт ipupkin c адресом почты ipupkin@domain.com. В дальнейшем будет использоваться только один домен domain.com, поэтому создаем в нашем контейнере ou=groups posix группу domain и добавляем ipupkin в эту группу.
В данном примере все сервисы работают на одном линукс сервере и логично использовать лдап базу для идентификации пользователя в системе, поэтому перекладываем это процедуру на плечи Name Service Switch (nss) или System Security Services Daemon (sssd). Также плюсом данного решения является легкая адаптация с Samba доменом, при необходимости.
Предварительно нужно убедиться что в системе установлен пакет nss_ldap (или libnss-ldapd).
В /etc/nsswitch.conf меняем строки с compat на ldap (или winbind в случае Samba домена):
passwd: files ldap
shadow: files ldap
group: files ldap
Создаем файл /etc/ldap.conf следующего содержания:
ldap.conf
uri ldap://127.0.0.1
uri ldap://192.168.0.1 # Второй fallback лдап-сервер
base dc=domain,dc=com
binddn uid=proxyagent,ou=services,dc=domain,dc=com # Предварительно созданный в ou=services posix аккаунт, указанный в slapd.conf
bindpw *****
pam_filter objectclass=posixAccount
pam_login_attribute uid
pam_check_host_attr no
pam_lookup_policy no
pam_member_attribute memberUid
pam_min_uid 1000
pam_max_uid 65535
ssl start_tls #используем TLS вместо SSL
# Указываем пути к сертификатам
tls_cacert /etc/openldap/ssl/ca.pem
tls_key /etc/openldap/ssl/mail_crt_new.pem
tls_cert /etc/openldap/ssl/mail_key_new.pem
tls_reqcert allow
tls_checkpeer no
tls_ciphers TLSv1
scope sub
timelimit 5
bind_timelimit 5
bind_policy soft
nss_reconnect_tries 4
nss_reconnect_sleeptime 1
nss_reconnect_maxsleeptime 16
nss_reconnect_maxconntries 2
uri ldap://192.168.0.1 # Второй fallback лдап-сервер
base dc=domain,dc=com
binddn uid=proxyagent,ou=services,dc=domain,dc=com # Предварительно созданный в ou=services posix аккаунт, указанный в slapd.conf
bindpw *****
pam_filter objectclass=posixAccount
pam_login_attribute uid
pam_check_host_attr no
pam_lookup_policy no
pam_member_attribute memberUid
pam_min_uid 1000
pam_max_uid 65535
ssl start_tls #используем TLS вместо SSL
# Указываем пути к сертификатам
tls_cacert /etc/openldap/ssl/ca.pem
tls_key /etc/openldap/ssl/mail_crt_new.pem
tls_cert /etc/openldap/ssl/mail_key_new.pem
tls_reqcert allow
tls_checkpeer no
tls_ciphers TLSv1
scope sub
timelimit 5
bind_timelimit 5
bind_policy soft
nss_reconnect_tries 4
nss_reconnect_sleeptime 1
nss_reconnect_maxsleeptime 16
nss_reconnect_maxconntries 2
Проверяем, что nss работает и создана домашная директория (mkdir -m 700 /home ipupkin && chown ipupkin:domain /home/ipupkin).
>id ipupkin
uid=1057(ipupkin) gid=1000(domain) groups=1000(domain)
> ls -ld /home/ipupkin
drwx------ 3 ipupkin domain 4096 Jun 22 15:50 /home/ipupkin
Теперь, когда пользователь ipupkin распознается системой, нужно чтобы ipupkin смог получать и отсылать почту.
Настройка Dovecot
При настройке Dovecot я удалил все вложенные монструозные дефолтные конфиги и для удобства создал только два — dovecot.conf и dovecot-ldap.conf.
dovecot.conf
auth_cache_negative_ttl = 10 mins
auth_debug = no
auth_debug_passwords = no
auth_mechanisms = plain login # Используется TLS, поэтому разрешаем plain
base_dir = /var/run/dovecot/
default_vsz_limit = 1024 M
disable_plaintext_auth = no
dotlock_use_excl = yes
lda_mailbox_autocreate = yes # Обязательно
lda_mailbox_autosubscribe = yes # Обязательно
listen = *
mmap_disable = yes
mail_fsync = always
mail_nfs_storage = no
mail_nfs_index = no
mail_debug = no
mail_location = maildir:~/Maildir # Где искать почтовый ящик, значение maildir берется из атрибута homeDirectory (в нашем случае это /home/ipupkin), соответственно полный путь будет /home/ipupkin/Maildir.
mail_plugins = $mail_plugins quota notify expire
managesieve_notify_capability = mailto
managesieve_sieve_capability = fileinto reject envelope encoded-character vacation subaddress comparator-i;ascii-numeric relational regex imap4flags copy include variables body enotify environment mailbox date
ssl_ca = </etc/dovecot/ssl/ca.pem
ssl_cert = </etc/dovecot/ssl/mail_crt_new.pem
ssl_key = </etc/dovecot/ssl/mail_key_new.pem
ssl_verify_client_cert = no
verbose_ssl = no
protocols = imap pop3 sieve
userdb {
args = /etc/dovecot/dovecot-ldap.conf # Подключаем наш ldap конфиг
driver = ldap
}
passdb {
args = /etc/dovecot/dovecot-ldap-pass.conf #По рекомендации авторов dovecot символическая ссылка на dovecot-ldap.conf
driver = ldap
}
service auth {
unix_listener auth-userdb {
mode = 0666
}
}
service imap-login {
process_min_avail = 6
service_count = 0
}
service pop3-login {
process_min_avail = 6
service_count = 0
}
# Используется managesieve плагином вебпочты roundcube
service managesieve-login {
process_min_avail = 6
service_count = 0
inet_listener sieve {
port = 4190
}
}
service managesieve {
}
service dict {
unix_listener dict {
mode = 0666
}
}
# При заполнении почтового ящика на 90%, выполняется следующий скрипт
service quota-warning {
executable = script /etc/dovecot/quota-warning.sh # Сам скрипт выглядит так
unix_listener quota-warning {
mode = 0666
}
}
protocol imap {
imap_client_workarounds = delay-newmail # Хак для Аутлука
mail_plugins = quota imap_quota mail_log notify
}
protocol pop {
pop3_client_workarounds = outlook-no-nuls oe-ns-eoh # Различные хаки для Аутлука
#pop3_uidl_format = %08Xu%08Xv # Используется при хранении Аутлуком почты на сервере, этот формат имеет проблемы с Аутлуком 2013
pop3_uidl_format = %g
pop3_fast_size_lookups=yes # Подробнее здесь раздел Maildir perfomance.
mail_plugins =
}
# Logical Delivery Agent (LDA) сервис, используемый Exim для доставки почты.
protocol lda {
hostname = domain.com
mail_fsync = optimized
mail_plugins = sieve quota
postmaster_address = postmaster@domain.com
log_path =
info_log_path =
}
protocol sieve {
}
# Различные опциональные добавки
plugin {
quota = maildir:User quota
quota_rule = *:storage=1M
quota_rule2 = Trash:ignore
quota_rule3 = Deleted Items:ignore
quota_rule4 = Junk E-mail:ignore
quota_rule5 = Archive:ignore
quota_rule6 = archive:ignore
quota_warning = storage=90%% quota-warning 90 %u # Указываем процент заполнения почтового ящика, при котором срабатывает скрипт quota-warning.sh
sieve = ~/Maildir/.dovecot.sieve
sieve_dir = ~/Maildir/sieve
expire_dict = proxy::expire
expire = Trash
expire2 = Deleted Items
expire3 = Junk E-mail
expire_cache = yes
}
auth_debug = no
auth_debug_passwords = no
auth_mechanisms = plain login # Используется TLS, поэтому разрешаем plain
base_dir = /var/run/dovecot/
default_vsz_limit = 1024 M
disable_plaintext_auth = no
dotlock_use_excl = yes
lda_mailbox_autocreate = yes # Обязательно
lda_mailbox_autosubscribe = yes # Обязательно
listen = *
mmap_disable = yes
mail_fsync = always
mail_nfs_storage = no
mail_nfs_index = no
mail_debug = no
mail_location = maildir:~/Maildir # Где искать почтовый ящик, значение maildir берется из атрибута homeDirectory (в нашем случае это /home/ipupkin), соответственно полный путь будет /home/ipupkin/Maildir.
mail_plugins = $mail_plugins quota notify expire
managesieve_notify_capability = mailto
managesieve_sieve_capability = fileinto reject envelope encoded-character vacation subaddress comparator-i;ascii-numeric relational regex imap4flags copy include variables body enotify environment mailbox date
ssl_ca = </etc/dovecot/ssl/ca.pem
ssl_cert = </etc/dovecot/ssl/mail_crt_new.pem
ssl_key = </etc/dovecot/ssl/mail_key_new.pem
ssl_verify_client_cert = no
verbose_ssl = no
protocols = imap pop3 sieve
userdb {
args = /etc/dovecot/dovecot-ldap.conf # Подключаем наш ldap конфиг
driver = ldap
}
passdb {
args = /etc/dovecot/dovecot-ldap-pass.conf #По рекомендации авторов dovecot символическая ссылка на dovecot-ldap.conf
driver = ldap
}
service auth {
unix_listener auth-userdb {
mode = 0666
}
}
service imap-login {
process_min_avail = 6
service_count = 0
}
service pop3-login {
process_min_avail = 6
service_count = 0
}
# Используется managesieve плагином вебпочты roundcube
service managesieve-login {
process_min_avail = 6
service_count = 0
inet_listener sieve {
port = 4190
}
}
service managesieve {
}
service dict {
unix_listener dict {
mode = 0666
}
}
# При заполнении почтового ящика на 90%, выполняется следующий скрипт
service quota-warning {
executable = script /etc/dovecot/quota-warning.sh # Сам скрипт выглядит так
Скрипт
#!/bin/sh
PERCENT=$1
USER=$2
cat << EOF | /usr/libexec/dovecot/dovecot-lda -d $USER -o "plugin/quota=maildir:User quota:noenforcing"
From: postmaster@domain.com
Subject: Your mailbox is $PERCENT% full
Content-Type: text/plain; charset="UTF-8"
X-Priority: 2
Warning! Your mailbox is now $PERCENT% full.
EOF
unix_listener quota-warning {
mode = 0666
}
}
protocol imap {
imap_client_workarounds = delay-newmail # Хак для Аутлука
mail_plugins = quota imap_quota mail_log notify
}
protocol pop {
pop3_client_workarounds = outlook-no-nuls oe-ns-eoh # Различные хаки для Аутлука
#pop3_uidl_format = %08Xu%08Xv # Используется при хранении Аутлуком почты на сервере, этот формат имеет проблемы с Аутлуком 2013
pop3_uidl_format = %g
pop3_fast_size_lookups=yes # Подробнее здесь раздел Maildir perfomance.
mail_plugins =
}
# Logical Delivery Agent (LDA) сервис, используемый Exim для доставки почты.
protocol lda {
hostname = domain.com
mail_fsync = optimized
mail_plugins = sieve quota
postmaster_address = postmaster@domain.com
log_path =
info_log_path =
}
protocol sieve {
}
# Различные опциональные добавки
plugin {
quota = maildir:User quota
quota_rule = *:storage=1M
quota_rule2 = Trash:ignore
quota_rule3 = Deleted Items:ignore
quota_rule4 = Junk E-mail:ignore
quota_rule5 = Archive:ignore
quota_rule6 = archive:ignore
quota_warning = storage=90%% quota-warning 90 %u # Указываем процент заполнения почтового ящика, при котором срабатывает скрипт quota-warning.sh
sieve = ~/Maildir/.dovecot.sieve
sieve_dir = ~/Maildir/sieve
expire_dict = proxy::expire
expire = Trash
expire2 = Deleted Items
expire3 = Junk E-mail
expire_cache = yes
}
Проверяем что ошибок нет (dovecot -a) и запускаем dovecot.
Финал: Настройка Exim
Основной целью при настройке Exim был максимальный отказ от любых скриптов и реализация всего функционала только средствами Exim. Только как исключение используются дебиановский greylistd на питоне и перловый amavisd-new.
При настройке Exim также будут использоваться два файла — основной exim.conf и acl_smtp для ACL правил.
Также опционально в конфиге присутствует роутер и транспорт для mailman.
exim.conf
CONFIG_PREFIX=/etc/exim
ACL_PREFIX=CONFIG_PREFIX/acls #здесь хранятся все ACL конфиги
DB_PREFIX=/var/spool/exim/db #для увеличения быстродействия желательно использовать tmpfs
# Шаблоны являются сильной стороной Exim и позволяют легко заменить длинную строку коротким словом.
# Определяем шаблоны для mailman
MM_HOME=/var/lib/mailman
MM_UID=mailman
MM_GID=mailman
MM_WRAP=/usr/lib/mailman/mail/mailman
MM_LISTCHK=MM_HOME/lists/${lc::$local_part}/config.pck
ldap_default_servers = /var/run/openldap/slapd.sock: 192.168.0.1 # Указываем как соединяться к лдап-серверам, второй сервер будет использоваться как fallback.
INTERFACE = your_external_ip # Указываем внешний айпи, на котором будет висеть exim
BASEDN = dc=domain,dc=com # basedn лдап сервера
# В этой секции указаны самые важные темплейты, задающие логику работы Exim
# Проверка альяса, соответствует ли адрес альяса значению атрибута mail в контейнере aliases, альяс также должен принадлежать классу VirtualMailAlias и иметь значение TRUE для атрибута accountActive
CHECK_1 = ${lookup ldap {user=«uid=exim,ou=services,dc=domain,dc=com» pass=*** ldap:///ou=aliases,dc=domain,dc=com?mail?sub?(&(objectClass=VirtualMailAlias)(accountActive=TRUE)(mail=${quote_ldap:$local_part@$domain}))} }
# Проверка аккаунта, соответствует ли адрес получателя значению атрибута mail в контейнере people, аккаунт также должен принадлежать к классу VirtualMailAccount и иметь значение TRUE для атрибута accountActive
CHECK_2 = ${lookup ldap {user=«uid=exim,ou=services,dc=domain,dc=com» pass=*** ldap:///ou=people,dc=domain,dc=com?mail?sub?(&(objectClass=VirtualMailAccount)(accountActive=TRUE)(mail=${quote_ldap:$local_part@$domain}))} }
# Список участников альяса, кому пересылать почту, значение аттрибута maildrop
CHECK_DATA = ${lookup ldapm {user=«uid=exim,ou=services,dc=domain,dc=com» pass=*** ldap:///ou=aliases,dc=domain,dc=com?maildrop?sub?(&(objectClass=VirtualMailAlias)(mail=${quote_ldap:$local_part@$domain}))}}
# Путь к почтовому ящику, значение аттрибута mailbox
CHECK_MAILDIR = ${lookup ldap {user=«uid=exim,ou=services,dc=domain,dc=com» pass=*** ldap:///ou=people,dc=domain,dc=com?mailbox?sub?(&(objectClass=VirtualMailAccount)(accountActive=TRUE)(mail=${quote_ldap:$local_part@$domain}))} }
# Текст OoO сообщения, значение аттрибута vacationInfo
CHECK_VACATION = ${lookup ldap {user=«uid=exim,ou=services,dc=domain,dc=com» pass=*** ldap:///ou=people,dc=domain,dc=com?vacationInfo?sub?(&(objectClass=VirtualMailAccount)(vacationActive=TRUE)(mail=${quote_ldap:$local_part@$domain}))}}
#Hack for double commas in OoO message, exim's bug 660 — кажется так и не исправили
VACATION = ${sg{ ${lookup ldap {user=«uid=exim,ou=services,dc=domain,dc=com» pass=*** ldap:///ou=people,dc=domain,dc=com?vacationInfo?sub?(&(objectClass=VirtualMailAccount)(vacationActive=TRUE)(mail=${quote_ldap:$local_part@$domain}))}} }{,,}{,}}
domainlist_cache virt_domains = domain.com # Так как у нас только один домен, то указываем его. Если используется множество доменов, то нужно создать темплейт выборки доменов из лдап базы.
domainlist_cache local_domains = localhost: mail.domain.com
hostlist relay_from_hosts = 127.0.0.1: 192.168.0.0/16
addresslist noautoreply_senders = DB_PREFIX/autoreply.noanswer.db
sender_unqualified_hosts = 127.0.0.1: 192.168.0.0/16
recipient_unqualified_hosts = 127.0.0.1: 192.168.0.0/16
local_interfaces = 0.0.0.0.25: 0.0.0.0.26: 0.0.0.0.465: 0.0.0.0.587: 127.0.0.1.10025
tls_on_connect_ports = 465
acl_smtp_connect = acl_check_connect
acl_smtp_helo = acl_check_helo
acl_smtp_mail = acl_check_mail
acl_smtp_rcpt = acl_check_rcpt
acl_smtp_data = acl_check_data
acl_smtp_dkim = acl_check_dkim
accept_8bitmime
auth_advertise_hosts = !127.0.0.1 # Не предлагать SMTP AUTH локалхосту
bounce_message_file = CONFIG_PREFIX/bounce.msg # Указываем формат bounce сообщения, у меня так:
bounce_return_size_limit = 100K
delay_warning = 15m:1h:99d
deliver_queue_load_max = 40
disable_ipv6
exim_group = vmail # Желательно чтобы все почтовые сервисы (dovecot, spamassassin, clamav и тд) работали под одним gid
exim_user = vmail # Желательно чтобы все почтовые сервисы (dovecot, spamassassin, clamav и тд) работали под одним uid
headers_charset = UTF-8 # Желательно включить
ignore_bounce_errors_after = 0s
local_scan_timeout = 0s
message_size_limit = 50M
never_users = root
no_message_logs
no_smtp_enforce_sync
no_syslog_duplication
primary_hostname = mail.domain.com
qualify_domain = domain.com
queue_only_load = 12
queue_run_max = 5
recipients_max = 500
recipients_max_reject
remote_max_parallel = 2
return_size_limit = 10000
rfc1413_query_timeout = 0s
smtp_accept_max = 500
smtp_accept_max_per_host = 500
smtp_accept_queue = 500
smtp_accept_queue_per_connection = 1000
smtp_accept_reserve = 15
smtp_banner = $primary_hostname ESMTP ready $tod_full
smtp_connect_backlog = 40
smtp_load_reserve = 20
smtp_return_error_details
split_spool_directory
strip_excess_angle_brackets
strip_trailing_dot
syslog_facility = mail # Логи отсылаются syslog сервису
syslog_processname = exim
system_filter = DB_PREFIX/exim.filter # Глобальный exim фильтер, у меня в основном не используется
timeout_frozen_after = 7d
tls_advertise_hosts = !127.0.0.1 # Не предлагать TLS локалхосту
# Указываем пути к сертификатам
tls_certificate = /etc/exim/ssl/mail_crt_new.pem
tls_privatekey = /etc/exim/ssl/mail_key_new.pem
tls_verify_certificates = /etc/exim/ssl/ca.pem
# Определяем формат заголовка письма
received_header_text = «Received:\
${if def:sender_rcvhost {from INTERFACE\n\t}\
{${if def:sender_ident {from relay }}\
${if def:sender_helo_name {(helo=${sender_helo_name})\n\t}}}}\
by ${qualify_domain}\
id ${message_id}\
${if def:received_for {\n\tfor <$received_for>}}»
# Подключаем антивирус и антиспам напрямую, так как LDAP здесь не используется, то все стандартно, поэтому опускаем настройку.
# av_scanner = clamd:/tmp/clamd
# spamd_address = 127.0.0.1 783
begin acl
# Подключаем наш ACL конфиг (см ниже)
.include ACL_PREFIX/acl_smtp
# Создаем роутеры
begin routers
# Роутер для исходящей почты
dnslookup:
driver = dnslookup
domains = !+local_domains: !+virt_domains
transport = remote_smtp
ignore_target_hosts = 0.0.0.0: 127.0.0.0/8
no_more
# Роутеры для входящей почты
#Опицональный транспорт для amavis (сделаем одно исключение перлу :) )
#amavis:
# driver = manualroute
# condition = ${if or {\
# {eq{$interface_port}{10025}} \
# {eq{$received_protocol}{spam-scanned}}\
# {eq{$sender_address}{}}\
# {eq{$sender_address_domain}{domain.com}}\
# {eq{$sender_address_domain}{kaspersky.com}}\
# {eq{${lc:$dkim_verify_status}}{pass}}\
# {match{$sender_address_local_part}{-bounces}}\
# }{0}{1}}
# domains = +virt_domains
# senders =!: !postmaster@*: !mailer-daemon@*: !nagios@*: !monit@*
# no_verify
# no_expn
# transport = amavis
# route_list = "* localhost byname"
# self = send
autorespond:
driver = accept
domains = +virt_domains
senders =!: !+noautoreply_senders # Не отвечать локалхосту и отправителям, указанным в autoreply.noanswer.db
condition = ${if and {\
{!eq{CHECK_VACATION}{}}\ # Наш шаблон
{!match{$h_precedence:}{junk|bulk|list}}\ # Не отвечать рассылкам
{!def:header_Auto-Submitted:}\
{!def:header_List-Id:}\
}}
no_verify
no_expn
unseen
transport = auto_responder
# Виртуальные альясы
aliases:
driver = redirect
domains = !+local_domains
condition = CHECK_1 # Тут происходит двойная проверка (первая в acl_smtp), если действующий аккаунт имеет еще и альяс, ничего лучше не придумал
forbid_file
forbid_pipe
forbid_filter_reply = true
data = CHECK_DATA # Кому пересылать письма
allow_fail
allow_defer
mailman_router:
driver = accept
domains = domain.com
require_files = MM_LISTCHK # Вместо проверки по файлу, можно сделать поверку по значению атрибута aliasType=DL, например
local_part_suffix_optional
local_part_suffix = -admin: \
-bounces: -bounces+*: \
-confirm: -confirm+*: \
-join: -leave: \
-owner: -request: \
-subscribe: -unsubscribe
transport = mailman_transport
system_aliases:
driver = redirect
domains = +local_domains
errors_to =
no_verify
data = ${lookup{$local_part}partial0-dbm{DB_PREFIX/aliases.db}{$value}fail}
file_transport = address_file
pipe_transport = address_pipe
allow_fail
allow_defer
localuser:
driver = accept
domains = +local_domains: +virt_domains
check_local_user
transport = dovecot_lda # Перекладываем доставку письма в почтовый ящик на плечи dovecot lda
cannot_route_message = Unknown account # Dovecot ответил нет, сдаемся
no_more
###############################################################
begin transports
###############################################################
remote_smtp:
driver = smtp
helo_data = mail.domain.com
max_rcpt = 500
#подключаем DKIM
dkim_domain = domain.com
dkim_selector = dkim
dkim_private_key = DB_PREFIX/dkim.private.key
dkim_canon = relaxed
auto_responder:
driver = autoreply
from = "${local_part}@${domain}"
to = "${reply_address}"
once = "/var/spool/exim/autoreply/${local_part}@${domain}"
once_repeat = 1d # Отвечать отправителю один раз в день, хотя можно логику перенести также в LDAP (см. phamm-vacation.schema)
headers = «Content-Type: text/plain; charset=utf-8\nContent-Transfer-Encoding: 8bit»
subject = ${rfc2047:Auto-Reply: $h_subject:}
text = VACATION # Наш шаблон с текстом OoO сообщения
body_only
no_return_message
# Тот самый транспорт для dovecot
dovecot_lda:
driver = pipe
command = /usr/libexec/dovecot/dovecot-lda -f "$sender_address" -d "$local_part@$domain"
home_directory = /home/$local_part
delivery_date_add
envelope_to_add
return_path_add
log_output
log_defer_output
return_fail_output
freeze_exec_fail
temp_errors = 64: 69: 70: 71: 72: 73: 74: 75: 78
address_pipe:
driver = pipe
return_output
address_file:
driver = appendfile
current_directory = SPOOL
home_directory = SPOOL
create_directory
directory_mode = 0700
maildir_format
user = vmail
group = vmail
mode = 0600
no_check_owner
no_mode_fail_narrower
address_reply:
driver = autoreply
maillist_pipe:
driver = pipe
group = mail
return_fail_output
user = vmail
mailman_transport:
driver = pipe
command = MM_WRAP \
'${if def:local_part_suffix \
{${sg{$local_part_suffix}{-(\\w+)(\\+.*)?}{\$1}}} \
{post}}' \
$local_part
current_directory = MM_HOME
home_directory = MM_HOME
user = MM_UID
group = MM_GID
#amavis:
# driver = smtp
# port = 10024
# allow_localhost
begin retry
* quota
* rcpt_4xx senders=: F,1h,10m
* * F,2h,10m; G,16h,1h,1.5; F,4d,6h
# Все системные сообщения рутов отправляем на один ящик
begin rewrite
root@* collector@domain.com Ttbcr
# Аутенфикаторы SMTP AUTH
begin authenticators
plain:
driver = plaintext
public_name = PLAIN
server_prompts =:
server_condition = "${lookup ldap{user=uid=${quote_ldap_dn:$auth2},ou=people,BASEDN pass=${quote:$auth3} \
ldap:///ou=people,BASEDN?uid?sub?(&(uid=$auth2)(objectClass=VirtualMailAccount)(accountActive=TRUE))}{yes}fail}"
server_set_id = $auth2
login:
driver = plaintext
public_name = LOGIN
server_prompts = «Username::: Password::»
server_condition = "${lookup ldap{user=uid=${quote_ldap_dn:$auth1},ou=people,BASEDN pass=${quote:$auth2} \
ldap:///ou=people,BASEDN?uid?sub?(&(uid=$auth1)(objectClass=VirtualMailAccount)(accountActive=TRUE))}{yes}fail}"
server_set_id = $auth1
ACL_PREFIX=CONFIG_PREFIX/acls #здесь хранятся все ACL конфиги
DB_PREFIX=/var/spool/exim/db #для увеличения быстродействия желательно использовать tmpfs
# Шаблоны являются сильной стороной Exim и позволяют легко заменить длинную строку коротким словом.
# Определяем шаблоны для mailman
MM_HOME=/var/lib/mailman
MM_UID=mailman
MM_GID=mailman
MM_WRAP=/usr/lib/mailman/mail/mailman
MM_LISTCHK=MM_HOME/lists/${lc::$local_part}/config.pck
ldap_default_servers = /var/run/openldap/slapd.sock: 192.168.0.1 # Указываем как соединяться к лдап-серверам, второй сервер будет использоваться как fallback.
INTERFACE = your_external_ip # Указываем внешний айпи, на котором будет висеть exim
BASEDN = dc=domain,dc=com # basedn лдап сервера
# В этой секции указаны самые важные темплейты, задающие логику работы Exim
# Проверка альяса, соответствует ли адрес альяса значению атрибута mail в контейнере aliases, альяс также должен принадлежать классу VirtualMailAlias и иметь значение TRUE для атрибута accountActive
CHECK_1 = ${lookup ldap {user=«uid=exim,ou=services,dc=domain,dc=com» pass=*** ldap:///ou=aliases,dc=domain,dc=com?mail?sub?(&(objectClass=VirtualMailAlias)(accountActive=TRUE)(mail=${quote_ldap:$local_part@$domain}))} }
# Проверка аккаунта, соответствует ли адрес получателя значению атрибута mail в контейнере people, аккаунт также должен принадлежать к классу VirtualMailAccount и иметь значение TRUE для атрибута accountActive
CHECK_2 = ${lookup ldap {user=«uid=exim,ou=services,dc=domain,dc=com» pass=*** ldap:///ou=people,dc=domain,dc=com?mail?sub?(&(objectClass=VirtualMailAccount)(accountActive=TRUE)(mail=${quote_ldap:$local_part@$domain}))} }
# Список участников альяса, кому пересылать почту, значение аттрибута maildrop
CHECK_DATA = ${lookup ldapm {user=«uid=exim,ou=services,dc=domain,dc=com» pass=*** ldap:///ou=aliases,dc=domain,dc=com?maildrop?sub?(&(objectClass=VirtualMailAlias)(mail=${quote_ldap:$local_part@$domain}))}}
# Путь к почтовому ящику, значение аттрибута mailbox
CHECK_MAILDIR = ${lookup ldap {user=«uid=exim,ou=services,dc=domain,dc=com» pass=*** ldap:///ou=people,dc=domain,dc=com?mailbox?sub?(&(objectClass=VirtualMailAccount)(accountActive=TRUE)(mail=${quote_ldap:$local_part@$domain}))} }
# Текст OoO сообщения, значение аттрибута vacationInfo
CHECK_VACATION = ${lookup ldap {user=«uid=exim,ou=services,dc=domain,dc=com» pass=*** ldap:///ou=people,dc=domain,dc=com?vacationInfo?sub?(&(objectClass=VirtualMailAccount)(vacationActive=TRUE)(mail=${quote_ldap:$local_part@$domain}))}}
#Hack for double commas in OoO message, exim's bug 660 — кажется так и не исправили
VACATION = ${sg{ ${lookup ldap {user=«uid=exim,ou=services,dc=domain,dc=com» pass=*** ldap:///ou=people,dc=domain,dc=com?vacationInfo?sub?(&(objectClass=VirtualMailAccount)(vacationActive=TRUE)(mail=${quote_ldap:$local_part@$domain}))}} }{,,}{,}}
domainlist_cache virt_domains = domain.com # Так как у нас только один домен, то указываем его. Если используется множество доменов, то нужно создать темплейт выборки доменов из лдап базы.
domainlist_cache local_domains = localhost: mail.domain.com
hostlist relay_from_hosts = 127.0.0.1: 192.168.0.0/16
addresslist noautoreply_senders = DB_PREFIX/autoreply.noanswer.db
sender_unqualified_hosts = 127.0.0.1: 192.168.0.0/16
recipient_unqualified_hosts = 127.0.0.1: 192.168.0.0/16
local_interfaces = 0.0.0.0.25: 0.0.0.0.26: 0.0.0.0.465: 0.0.0.0.587: 127.0.0.1.10025
tls_on_connect_ports = 465
acl_smtp_connect = acl_check_connect
acl_smtp_helo = acl_check_helo
acl_smtp_mail = acl_check_mail
acl_smtp_rcpt = acl_check_rcpt
acl_smtp_data = acl_check_data
acl_smtp_dkim = acl_check_dkim
accept_8bitmime
auth_advertise_hosts = !127.0.0.1 # Не предлагать SMTP AUTH локалхосту
bounce_message_file = CONFIG_PREFIX/bounce.msg # Указываем формат bounce сообщения, у меня так:
bounce msg
Subject: Mail delivery failed ${if eq{$sender_address}{$bounce_recipient}{: returning message to sender}}
****
This message was created automatically by mail delivery software.
A message ${if eq{$sender_address}{$bounce_recipient}{that you sent }{sent by
<$sender_address>
}}could not be delivered to all of its recipients.
The following address(es) failed:
****
The following text was generated during the delivery attempt(s):
****
— This is a copy of the message, including all the headers. — ****
— The body of the message is $message_size characters long; only the first
— $return_size_limit or so are included here.
****
****
This message was created automatically by mail delivery software.
A message ${if eq{$sender_address}{$bounce_recipient}{that you sent }{sent by
<$sender_address>
}}could not be delivered to all of its recipients.
The following address(es) failed:
****
The following text was generated during the delivery attempt(s):
****
— This is a copy of the message, including all the headers. — ****
— The body of the message is $message_size characters long; only the first
— $return_size_limit or so are included here.
****
bounce_return_size_limit = 100K
delay_warning = 15m:1h:99d
deliver_queue_load_max = 40
disable_ipv6
exim_group = vmail # Желательно чтобы все почтовые сервисы (dovecot, spamassassin, clamav и тд) работали под одним gid
exim_user = vmail # Желательно чтобы все почтовые сервисы (dovecot, spamassassin, clamav и тд) работали под одним uid
headers_charset = UTF-8 # Желательно включить
ignore_bounce_errors_after = 0s
local_scan_timeout = 0s
message_size_limit = 50M
never_users = root
no_message_logs
no_smtp_enforce_sync
no_syslog_duplication
primary_hostname = mail.domain.com
qualify_domain = domain.com
queue_only_load = 12
queue_run_max = 5
recipients_max = 500
recipients_max_reject
remote_max_parallel = 2
return_size_limit = 10000
rfc1413_query_timeout = 0s
smtp_accept_max = 500
smtp_accept_max_per_host = 500
smtp_accept_queue = 500
smtp_accept_queue_per_connection = 1000
smtp_accept_reserve = 15
smtp_banner = $primary_hostname ESMTP ready $tod_full
smtp_connect_backlog = 40
smtp_load_reserve = 20
smtp_return_error_details
split_spool_directory
strip_excess_angle_brackets
strip_trailing_dot
syslog_facility = mail # Логи отсылаются syslog сервису
syslog_processname = exim
system_filter = DB_PREFIX/exim.filter # Глобальный exim фильтер, у меня в основном не используется
timeout_frozen_after = 7d
tls_advertise_hosts = !127.0.0.1 # Не предлагать TLS локалхосту
# Указываем пути к сертификатам
tls_certificate = /etc/exim/ssl/mail_crt_new.pem
tls_privatekey = /etc/exim/ssl/mail_key_new.pem
tls_verify_certificates = /etc/exim/ssl/ca.pem
# Определяем формат заголовка письма
received_header_text = «Received:\
${if def:sender_rcvhost {from INTERFACE\n\t}\
{${if def:sender_ident {from relay }}\
${if def:sender_helo_name {(helo=${sender_helo_name})\n\t}}}}\
by ${qualify_domain}\
id ${message_id}\
${if def:received_for {\n\tfor <$received_for>}}»
# Подключаем антивирус и антиспам напрямую, так как LDAP здесь не используется, то все стандартно, поэтому опускаем настройку.
# av_scanner = clamd:/tmp/clamd
# spamd_address = 127.0.0.1 783
begin acl
# Подключаем наш ACL конфиг (см ниже)
.include ACL_PREFIX/acl_smtp
# Создаем роутеры
begin routers
# Роутер для исходящей почты
dnslookup:
driver = dnslookup
domains = !+local_domains: !+virt_domains
transport = remote_smtp
ignore_target_hosts = 0.0.0.0: 127.0.0.0/8
no_more
# Роутеры для входящей почты
#Опицональный транспорт для amavis (сделаем одно исключение перлу :) )
#amavis:
# driver = manualroute
# condition = ${if or {\
# {eq{$interface_port}{10025}} \
# {eq{$received_protocol}{spam-scanned}}\
# {eq{$sender_address}{}}\
# {eq{$sender_address_domain}{domain.com}}\
# {eq{$sender_address_domain}{kaspersky.com}}\
# {eq{${lc:$dkim_verify_status}}{pass}}\
# {match{$sender_address_local_part}{-bounces}}\
# }{0}{1}}
# domains = +virt_domains
# senders =!: !postmaster@*: !mailer-daemon@*: !nagios@*: !monit@*
# no_verify
# no_expn
# transport = amavis
# route_list = "* localhost byname"
# self = send
autorespond:
driver = accept
domains = +virt_domains
senders =!: !+noautoreply_senders # Не отвечать локалхосту и отправителям, указанным в autoreply.noanswer.db
condition = ${if and {\
{!eq{CHECK_VACATION}{}}\ # Наш шаблон
{!match{$h_precedence:}{junk|bulk|list}}\ # Не отвечать рассылкам
{!def:header_Auto-Submitted:}\
{!def:header_List-Id:}\
}}
no_verify
no_expn
unseen
transport = auto_responder
# Виртуальные альясы
aliases:
driver = redirect
domains = !+local_domains
condition = CHECK_1 # Тут происходит двойная проверка (первая в acl_smtp), если действующий аккаунт имеет еще и альяс, ничего лучше не придумал
forbid_file
forbid_pipe
forbid_filter_reply = true
data = CHECK_DATA # Кому пересылать письма
allow_fail
allow_defer
mailman_router:
driver = accept
domains = domain.com
require_files = MM_LISTCHK # Вместо проверки по файлу, можно сделать поверку по значению атрибута aliasType=DL, например
local_part_suffix_optional
local_part_suffix = -admin: \
-bounces: -bounces+*: \
-confirm: -confirm+*: \
-join: -leave: \
-owner: -request: \
-subscribe: -unsubscribe
transport = mailman_transport
system_aliases:
driver = redirect
domains = +local_domains
errors_to =
no_verify
data = ${lookup{$local_part}partial0-dbm{DB_PREFIX/aliases.db}{$value}fail}
file_transport = address_file
pipe_transport = address_pipe
allow_fail
allow_defer
localuser:
driver = accept
domains = +local_domains: +virt_domains
check_local_user
transport = dovecot_lda # Перекладываем доставку письма в почтовый ящик на плечи dovecot lda
cannot_route_message = Unknown account # Dovecot ответил нет, сдаемся
no_more
###############################################################
begin transports
###############################################################
remote_smtp:
driver = smtp
helo_data = mail.domain.com
max_rcpt = 500
#подключаем DKIM
dkim_domain = domain.com
dkim_selector = dkim
dkim_private_key = DB_PREFIX/dkim.private.key
dkim_canon = relaxed
auto_responder:
driver = autoreply
from = "${local_part}@${domain}"
to = "${reply_address}"
once = "/var/spool/exim/autoreply/${local_part}@${domain}"
once_repeat = 1d # Отвечать отправителю один раз в день, хотя можно логику перенести также в LDAP (см. phamm-vacation.schema)
headers = «Content-Type: text/plain; charset=utf-8\nContent-Transfer-Encoding: 8bit»
subject = ${rfc2047:Auto-Reply: $h_subject:}
text = VACATION # Наш шаблон с текстом OoO сообщения
body_only
no_return_message
# Тот самый транспорт для dovecot
dovecot_lda:
driver = pipe
command = /usr/libexec/dovecot/dovecot-lda -f "$sender_address" -d "$local_part@$domain"
home_directory = /home/$local_part
delivery_date_add
envelope_to_add
return_path_add
log_output
log_defer_output
return_fail_output
freeze_exec_fail
temp_errors = 64: 69: 70: 71: 72: 73: 74: 75: 78
address_pipe:
driver = pipe
return_output
address_file:
driver = appendfile
current_directory = SPOOL
home_directory = SPOOL
create_directory
directory_mode = 0700
maildir_format
user = vmail
group = vmail
mode = 0600
no_check_owner
no_mode_fail_narrower
address_reply:
driver = autoreply
maillist_pipe:
driver = pipe
group = mail
return_fail_output
user = vmail
mailman_transport:
driver = pipe
command = MM_WRAP \
'${if def:local_part_suffix \
{${sg{$local_part_suffix}{-(\\w+)(\\+.*)?}{\$1}}} \
{post}}' \
$local_part
current_directory = MM_HOME
home_directory = MM_HOME
user = MM_UID
group = MM_GID
#amavis:
# driver = smtp
# port = 10024
# allow_localhost
begin retry
* quota
* rcpt_4xx senders=: F,1h,10m
* * F,2h,10m; G,16h,1h,1.5; F,4d,6h
# Все системные сообщения рутов отправляем на один ящик
begin rewrite
root@* collector@domain.com Ttbcr
# Аутенфикаторы SMTP AUTH
begin authenticators
plain:
driver = plaintext
public_name = PLAIN
server_prompts =:
server_condition = "${lookup ldap{user=uid=${quote_ldap_dn:$auth2},ou=people,BASEDN pass=${quote:$auth3} \
ldap:///ou=people,BASEDN?uid?sub?(&(uid=$auth2)(objectClass=VirtualMailAccount)(accountActive=TRUE))}{yes}fail}"
server_set_id = $auth2
login:
driver = plaintext
public_name = LOGIN
server_prompts = «Username::: Password::»
server_condition = "${lookup ldap{user=uid=${quote_ldap_dn:$auth1},ou=people,BASEDN pass=${quote:$auth2} \
ldap:///ou=people,BASEDN?uid?sub?(&(uid=$auth1)(objectClass=VirtualMailAccount)(accountActive=TRUE))}{yes}fail}"
server_set_id = $auth1
Орудие главного калибра Exim — это Access Control Lists.
Собственно, вся статья затевалась ради одной стройки в секции smtp_rcpt.
acl_smtp
acl_check_connect:
accept hosts =: +relay_from_hosts: net-dbm;DB_PREFIX/whitelist_hosts.db
deny message = $sender_host_address is listed in $dnslist_domain ${if def:dnslist_text {($dnslist_text)}}
dnslists = sbl.spamhaus.org: xbl.spamhaus.org: bl.spamcop.net
accept
acl_check_dkim:
warn log_message = DKIM: Sender without DKIM signature
sender_domains = gmail.com: autodesk.com: paypal.com
dkim_signers = gmail.com: autodesk.com: paypal.com
dkim_status = none:invalid:fail
accept
acl_check_helo:
accept hosts =: +relay_from_hosts
#HELO is an open proxy
deny condition = ${if and {\
{isip{$sender_helo_name}}\
{eq{$sender_helo_name}{$sender_host_address}}\
}}
message = Open Proxy in HELO/EHLO (HELO was $sender_helo_name)
delay = 10s
#HELO is my hostname
deny condition = ${if match{$sender_helo_name}{$primary_hostname}}
message = Bad HELO — Host impersonating [$sender_helo_name]
#HELO is my address
deny condition = ${if eq{$interface_address}{$sender_helo_name}}
message = $interface_address is my address
accept
acl_check_mail:
accept hosts =: +relay_from_hosts
discard senders = dbm;DB_PREFIX/banned_senders.db: dbm;DB_PREFIX/scammers.db
#HELO required before MAIL
deny condition = ${if eq{$sender_helo_name}{}}
message = HELO/EHLO required before MAIL
accept
acl_check_rcpt:
#stub address
discard condition = ${if match{$local_part@$domain}{blackhole@domain.com}} # blackhole аккаунт
deny message = Restricted characters in address
local_parts = ^[.]: ^.*[@%!/|]
#Reverse DNS check
warn condition = ${if and{{def:sender_host_address}{!def:sender_host_name}}{yes}{no}}
!hosts =: +relay_from_hosts: net-dbm;DB_PREFIX/whitelist_hosts.db
control = no_pipelining
delay = 10s # Делаем задержку в 10 секунд из вредности, если хост не имеет обратки
log_message = X-Host-Lookup-Failed: Reverse DNS lookup failed for $sender_host_address
# RATELIMIT SECTION
#Keep authenticated users under control
warn authenticated = *
ratelimit = 100 / 5m / strict / $authenticated_id
set acl_m100 = ${eval: ${sg{$sender_rate}{[.].*}{}} — $sender_rate_limit + 10}s
delay = $acl_m100
log_message = Ratelimit: Delay $acl_m100 for $authenticated_id. Rate limit $sender_rate / $sender_rate_period
#Limit local senders, exclude mailing-list agent
warn condition = ${if !match{$sender_address_local_part}{bounces}}
hosts =: 127.0.0.1
ratelimit = 1000 / 1h / per_rcpt / strict / $sender_host_address
set acl_m101 = ${eval: ${sg{$sender_rate}{[.].*}{}} — $sender_rate_limit}s
delay = $acl_m101
log_message = Ratelimit: Delay $acl_m101 for $sender_address ($sender_host_address). Rate $sender_rate / limit $sender_rate_limit
#Limit fast senders
hosts = !127.0.0.1: +relay_from_hosts
ratelimit = 100 / 5m / per_rcpt / strict
set acl_m102 = ${eval: ${sg{$sender_rate}{[.].*}{}} — $sender_rate_limit + 5}s
delay = $acl_m102
log_message = Ratelimit: Delay $acl_m102 for $sender_address ($sender_host_address). Rate $sender_rate / limit $sender_rate_limit
#Limit DSNs
warn condition = ${if and{\
{<{$recipients_count}{0}}\
{!eq{$sender_address_domain}{domain.com}}\
}}
senders =: postmaster@*: mailer-daemon@*
delay = 10s
log_message = Ratelimit: DSN delay 10s for $sender_address ($sender_host_address)
# END RATELIMIT SECTION
#Predefined acl variables for smtp_data level
warn set acl_m0 = $sender_address_domain
warn set acl_m1 = $domain
warn set acl_m2 = $sender_host_address
warn set acl_m3 = $sender_address
warn set acl_m4 = $local_part@$domain
#Verify recipient for our domains.
deny message = Unknown or disabled account
domains = +virt_domains
!local_parts = postmaster: *-admin: *-bounces: *-bounces+*: *-confirm: *-confirm+* :\
*-join: *-leave: *-owner: *-request: *-subscribe: *-unsubscribe
# Вот эта та самая строчка, проверка валидности аккаунтов и альясов на этапе check_rcpt, исключение только для служебных адресов рассылок.
!recipients = CHECK_1: CHECK_2
accept hosts =: +relay_from_hosts
control = dkim_disable_verify
# Разрешаем аутенфицированным отправителям дальше делать что угодно
accept authenticated = *
control = dkim_disable_verify
# Кому можно релеить почту, если нельзя, то 10 секунд задержки сессии.
deny message = relay not permitted
!domains = +local_domains: +virt_domains
delay = 10s
# Тут мы запрещаем отсылать почту с чужих хостов, используя наше доменное имя, опционально
#Deny non-authorized senders with our own domain prefix
deny condition = ${if match{$sender_address_domain}{domain.com}}
!hosts =: +relay_from_hosts: +adobe_hosts: +microsoft_hosts: net-dbm;DB_PREFIX/whitelist_hosts.db
message = Sender domain is not allowed here
log_message = Sender $sender_address is not authenticated
#Dictionary attack protection
#Start
warn condition = ${if > {${eval:$rcpt_fail_count}}{4}{yes}{no}}
log_message = Ratelimit: Detected Dictionary Attack (Let $rcpt_fail_count bad recipients though before engaging)
set acl_m7 = 1
warn condition = ${if eq {${acl_m7}}{1}{1}{0}}
ratelimit = 0 / 1h / strict / per_conn
log_message = Ratelimit: Increment Connection Ratelimit — $sender_fullhost because of Dictionary Attack
drop condition = ${if eq {${acl_m7}}{1}{1}{0}}
log_message = Ratelimit: Number of failed recipients exceeded
#End
# Здесь я реализовал проверку альясов на активность, раз в сутки парсер выбирает из лога exim записи и обновляет значение атрибута lastchange в формате "%Y%m%d" для данного альяса. Потом по значению этого атрибута можно проверять, пользуются ли альясом или нет.
Тоже самое сделано для аккаунтов, но дополнительного log_message, как для альяса, не требуется.
#Alias statistic
warn
domains = +virt_domains
log_message = ALIAS: $local_part@$domain
recipients = CHECK_1
# Отсылаем письмо в грейлист
#Greylist section
defer message = $sender_host_address is not yet authorized to deliver \
mail from <$sender_address> to <$local_part@$domain>. Please try later
log_message = Sender $sender_address greylisted
domains = +virt_domains
!sender_domains = partial1()dbm;DB_PREFIX/whitelist_grey_domains.db
!authenticated = *
condition = ${readsocket{/var/run/greylistd/socket}\
{--grey %s $sender_address $local_part@$domain}{5s}{}{false}}
accept
acl_check_vrfy_expn_etrn:
accept hosts = 127.0.0.1
deny
acl_check_data:
# Кого отправлять spamassassinу, опционально. В данном примере помещением тэга SPAM в заголовок письма занимается сам Exim.
Глобальный exim.filter тогда будет выглядеть так:
#
# no antispam check for relay hosts and authenticated users
# accept hosts =: +relay_from_hosts
# accept authenticated = *
#
# Antispam scan
# warn
# condition = ${if and {\
# {<{$message_size}{50k}}\
## {!eq{${mask:$acl_m2/16}}{192.168.0.0/16}}\
# {!eq{$sender_address}{}}\
## {!match_address{$sender_address}{dbm;DB_PREFIX/whitelist_spam_senders.db}}\
# {!match_domain{$acl_m0}{partial1()dbm;DB_PREFIX/whitelist_grey_domains.db}}\
## {match_domain{$acl_m1}{dbm;DB_PREFIX/domains_spam.db}}\
# }}
# spam = nobody:true/defer_ok
# set acl_m6 = $spam_score_int
# add new subj for global exim filter
# message = X-New-Subject: SPAM[$spam_score_int/80]: $rh_subject:
# condition = ${if and {\
# {def:spam_score_int}\
# {>{$spam_score_int}{80}}\
# }}
accept
accept hosts =: +relay_from_hosts: net-dbm;DB_PREFIX/whitelist_hosts.db
deny message = $sender_host_address is listed in $dnslist_domain ${if def:dnslist_text {($dnslist_text)}}
dnslists = sbl.spamhaus.org: xbl.spamhaus.org: bl.spamcop.net
accept
acl_check_dkim:
warn log_message = DKIM: Sender without DKIM signature
sender_domains = gmail.com: autodesk.com: paypal.com
dkim_signers = gmail.com: autodesk.com: paypal.com
dkim_status = none:invalid:fail
accept
acl_check_helo:
accept hosts =: +relay_from_hosts
#HELO is an open proxy
deny condition = ${if and {\
{isip{$sender_helo_name}}\
{eq{$sender_helo_name}{$sender_host_address}}\
}}
message = Open Proxy in HELO/EHLO (HELO was $sender_helo_name)
delay = 10s
#HELO is my hostname
deny condition = ${if match{$sender_helo_name}{$primary_hostname}}
message = Bad HELO — Host impersonating [$sender_helo_name]
#HELO is my address
deny condition = ${if eq{$interface_address}{$sender_helo_name}}
message = $interface_address is my address
accept
acl_check_mail:
accept hosts =: +relay_from_hosts
discard senders = dbm;DB_PREFIX/banned_senders.db: dbm;DB_PREFIX/scammers.db
#HELO required before MAIL
deny condition = ${if eq{$sender_helo_name}{}}
message = HELO/EHLO required before MAIL
accept
acl_check_rcpt:
#stub address
discard condition = ${if match{$local_part@$domain}{blackhole@domain.com}} # blackhole аккаунт
deny message = Restricted characters in address
local_parts = ^[.]: ^.*[@%!/|]
#Reverse DNS check
warn condition = ${if and{{def:sender_host_address}{!def:sender_host_name}}{yes}{no}}
!hosts =: +relay_from_hosts: net-dbm;DB_PREFIX/whitelist_hosts.db
control = no_pipelining
delay = 10s # Делаем задержку в 10 секунд из вредности, если хост не имеет обратки
log_message = X-Host-Lookup-Failed: Reverse DNS lookup failed for $sender_host_address
# RATELIMIT SECTION
#Keep authenticated users under control
warn authenticated = *
ratelimit = 100 / 5m / strict / $authenticated_id
set acl_m100 = ${eval: ${sg{$sender_rate}{[.].*}{}} — $sender_rate_limit + 10}s
delay = $acl_m100
log_message = Ratelimit: Delay $acl_m100 for $authenticated_id. Rate limit $sender_rate / $sender_rate_period
#Limit local senders, exclude mailing-list agent
warn condition = ${if !match{$sender_address_local_part}{bounces}}
hosts =: 127.0.0.1
ratelimit = 1000 / 1h / per_rcpt / strict / $sender_host_address
set acl_m101 = ${eval: ${sg{$sender_rate}{[.].*}{}} — $sender_rate_limit}s
delay = $acl_m101
log_message = Ratelimit: Delay $acl_m101 for $sender_address ($sender_host_address). Rate $sender_rate / limit $sender_rate_limit
#Limit fast senders
hosts = !127.0.0.1: +relay_from_hosts
ratelimit = 100 / 5m / per_rcpt / strict
set acl_m102 = ${eval: ${sg{$sender_rate}{[.].*}{}} — $sender_rate_limit + 5}s
delay = $acl_m102
log_message = Ratelimit: Delay $acl_m102 for $sender_address ($sender_host_address). Rate $sender_rate / limit $sender_rate_limit
#Limit DSNs
warn condition = ${if and{\
{<{$recipients_count}{0}}\
{!eq{$sender_address_domain}{domain.com}}\
}}
senders =: postmaster@*: mailer-daemon@*
delay = 10s
log_message = Ratelimit: DSN delay 10s for $sender_address ($sender_host_address)
# END RATELIMIT SECTION
#Predefined acl variables for smtp_data level
warn set acl_m0 = $sender_address_domain
warn set acl_m1 = $domain
warn set acl_m2 = $sender_host_address
warn set acl_m3 = $sender_address
warn set acl_m4 = $local_part@$domain
#Verify recipient for our domains.
deny message = Unknown or disabled account
domains = +virt_domains
!local_parts = postmaster: *-admin: *-bounces: *-bounces+*: *-confirm: *-confirm+* :\
*-join: *-leave: *-owner: *-request: *-subscribe: *-unsubscribe
# Вот эта та самая строчка, проверка валидности аккаунтов и альясов на этапе check_rcpt, исключение только для служебных адресов рассылок.
!recipients = CHECK_1: CHECK_2
accept hosts =: +relay_from_hosts
control = dkim_disable_verify
# Разрешаем аутенфицированным отправителям дальше делать что угодно
accept authenticated = *
control = dkim_disable_verify
# Кому можно релеить почту, если нельзя, то 10 секунд задержки сессии.
deny message = relay not permitted
!domains = +local_domains: +virt_domains
delay = 10s
# Тут мы запрещаем отсылать почту с чужих хостов, используя наше доменное имя, опционально
#Deny non-authorized senders with our own domain prefix
deny condition = ${if match{$sender_address_domain}{domain.com}}
!hosts =: +relay_from_hosts: +adobe_hosts: +microsoft_hosts: net-dbm;DB_PREFIX/whitelist_hosts.db
message = Sender domain is not allowed here
log_message = Sender $sender_address is not authenticated
#Dictionary attack protection
#Start
warn condition = ${if > {${eval:$rcpt_fail_count}}{4}{yes}{no}}
log_message = Ratelimit: Detected Dictionary Attack (Let $rcpt_fail_count bad recipients though before engaging)
set acl_m7 = 1
warn condition = ${if eq {${acl_m7}}{1}{1}{0}}
ratelimit = 0 / 1h / strict / per_conn
log_message = Ratelimit: Increment Connection Ratelimit — $sender_fullhost because of Dictionary Attack
drop condition = ${if eq {${acl_m7}}{1}{1}{0}}
log_message = Ratelimit: Number of failed recipients exceeded
#End
# Здесь я реализовал проверку альясов на активность, раз в сутки парсер выбирает из лога exim записи и обновляет значение атрибута lastchange в формате "%Y%m%d" для данного альяса. Потом по значению этого атрибута можно проверять, пользуются ли альясом или нет.
Тоже самое сделано для аккаунтов, но дополнительного log_message, как для альяса, не требуется.
#Alias statistic
warn
domains = +virt_domains
log_message = ALIAS: $local_part@$domain
recipients = CHECK_1
# Отсылаем письмо в грейлист
#Greylist section
defer message = $sender_host_address is not yet authorized to deliver \
mail from <$sender_address> to <$local_part@$domain>. Please try later
log_message = Sender $sender_address greylisted
domains = +virt_domains
!sender_domains = partial1()dbm;DB_PREFIX/whitelist_grey_domains.db
!authenticated = *
condition = ${readsocket{/var/run/greylistd/socket}\
{--grey %s $sender_address $local_part@$domain}{5s}{}{false}}
accept
acl_check_vrfy_expn_etrn:
accept hosts = 127.0.0.1
deny
acl_check_data:
# Кого отправлять spamassassinу, опционально. В данном примере помещением тэга SPAM в заголовок письма занимается сам Exim.
Глобальный exim.filter тогда будет выглядеть так:
exim.filter
if first_delivery then
headers remove X-Spam-Score:X-Spam-Report:X-Spam-Checker-Version:X-Spam-Status:X-Spam-Level
if "${if def:header_X-New-Subject: {there}}" is there
then
headers remove Subject
headers add «Subject: $rh_X-New-Subject:»
headers remove X-New-Subject
endif
endif
headers remove X-Spam-Score:X-Spam-Report:X-Spam-Checker-Version:X-Spam-Status:X-Spam-Level
if "${if def:header_X-New-Subject: {there}}" is there
then
headers remove Subject
headers add «Subject: $rh_X-New-Subject:»
headers remove X-New-Subject
endif
endif
#
# no antispam check for relay hosts and authenticated users
# accept hosts =: +relay_from_hosts
# accept authenticated = *
#
# Antispam scan
# warn
# condition = ${if and {\
# {<{$message_size}{50k}}\
## {!eq{${mask:$acl_m2/16}}{192.168.0.0/16}}\
# {!eq{$sender_address}{}}\
## {!match_address{$sender_address}{dbm;DB_PREFIX/whitelist_spam_senders.db}}\
# {!match_domain{$acl_m0}{partial1()dbm;DB_PREFIX/whitelist_grey_domains.db}}\
## {match_domain{$acl_m1}{dbm;DB_PREFIX/domains_spam.db}}\
# }}
# spam = nobody:true/defer_ok
# set acl_m6 = $spam_score_int
# add new subj for global exim filter
# message = X-New-Subject: SPAM[$spam_score_int/80]: $rh_subject:
# condition = ${if and {\
# {def:spam_score_int}\
# {>{$spam_score_int}{80}}\
# }}
accept
Вот, собственно, и все.
Запускаем Exim, отсылаем письмо ipupkin-у, смотрим логи…