Advanced Exim and Dovecot configuration linked to OpenLDAP

This article discusses advanced settings for Exim, Dovecot, and OpenLDAP to work together based on my experience with these applications. Perhaps someone will find something interesting and new for himself - this was the purpose of writing the next howto on this topic.

Why Exim and OpenLDAP, not Postfix and MySQL, for example? Postfix works fine “out of the box”, but if you need something extraordinary, very soon Postfix turns into a clumsy monster, hung with pearl scripts, Exim has a monstrous power meta-language configuration and allows you to do without third-party scripts and crutches. I considered MySQL redundant for my tasks and replaced it with standard OpenLDAP, all the more LDAP is used for the address book. Dovecot is very fast and easy to configure, plus it integrates perfectly with both Exim and OpenLDAP.

So, we install the necessary software, everything is standard here (apt-get, yum, etc.). I used Gentoo, so emerge openldap dovecot exim (Exim and dovecot must have ldap support).

USE flags used during assembly:

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

The first in line will be OpenLDAP, all mail accounts, groups and alliances will be stored in its database, and OpenLDAP will also be used as an address book for email clients. For simplicity and convenience, I do not use slapd-config, but store all the settings in the text slapd.conf.

Since the standard OpenLDAP does not have a suitable scheme for working with mail, I used my modified version of phamm.schema and the default phamm-vacation.schema .

Configure OpenLDAP


I omit the initial configuration, creating basedn dc = domain, dc = com and ssl certificates for OpenLDAP, since everything is standard here.

Config 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


Be sure to configure replication for OpenLDAP (again, everything is standard here, so I omit the details). The only thing I had to build OpenLDAP with my hands to support sssvlv (Server Side Sorting and Virtual List View) in the address book (./configure --enable-ipv6 = no --enable-syncprov = yes --enable-sssvlv = yes - with-tls = yes).
Also, for sssvlv to work correctly in Outlook, I had to patch OpenLDAP sources.
Patch
--- 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)


The second server will act as a read-only address book.

Config slapd.conf for the second LDAP server
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

I use phpldapadmin to administer the LDAP database, as it is lightweight, convenient and has support for XML templates, which allows you to flexibly configure the necessary templates for creating accounts. Unfortunately, the project has long been abandoned by the author and is no longer developing.

For example, an example template for creating an email account.
The code
 # Базовый posix аккаунт
  # Указываем класс из нашей почтовой phamm схемы
  # Указываем класс из phamm-vacation схемы
 First Nameldap-uid.png11Last Name=autoFill(cn;%givenName% %sn%)=autoFill(uid;%givenName|0-1/l%%sn/l%)=autoFill(loginShell;/sbin/nologin)=autoFill(FTPStatus;enabled)21Common Name3UID=autoFill(homeDirectory;/home/%uid%)=autoFill(mailbox;/home/%uid%/Maildir)=autoFill(mail;%uid%@domain.com)=autoFill(company;My Company)41

When creating, I use the following LDAP database scheme:

ou = people, dc = domain, dc = com - a container for storing mail accounts;
ou = groups, dc = domain, dc = com - a container for storing posix groups;
ou = services, dc = domain, dc = com - a container for storing system service accounts (posix accounts);
ou = aliases, dc = domain, dc = com - a container for storing mail alliances.

When creating an email account, many attributes of the phamm scheme modified by me are used, the use of which is beyond the scope of this article (for example, createMaildir or Backup). I’ll only mention those that are used in search filters.
accountActive = TRUE | FALSE - allows you to temporarily enable / disable an account or alias;
vacationInfo - contains the text Out of Office messages;
vacationActive - allows you to enable / disable OoO message;
quota - here it’s clear (for example: quota = 4G)

So, OpenLDAP is configured and started, replication works and using phpldapadmin the first ipupkin test account was created with the ipupkin@domain.com mail address. In the future, only one domain.com domain will be used, therefore, create the domain group in our ou = groups posix container and add ipupkin to this group.

In this example, all services run on the same Linux server and it is logical to use the ldap database to identify the user in the system, so we shift this procedure to the shoulders of Name Service Switch (nss) or System Security Services Daemon (sssd). Also, the advantage of this solution is the easy adaptation with the Samba domain, if necessary.
First you need to make sure that the package is installed nss_ldap (or libnss-ldapd).
In /etc/nsswitch.conf we change the lines from compat to ldap (or winbind in the case of a Samba domain):
passwd: files ldap
shadow: files ldap
group: files ldap

Create the file /etc/ldap.conf with the following contents:

ldap.conf
uri ldap: //127.0.0.1
uri ldap: //192.168.0.1 # Second fallback ldap server

base dc = domain, dc = com
binddn uid = proxyagent, ou = services, dc = domain, dc = com # Pre-created in ou = services posix account specified in the slapd.conf
bindpw *****

pam_filter objectclass = posixAccount
pam_login_attribute the uid
pam_check_host_attr the no
pam_lookup_policy the no
pam_member_attribute memberUid
pam_min_uid 1000
pam_max_uid 65535
ssl start_tls # use TLS instead of the SSL
# Specify the path to the certificate
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

We check that nss is working and the home directory has been created (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

Now when the ipupkin user is recognized by the system, ipupkin needs to be able to receive and send mail.

Configure Dovecot


When configuring Dovecot, I deleted all the nested monstrous default configs and for convenience I created only two - dovecot.conf and dovecot-ldap.conf.

dovecot.conf
auth_cache_negative_ttl = 10 mins
auth_debug = the no
auth_debug_passwords = the no
auth_mechanisms = plain the login # Use TLS, so allow plain
'base_dir' = / var / the run / dovecot /
default_vsz_limit = 1024 M
disable_plaintext_auth = the no
dotlock_use_excl = yes or The
lda_mailbox_autocreate = yes or The # Required
lda_mailbox_autosubscribe = yes # Always
listen = *
mmap_disable = yes
mail_fsync = always
mail_nfs_storage = no
mail_nfs_index = no
mail_debug = no
mail_location = maildir: ~ / Maildir # Where to look for a mailbox, the maildir value is taken from the homeDirectory attribute (in our case it is / home / ipupkin), accordingly the full path will be / 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 = ssl_cert = ssl_key = ssl_verify_client_cert = no
verbose_ssl = no

protocols = imap pop3 sieve

userdb {
args = /etc/dovecot/dovecot-ldap.conf # Connect our ldap config
driver = ldap
}

passdb {
args = /etc/dovecot/dovecot-ldap-pass.conf # According to the recommendations of the authors dovecot, a symbolic link to 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
}

# Used by managesieve webmail plugin 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
}
}

# When the mailbox is 90% full, the following
service quota-warning
script is executed { executable = script /etc/dovecot/quota-warning.sh # The script itself looks like this
Script
#!/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 # Haq for Autluk
mail_plugins = quota imap_quota mail_log notify
}

protocol pop {
pop3_client_workarounds = outlook-no-nuls oe-ns-eoh # Various hacks for Autluk
# pop3_uidl_format =% 08Xu% 08Xv # used when storing Autluk mail on the server, this format has a problem with Autluk 2013
pop3_uidl_format =% g
pop3_fast_size_lookups = # yes or the More here section Maildir perfomance.
mail_plugins =
}

# Logical Delivery Agent (LDA) service used by Exim to deliver mail.
protocol lda {
hostname = domain.com
mail_fsync = optimized
mail_plugins = sieve quota
postmaster_address = postmaster@domain.com
log_path =
info_log_path =
}

protocol sieve {
}

# Various optional add-
ons 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 # Specify the percentage of mailbox filling that triggers the script 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-ldap.conf
hosts = 127.0.0.1
dn = uid = proxyagent, ou = services, dc = domain, dc = com
dnpass = *****
tls = no
auth_bind = no
auth_bind_userdn = uid =% u, ou = people, dc = domain, dc = com
ldap_version = 3
base = dc = domain, dc = com
deref = never
scope = subtree
user_attrs = homeDirectory = home, uidNumber = uid, gidNumber = gid, quota = quota_rule = *: storage =% $
user_filter = (& ( objectClass = VirtualMailAccount) (accountActive = TRUE) (uid =% n) (mail = *))
pass_attrs = uid = user, userPassword = password
pass_filter = (& (objectClass = VirtualMailAccount) (accountActive = TRUE) (uid =% u) (mail = *))
default_pass_scheme = SSHA


Check that there are no errors (dovecot -a) and run dovecot.

Final: Exim Setup


The main goal when configuring Exim was to maximize the rejection of any scripts and implement all the functionality only using Exim. Only as an exception are Debian greylistd in python and pearl barley amavisd-new used.
When configuring Exim, two files will also be used - the main exim.conf and acl_smtp for ACL rules.
Also optionally in the config there is a router and transport for mailman.

exim.conf
CONFIG_PREFIX = / etc / exim
ACL_PREFIX = CONFIG_PREFIX / acls # all ACL configs
DB_PREFIX = / var / spool / exim / db # are stored here; it is advisable to use tmpfs to increase performance

# Templates are Exim's strength and make it easy to replace a long line with a short word.
# Define templates for 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 # We specify how to connect to the ldap servers, the second server will be used as fallback.

INTERFACE = your_external_ip # Specify the external ip on which Exim will hang
BASEDN = dc = domain, dc = com # basedn server lap

# This section shows the most important templates that specify Exim logic

# Check the alias if the alias address matches the mail attribute value in the aliases container, the alias must also belong to the VirtualMailAlias ​​class and have a value TRUE for the accountActive attribute 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}))}}

# Account check if the recipient address matches the value of the mail attribute in the people container, account must also belong to class Virtual MailAccount and set to TRUE for the accountActive attribute
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}))}}

# Check for a suspended account, the account cannot receive mail, but remains in the system (relevant for employees on maternity leave)
CHECK_3 = $ {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) (accountSuspend = TRUE) (mail = $ {quote_ldap: $ local_part @ $ domain}))}}

# List of members of the alliance to whom to forward mail, value maildrop attribute
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}))}}

# Path to the mailbox, value of the mailbox attribute
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}))}}

# Text of the OoO message, attribute value 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 - it seems they have not fixed
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 # Since we have only one domain, we indicate it. If you use many domains, then you need to create a template for selecting domains from the database.
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 # Do not offer SMTP AUTH lokalhostu
bounce_message_file = CONFIG_PREFIX / bounce.msg # specifies the format of bounce messages that I have this:

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.
****

bounce_return_size_limit = 100K
delay_warning = 15m: 1h: 99d
deliver_queue_load_max = 40
disable_ipv6
exim_group = vmail # It is desirable that all mail services (dovecot, spamassassin, clamav, etc.) work under one gid
exim_user = vmail # it is desirable to do allass clamav, etc.) working under one the uid
the headers_charset = the UTF-8 # It is advisable to include
ignore_bounce_errors_after = 0s
local_scan_timeout for = 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 # Logs are sent to syslog service
syslog_processname = exim
system_filter = DB_PREFIX / exim.filter # Global exim filter, I mostly do not use
timeout_frozen_after = 7d
tls_advertise_hosts =! 127.0.0.1 # Do not offer TLS to localhost

# Specify certificate
paths tls_certificate = /
etc /exim/ ssl_pc_pc = /etc/exim/ssl/mail_key_new.pem
tls_verify_certificates = /etc/exim/ssl/ca.pem

# Define the format of the message header
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>}} »

# We connect the antivirus and antispam directly, since LDAP is not used here, then everything is standard, so we omit the setting.
# av_scanner = clamd: / tmp / clamd
# spamd_address = 127.0.0.1 783

begin acl

# Connect our ACL config (see below)
.include ACL_PREFIX / acl_smtp

# Create
begin routers

# router for outgoing mail
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

# Incoming routers

# An optional transport for amavis (we will make one exception to the pearl :))
#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 # Do not respond to the localhost and senders specified in autoreply.noanswer.db
condition = $ {if and {\
{! eq {CHECK_VACATION} {}} \ # Our template
{! match {$ h_precedence:} {junk | bulk | list}} \ # Do not reply to newsletters
{! def: header_Auto-Submitted:} \
{! def: header_List-Id:} \
}}
no_verify
no_expn
unseen
transport = auto_responder

# Suspended accounts, all incoming mail for a suspended account is sent to the "black hole"
suspended:
driver = redirect
domains = + virt_domains
condition = CHECK_3
forbid_file
forbid_pipe
forbid_filter_reply = true
data =: blackhole:
no_more

# Virtual
aliases aliases:
driver = redirect
domains =! + local_domains
condition = CHECK_1 # There is a double check (the first in acl_smtp), if the current account also has an alias, I didn’t come up with anything better
forbid_file
forbid_pipe
forbid_filter_reply = true
data = CHECK_DATA # To whom to forward letters
allow_fail
allow_defer

mailman_router:
driver = accept
domains = domain.com
require_files = MM_LISTCHK # Instead of checking against a file, you can check the value of the aliasType = DL attribute, for example,
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 # We shift the delivery of the letter to the mailbox on the shoulders dovecot
lda cannot_route_message = Unknown account # Dovecot answered no, give up
no_more

####################### ######################################################
begin transports
###################################################### ##############

remote_smtp:
driver = smtp
helo_data = mail.domain.com
max_rcpt = 500
# connect DKIM
dkim_domain = domain.com
dkim_selector = dkim
dkim_private_key = DB_PREFIX / dkim.private.key
dkim_can = relaxed

auto_responder:
driver = autoreply
from = "$ {local_part} @ $ {domain}"
to = "$ {reply_address}"
once = "/ var / spool / exim / autoreply / $ {local_part} @ $ {domain}"
once_repeat = 1d # Answer the sender once a day, although the logic can also be transferred to LDAP (see phamm-vacation.schema)
headers = “Content-Type: text / plain; charset = utf-8 \ nContent-Transfer-Encoding: 8bit »
subject = $ {rfc2047: Auto-Reply: $ h_subject:}
text = VACATION # Our template with OoO text message
body_only
no_return_message

# The same transport for
dovecot dovecot_lda:
driver = pipe
command = / usr / libexec / dovecot / dovecot-lda -f "$ sender_address" -d "
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

# All system root messages are sent to one box
begin rewrite
root @ * collector@domain.com Ttbcr

# SMTP
authenticators 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's main caliber weapon is Access Control Lists.
Actually, the whole article was conceived for the sake of a single construction in the smtp_rcpt section.

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 .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 account

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 # Make a delay of 10 seconds from harm if the host has no return
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
= ratelimit log_message: the DSN delay The 10s for $ sender_address ($ sender_host_address)

# the END ratelimit the SECTION

#Predefined the acl variables An smtp_data level for
a warn set acl_m0 = $ sender_address_domain
a warn set acl_m1 = $ domain
a warn set acl_m2 = $ sender_host_address
a warn set acl_m3 = $ sender_address
a 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

# This is the same line, checking the validity of accounts and alliances at the check_rcpt stage, an exception is only for the service addresses of mailings.
! recipients = CHECK_1: CHECK_2

accept hosts =: + relay_from_hosts
control = dkim_disable_verify
# Allow authenticated senders to do anything else
accept authenticated = *
control = dkim_disable_verify

# Who can relay mail, if not, then 10 seconds of session delay.
deny message = relay not permitted
! domains = + local_domains: + virt_domains
delay = 10s

# Here we prohibit sending mail from foreign hosts using our domain name, optional
#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

# Here I implemented an alias check for activity, once a day the parser selects from the log Exim records and updates the value of the lastchange attribute in the format "% Y% m% d" for this alliance. Then, by the value of this attribute, you can check whether the alias is used or not.
The same is done for accounts, but an additional log_message, as for an alliance, is not required.
#Alias ​​statistic
warn
domains = + virt_domains
log_message = ALIAS: $ local_part @ $ domain
recipients = CHECK_1

# Send an email to the greylist
#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:

# Who to send to spamassassin, optional. In this example, Exim takes care of placing the SPAM tag in the message header.

The global exim.filter will then look like this:

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


#
# 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


That, in fact, is all.

We start Exim, we send the letter to ipupkin, we look at logs ...

Also popular now: