Exim
Exim is a message transfer agent (MTA) under the GNU license.
Configuration
The configuration file should contain:
- Macros
- Main configuration option settings
- Sections for these sections in this order: acl, authenticators, rewrite, routers, transports, retry
Lines can be commented out using #
.
Main configuration settings are values that are defined in the file as foo = bar
.
To find out what a particular setting is, search the index: https://www.exim.org/exim-html-current/doc/html/spec_html/ch-option_index.html
Macros
Macros define a constant or string expansion which you can use later in the configuration file. These are written in all upper case.
Main Configuration
add_environment = PATH=/usr/local/sbin::/usr/local/bin::/sbin::/bin::/usr/sbin::/usr/bin::/sbin::/bin
keep_environment = X-SOURCE : X-SOURCE-ARGS : X-SOURCE-DIR
addresslist secondarymx = *@partial-lsearch;/etc/secondarymx
auto_thaw = 7d
callout_domain_negative_expire = 1h
callout_negative_expire = 1h
check_rfc2047_length = false
chunking_advertise_hosts = 198.51.100.1
daemon_smtp_ports = 25 : 465 : 587
tls_on_connect_ports = 465
never_users = root
deliver_queue_load_max = 3
helo_accept_junk_hosts = *
ignore_bounce_errors_after = 1d
timeout_frozen_after = 5d
split_spool_directory = yes
local_from_check = false
log_selector = +incoming_port +smtp_connection +all_parents +retry_defer +subject +arguments +received_recipients
message_body_newlines = true
message_body_visible = 5000
openssl_options = +no_sslv2 +no_sslv3 +no_tlsv1 +no_tlsv1_1
queue_only_load = 6
remote_max_parallel = 10
rfc1413_query_timeout = 0s
smtp_accept_max = 100
smtp_accept_queue_per_connection = 30
smtp_connect_backlog = 50
smtp_enforce_sync = false
smtp_receive_timeout = 165s
smtputf8_advertise_hosts = :
spamd_address = 127.0.0.1 783 retry=30s tmo=3m
system_filter = /etc/cpanel_exim_system_filter
system_filter_group = cpaneleximfilter
system_filter_user = cpaneleximfilter
timezone = America/Los_Angeles
tls_advertise_hosts = *
tls_require_ciphers = ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256
untrusted_set_sender = *
- add_environment
- Environment variables to set
- keep_environment
- Environment variables to import. eg.
^LDAP
- qualify_domain
- Used to construct a complete email address from local login names, for messages from local processes. Defaults to
primary_hostname
if not set. - primary_hostname
- The FQDN, otherwise if unset, Exim uses the uname system function to obtain the hostname
- allow_domain_literals
- To allow addresses of from
username@[10.1.1.1]
rather than domain name. Typically disabled nowadays - never_users
- Local message deliveries are normally run in processes that are setuid to the recipient. This list of users define which deliveries should not run as. Users that are on this list will have their delivery deferred. Build option with
FIXED_NEVER_USERS
also defines a set of users that cannot be overridden. - deliver_queue_load_max
- Defers delivery if system load exceeds the fixed point value.
- helo_accept_junk_hosts
- Accepts malformed HELO or ELHO commands for incoming SMTP mail to accomodate SMTP clients that send syntactic junk.
- ignore_bounce_errors_after
- Number of days until failing bounce messages are discarded
- timeout_frozen_after
- Number of days before any frozen messages (when a bouncing message encounters a permanent failure) are discarded.
- split_spool_directory
- Exim queues are stored in the spool directory. Large queues should have the directory split to avoid file system degradation from many files in one directory.
- check_rfc2047_length
- RFC 2047 allows for a line length of 76 characters. If set to true, any messages that violate this standard will fail. Set to false for better compatibility.
- rfc1413_hosts, rfc1413_query_timeout
- RFC 1413 deals with ident callbacks. Few hosts offer this service. Setting the timeout to 0 seconds will prevent ident callbacks for all incoming SMTP connections.
- domainlist local_domains = @
- identify domains that are to be delivered on the local host. The
@
is a special form of entry which means "the name of the local host" - domainlist relay_to_domains =
- Lists domains that allows this server to relay to. Default is empty and no relaying is permitted
- hostlist relay_from_hosts
- List of domains or IPs to permit relaying from. Default is the IP address of the loopback device.
Lists
There are named lists which are defined by the type of list followed by the name of the list and they come in the form of list-type list-name = list-values
.
There are 4 types of named lists:
- domainlist
- hostlist
- addresslist
- localpartlist
Items in lists are separated with a colon :
but can be changed by first defining a list with <FS
, where FS
is the field separator character. Items can be negated with an exclamation !
. List values may refer to other named lists with a plus +
.
Patterns that start with the name of a single-key lookup type followed by a semicolon must be followed by a filename suitable for the lookup type. For example, lsearch;/etc/trustedmailhosts
will search for a key in the file /etc/trustedmailhosts
which should contain data formatted as key: value
.
Lists are checked from left to right. The first match or negation that is found is the result.
Security
You can define the TLS certificate and private key files based on the presence of whether a file exists or not. See cPanel's configs:
tls_certificate = ${if and \
{ \
{gt{$tls_in_sni}{}} \
{!match{$tls_in_sni}{/}} \
} \
{${if exists {/var/cpanel/ssl/domain_tls/$tls_in_sni/combined} \
{/var/cpanel/ssl/domain_tls/$tls_in_sni/combined} \
{${if exists {${sg{/var/cpanel/ssl/domain_tls/$tls_in_sni/combined}{(.+/)[^.]+(.+/combined)}{\$1*\$2}}} \
{${sg{/var/cpanel/ssl/domain_tls/$tls_in_sni/combined}{(.+/)[^.]+(.+/combined)}{\$1*\$2}}} \
{/etc/exim.crt} \
}} \
}} \
{/etc/exim.crt} \
}
tls_privatekey = ${if and \
{ \
{gt{$tls_in_sni}{}} \
{!match{$tls_in_sni}{/}} \
} \
{${if exists {/var/cpanel/ssl/domain_tls/$tls_in_sni/combined} \
{/var/cpanel/ssl/domain_tls/$tls_in_sni/combined} \
{${if exists {${sg{/var/cpanel/ssl/domain_tls/$tls_in_sni/combined}{(.+/)[^.]+(.+/combined)}{\$1*\$2}}} \
{${sg{/var/cpanel/ssl/domain_tls/$tls_in_sni/combined}{(.+/)[^.]+(.+/combined)}{\$1*\$2}}} \
{/etc/exim.key} \
}} \
}} \
{/etc/exim.key} \
}
tls_advertise_hosts = *
tls_on_connect_ports = 465
tls_require_ciphers = ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256
Embedded Perl
The Perl interpreter can be used as part of the string expansion process, allowing for the ability to write sophisticated string transformations. Exim should be compiled with EXIM_PERL = perl.o
to support the perl interpreter.
When the perl_startup = do '/etc/exim.pl'
is included in the configuration, it will be executed in the Perl interpreter. Subroutines defined there can then be referenced later in the form of ${perl{func}{argument1}{argument2
}. Up to 8 arguments can be passed.
ACLs
Access Control Lists or ACLs. The ACL section of the configuration file starts with begin acl
. All ACLs that were referenced in the main configuration section must be defined here.
For example, cPanel will define and use the following ACLs.
acl_not_smtp = acl_not_smtp
acl_smtp_connect = acl_smtp_connect
acl_smtp_data = acl_smtp_data
acl_smtp_helo = acl_smtp_helo
acl_smtp_mail = acl_smtp_mail
acl_smtp_notquit = acl_smtp_notquit
acl_smtp_quit = acl_smtp_quit
acl_smtp_rcpt = acl_smtp_rcpt
The ACL statements are considered in order, until the recipient address is either accepted or rejected.
cPanel has a ton of rules. However, this illustrates how a RCPT is denied if the user's $HOME/etc/$DOMAIN/.suspend_incoming
file exists.
acl_check_rcpt:
accept hosts = :
# implemented for "suspend incoming email" feature
deny
domains = +local_domains
condition = ${if exists {${extract{5}{::}{${lookup passwd{${lookup{$domain}lsearch{/etc/userdomains}{$value}}}{$value}}}}/etc/\.$local_part\@$domain\.suspended_incoming}}
message = Mail to ${lc:$local_part@$domain} has been suspended
log_message = Mail to ${lc:$local_part@$domain} has been suspended
ACL settings
- acl_smtp_rcpt
- Used during an incoming SMTP session for every recipient of a message; on every RCPT command
- acl_smtp_data
- Used after the content of the message have been received
Routers
Router configuration starts with begin routers
.
Routers handles addresses whose domains are not local. An address is passed through each router until it is either accepted or rejected. There are a few types of routers:
- lookuphost
- A router that looks up remote domains via DNS
- domainlist
- Routes remote domains using a locally supplied file
- queryprogram
- A router that runs an external program
- manualroute
- accept
- accept an address if it matches some condition. eg.
domains = !$primary_hostname : +local_domains
to accept all addresses hosted on this server. - redirect
- Resolves loacl part aliases and handling user's personal .forward files.
- ipliteral
- Router that handles IP Literal addresses and are no longer common
If a router is unable to lookup a MX record, the message is to be deferred unless the pass_on_timeout
is defined.
All routers, regardless of the type supports
- condition
- The string is expanded, and if the result is a forced failure, or an empty string, or one of the strings “0” or “no” or “false” (checked without regard to the case of the letters), the router is skipped, and the address is offered to the next one.
Specifically for redirect routers:
- data
- The contents of data are expanded, and then used as the list of forwarding items, or as a set of filtering instructions. If the expansion is forced to fail, or the result is an empty string or a string that has no effect (consists entirely of comments), the router declines.
cPanel implements a check_mail_permissions
redirect router that ensures the user can receive/send messages based on a variety of conditions. These conditions are checked with the check_mail_permissions
Perl function and is defined as:
# Check mail permissions. Sets up enforce_mail_permissions_data as enforce_mail_permissions_results
check_mail_permissions:
domains = ! +local_domains
condition = ${if eq {$authenticated_id}{root}{0}{1}}
ignore_target_hosts = +loopback : 64.94.110.0/24
driver = redirect
allow_filter
reply_transport = address_reply
user = mailnull
expn = false
condition = "${perl{check_mail_permissions}}"
data = "${perl{check_mail_permissions_results}}"
#
# If check_mail_permissions needs to defer or fail a message it is done here
# enforce_mail_permissions is true if enforce_mail_permissions_data has content
#
enforce_mail_permissions:
domains = ! +local_domains
ignore_target_hosts = +loopback : 64.94.110.0/24
condition = ${if eq {$authenticated_id}{root}{0}{1}}
driver = redirect
allow_fail
allow_defer
expn = false
condition = "${perl{enforce_mail_permissions}}"
data = "${perl{enforce_mail_permissions_results}}"
#
# Increments max emails per hour if needed
#
increment_max_emails_per_hour_if_needed:
domains = ! +local_domains
ignore_target_hosts = +loopback : 64.94.110.0/24
condition = ${if eq {$authenticated_id}{root}{0}{1}}
driver = redirect
allow_fail
no_verify
one_time
expn = false
condition = "${perl{increment_max_emails_per_hour_if_needed}}"
data = ":unknown:"
Breaking down what this actually does...
- Perl code
check_mail_permissions
will:- return 'yes' if user has their incoming/outgoing email suspended. check_mail_permissions_results will include a message and reason for a bounce message.
- return 'no' if any of these conditions occur:
- uid == mailtrap or nobody, enforce_mail_permissions_data = :fail: cannot relay mail as $uid
- uid is a demo user, enforce_mail_permissions_data = :fail: is a demo user
- domain is empty and sender is nobody, enforce_mail_permissions_data = :fail: mail sent by nobody rejected
- file '/var/cpanel/email_send_limits/max_deferfail_$domain' exists with deferfail values. enforce_mail_permissions_data = :fail: domain exceeds max defers per hour
- maximum emails by this domain per hour exceeds some defined limit.
- if beyond the cutoff, reject. enforce_mail_permissions_data = :fail: domain has exceeded max emails per hour
- if below the cutoff, defer. enforce_mail_permissions_data = :defer: domain exceeds max emails per hour
- outgoing mail is suspended for $domain. enforce_mail_permissions_data = :fail: domain has outgoing mail suspended
- outgoing mail for the sender is on hold. enforce_mail_permissions_data = :defer: $domain outgoing mail hold
- outgoing mail for the sender is suspended. enforce_mail_permissions_data = :fail: $domain outgoing mail suspended
- Send notifications if user exceeds daily limit by making /var/cpanel/email_send_limits/daily_notify/$domain.
- enforce_mail_permissions_results returns enforce_mail_permissions_data. If something is set, the enforcement router will do it.
- Increment max emails per hour by calling increment_max_emails_per_hour_if_needed, which appends '1' to a file at /var/cpanel/email_send_limits/track/$domain/$h.$d.$m.$y
The section after is to deliver emails destined to remote hosts or fail them otherwise.
# requires perl sender_domain_can_dkim_sign to check if dkim should be used
#
# Lookup host router for remote smtp and ignores verisign site finder 'service'
# This matches lookup exactly except we look for X-Precedence and Precedence so
# we can determinte what is an auto responder message in the log.
# Note: there is nothing to
# prevent X-Precedence from being added to non-autoresponded messages so this is for
# logging reasons only
#
# Note: Boxtrapper sets Precedence to auto_reply
#
autoreply_dkim_lookuphost:
driver = dnslookup
domains = ! +local_domains
condition = "${perl{sender_domain_can_dkim_sign}}"
condition = "${if or {{match{$h_Precedence:}{auto}}{match{$h_X-Precedence:}{auto}}}{1}{0}}"
#ignore verisign to prevent waste of bandwidth
ignore_target_hosts = +loopback : 64.94.110.0/24
headers_add = "${perl{mailtrapheaders}}"
transport = dkim_remote_smtp
#
# Lookup host router for remote smtp and ignores verisign site finder 'service' and uses domain keys
#
dkim_lookuphost:
driver = dnslookup
domains = ! +local_domains
condition = "${perl{sender_domain_can_dkim_sign}}"
#ignore verisign to prevent waste of bandwidth
ignore_target_hosts = +loopback : 64.94.110.0/24
headers_add = "${perl{mailtrapheaders}}"
transport = dkim_remote_smtp
#
# Lookup host router for remote smtp and ignores verisign site finder 'service'
# This matches lookup exactly except we look for X-Precedence and Precedence so
# we can determinte what is an auto responder message in the log.
# Note: there is nothing to
# prevent X-Precedence from being added to non-autoresponded messages so this is for
# logging reasons only
#
# Note: Boxtrapper sets Precedence to auto_reply
#
autoreply_lookuphost:
driver = dnslookup
domains = ! +local_domains
condition = "${if or {{match{$h_Precedence:}{auto}}{match{$h_X-Precedence:}{auto}}}{1}{0}}"
#ignore verisign to prevent waste of bandwidth
ignore_target_hosts = +loopback : 64.94.110.0/24
headers_add = "${perl{mailtrapheaders}}"
transport = remote_smtp
#
# Lookup host router for remote smtp and ignores verisign site finder 'service'
#
lookuphost:
driver = dnslookup
domains = ! +local_domains
#ignore verisign to prevent waste of bandwidth
ignore_target_hosts = +loopback : 64.94.110.0/24
headers_add = "${perl{mailtrapheaders}}"
transport = remote_smtp
# This router routes to remote hosts over SMTP by explicit IP address,
# given as a "domain literal" in the form [nnn.nnn.nnn.nnn]. The RFCs
# require this facility, which is why it is enabled by default in Exim.
# If you want to lock it out, set forbid_domain_literals in the main
# configuration section above.
literal:
driver = ipliteral
domains = ! +local_domains
ignore_target_hosts = +loopback : 64.94.110.0/24
headers_add = "${perl{mailtrapheaders}}"
transport = remote_smtp
#!!# This new router is put here to fail all domains that
#!!# were not in local_domains in the Exim 3 configuration.
#
# Trap Failures to Remote Domain
#
fail_remote_domains:
driver = redirect
domains = ! +local_domains : ! localhost : ! localhost.localdomain
allow_fail
data = ":fail: The mail server could not deliver mail to $local_part@$domain. The account or domain may not exist, they may be blacklisted, or missing the proper dns entries."
Everything else after deals with the cPanel email features such as forwarders, autoresponders, default/catch-all address, custom filters.
Transports
Transports start with begin transports
. Transports define how an email message is to be handled.
Transports have different drivers for messages that are destined to different destinations. Typically, messages destined for remote hosts are sent via SMTP using the smtp
driver. Other messages might be handled by a program (such as mailman) and are piped to a command. Finally, local messages can be transported using the Local Mail Transport Protocol (LMPT) where messages can be sent to a socket such as in the case with Dovecot.
Dovecot
These are the transport rules that cPanel configures for Exim to work with Dovecot.
# This transport is used for handling pipe deliveries generated by alias
# or .forward files. If the pipe generates any standard output, it is returned
# to the sender of the message as a delivery error. Set return_fail_output
# instead of return_output if you want this to happen only when the pipe fails
# to complete normally. You can set different transports for aliases and
# forwards if you want to - see the references to address_pipe below.
address_directory:
driver = pipe
command = /usr/libexec/dovecot/dovecot-lda -f $sender_address -d ${perl{convert_address_directory_to_dovecot_lda_destination_username}} -m ${perl{convert_address_directory_to_dovecot_lda_mailbox}}
message_prefix =
message_suffix =
log_output
delivery_date_add
envelope_to_add
return_path_add
temp_errors = 64 : 69 : 70: 71 : 72 : 73 : 74 : 75 : 78
# This transport is used for handling deliveries directly to files that are
# generated by aliassing or forwarding.
address_file:
driver = pipe
command = /usr/libexec/dovecot/dovecot-lda -e -f $sender_address -d ${perl{convert_address_directory_to_dovecot_lda_destination_username}} -m ${perl{convert_address_directory_to_dovecot_lda_mailbox}}
message_prefix =
message_suffix =
log_output
delivery_date_add
envelope_to_add
return_path_add
temp_errors = 64 : 69 : 70: 71 : 72 : 73 : 74 : 75 : 78
# For email with a bcc:
dovecot_delivery_no_batch:
driver = lmtp
socket = /var/run/dovecot/lmtp
batch_max = 1
rcpt_include_affixes
delivery_date_add
envelope_to_add
return_path_add
# For email with a bcc:
dovecot_virtual_delivery_no_batch:
driver = lmtp
socket = /var/run/dovecot/lmtp
batch_max = 1
rcpt_include_affixes
delivery_date_add
envelope_to_add
return_path_add
# For email with a bcc:
dovecot_delivery_no_batch:
driver = lmtp
socket = /var/run/dovecot/lmtp
batch_max = 1
rcpt_include_affixes
delivery_date_add
envelope_to_add
return_path_add
# For email with a bcc:
dovecot_virtual_delivery_no_batch:
driver = lmtp
socket = /var/run/dovecot/lmtp
batch_max = 1
rcpt_include_affixes
delivery_date_add
envelope_to_add
return_path_add
dovecot_delivery:
driver = lmtp
socket = /var/run/dovecot/lmtp
batch_max = 200
rcpt_include_affixes
delivery_date_add
envelope_to_add
return_path_add
dovecot_virtual_delivery:
driver = lmtp
socket = /var/run/dovecot/lmtp
batch_max = 200
rcpt_include_affixes
delivery_date_add
envelope_to_add
return_path_add
Authenticators
For systems that are using Dovecot as the MDA, it's a good idea to use the Dovecot driver as an authenticator so that users can use the same credentials to authenticate via SMTP as they do for accessing IMAP or POP3 via Dovecot.
A nice feature with Dovecot is the ability to use a custom dict server as a source for email users. By utilizing Dovecot as an authentication source in Exim, the single point of truth for email accounts lies with the custom dict server used by Dovecot thereby simplifying the authentication scheme of your mail server. This in fact is what cPanel does with the dict server provided by the cpsrvd daemon.
Use PLAIN and LOGIN SMTP authorization protocols with the dovecot driver. The plain text password will be checked by Dovecot against the hashed password.
In the cPanel config example below, the server_condition
value is set to false if:
- auth1 is '/'
- auth1 is an email address, and the domain is in /etc/demodomains
- auth1 is a username, and the username is in /etc/demousers
The authentication mechanism is advertised (server_advertise_condition
) if any:
- tls_ciper is defined, or
- server address is in loopback address
dovecot_plain:
driver = dovecot
public_name = PLAIN
server_socket = /var/run/dovecot/auth-client
server_set_id = $auth1
server_condition = ${if and {{!match {$auth1}{\N[/]\N}}{eq{${if match {$auth1}{\N[+%:@]\N}{${lookup{${extract{2}{+%:@}{$auth1}}}lsearch{/etc/demodomains}{yes}}}{${lookup{$auth1}lsearch{/etc/demousers}{yes}}}}}{}}}{true}{false}}
server_advertise_condition = ${if or {{def:tls_cipher}{match_ip{$sender_host_address}{+loopback}}}{1}{0}}
dovecot_login:
driver = dovecot
public_name = LOGIN
server_socket = /var/run/dovecot/auth-client
server_set_id = $auth1
server_condition = ${if and {{!match {$auth1}{\N[/]\N}}{eq{${if match {$auth1}{\N[+%:@]\N}{${lookup{${extract{2}{+%:@}{$auth1}}}lsearch{/etc/demodomains}{yes}}}{${lookup{$auth1}lsearch{/etc/demousers}{yes}}}}}{}}}{true}{false}}
server_advertise_condition = ${if or {{def:tls_cipher}{match_ip{$sender_host_address}{+loopback}}}{1}{0}}
Testing
String expansions can be tested with the -be
option.
# exim -be '$tod_log'
See Also
Search for any configuration term on the index:
Other Stuff:
- https://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_default_configuration_file.html
- https://www.exim.org/exim-html-current/doc/html/spec_html/ch-file_and_database_lookups.html