Exim configuration for client connected to smarthost

bike9876@エボ猫.コム

(From https://www.exim.org/index.html) Exim is a message transfer agent (MTA) originally developed at the University of Cambridge for use on Unix systems connected to the Internet.

Install on an arch linux system with 1:

sudo pacman -S exim

Note: don’t start or enable the exim daemon (with eg systemctl --now enable exim.service) as in my client setup, /usr/bin/exim (or possibly its alias /usr/bin/sendmail) will be run by any mail user agents as needed.

This page describes one of the simplest exim configurations. All emails are locally-originated. A smarthost will be used to deliver email to non-local recipients2.

Edit /etc/mail/aliases

Set the email alias for root (replace USERNAME with the username of the non-root user who should receive root’s emails):

sudo sed -i -E 's/^#?root:.*/root: USERNAME/' /etc/mail/aliases

Create /etc/mail/exim.conf

I use the configuration shown below. Definitions that must be changed for your setup are the defintions of FROM_DOMAIN_DEFAULT and ROUTER_SMARTHOST.

I want it to send all emails with a remote destination via a smarthost (as defined in ROUTER_SMARTHOST). In my case that smarthost requires authentication with a username and password, as provided in the file defined in SYSTEM_AUTH_FILE (more details later).

I want emails sent with an unqualified envelope sender and From: header (eg just “root”, as would typically be sent by a system cron job) to be qualified with @FROM_DOMAIN_DEFAULT. If I didn’t do that, the default action of exim is to qualify with @<hostname> (where <hostname> is the hostname of my client system), which in my case the smarthost will reject. Define FROM_DOMAIN_DEFAULT to be the domain to use to qualify an unqualified sender address.


# exim.conf
# For a system with emails accepted purely on the command line, with no SMTP (so no -bs or -bS flags used).
# Eg as
# exim -bm foo < mail.txt
# (or via "mail" command or mutt).
# Except for emails delivered locally, it is expected that all will be sent off to a smarthost (see ROUTER_SMARTHOST).

# File derived from configure.default in exim source code - many of the explanatory comments have been retained.

# Sender addresses should be qualified with FROM_DOMAIN_DEFAULT, so if user bar runs "exim -bm foo < mail.txt", the
# envelope sender and From: header should be bar@FROM_DOMAIN_DEFAULT.
FROM_DOMAIN_DEFAULT = example.net
ROUTER_SMARTHOST = mail.example.net
# Where system email aliases are (https://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_redirect_router.html#SECID124)
SYSTEM_ALIASES_FILE = /etc/aliases
# Where authentication data to the smarthost is. Comment out if no authentication needed.
SYSTEM_AUTH_FILE = /etc/exim/smtp_users

domainlist local_domains = @:localhost

# Sometimes useful for debugging purposes, see below.
acl_not_smtp = acl_log_not_smtp

# I want unqualified envelope sender addresses and From: headers to be qualified with FROM_DOMAIN_DEFAULT
.ifdef FROM_DOMAIN_DEFAULT
qualify_domain = FROM_DOMAIN_DEFAULT
.endif
# I want unqualified envelope recipient addresses and recipient headers to be qualified with localhost
# so that eg "exim -bm foo < mail.txt" will try to deliver to foo on the localhost (the chances are that foo
# will have a ~/.forward set up to forward to an external host, but I should be able to deliver to localhost if that is what is wanted).
qualify_recipient = localhost

# Never run exim as any of these users.
# Local message deliveries are normally run in processes that are setuid to the recipient (so make sure that root has an alias to the sys admin).
# and remote deliveries are normally run under Exim’s own uid and gid.
# https://www.exim.org/exim-html-current/doc/html/spec_html/ch-main_configuration.html
never_users = root:daemon:bin

# Do not add a Received: header line for the locally-sent email from the user to exim.
# (I don't want the final recipient to see information about this host.)
# https://www.exim.org/exim-html-current/doc/html/spec_html/ch-main_configuration.html
received_header_text =

# Need to be in admin group to eg run exim with -d flag (for debugging output)
# Change if different group used instead of wheel (debian uses "sudo" group).
# https://www.exim.org/exim-html-current/doc/html/spec_html/ch-main_configuration.html
admin_groups=wheel

# See https://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_exim_command_line.html#SECTtrustedadmin
# Sometimes I want to be able to specify the Return Path (with -f in exim).
# Change if different group used instead of wheel (debian uses "sudo" group).
trusted_groups = wheel

# To try to initialize the system resolver library with DNSSEC support.  It has no effect if your library lacks
# DNSSEC support.
# https://www.exim.org/exim-html-current/doc/html/spec_html/ch-main_configuration.htm
# and see notes there re linux glibc => 2.31
dns_dnssec_ok = 1

# go crazy on logging
log_selector = +all

keep_environment =

#------------------------------------------------------------------------

begin acl

# I just use this to log stuff as needed (see example here to log the value of a macro to exim's log -
# but just using "exim -be 'SYSTEM_ALIASES_FILE'" would be easier).
# (https://www.exim.org/exim-html-current/doc/html/spec_html/ch-access_control_lists.html#SECnonSMTP)
# The ACL is run for a non-SMTP message just before the local_scan() function.
acl_log_not_smtp:
#  warn log_message = ${uc:system_aliases_file}=SYSTEM_ALIASES_FILE
  accept

#------------------------------------------------------------------------

begin routers

# This router can be used when you want to send all mail to a
# server which handles DNS lookups for you; an ISP will typically run such
# a server for their customers.  The hostname in route_data comes from the
# macro defined at the top of the file.  If not defined, then we'll use the
# dnslookup router below instead.
# Beware that the hostname is specified again in the Transport.

.ifdef ROUTER_SMARTHOST

smarthost:
  driver = manualroute
  domains = ! +local_domains
  transport = smarthost_smtp
  route_data = ROUTER_SMARTHOST
  ignore_target_hosts = <; 0.0.0.0 ; 127.0.0.0/8 ; ::1
  no_more

.else

# This router routes addresses that are not in local domains by doing a DNS
# lookup on the domain name. The exclamation mark that appears in "domains = !
# +local_domains" is a negating operator, that is, it can be read as "not". The
# recipient's domain must not be one of those defined by "domainlist
# local_domains" above for this router to be used.
#
# If the router is used, any domain that resolves to 0.0.0.0 or to a loopback
# interface address (127.0.0.0/8) is treated as if it had no DNS entry. Note
# that 0.0.0.0 is the same as 0.0.0.0/32, which is commonly treated as the
# local host inside the network stack. It is not 0.0.0.0/0, the default route.
# If the DNS lookup fails, no further routers are tried because of the no_more
# setting, and consequently the address is unrouteable.

dnslookup:
  driver = dnslookup
  domains = ! +local_domains
  transport = remote_smtp
  ignore_target_hosts = 0.0.0.0 : 127.0.0.0/8
# if ipv6-enabled then instead use:
# ignore_target_hosts = <; 0.0.0.0 ; 127.0.0.0/8 ; ::1
  no_more

# This closes the ROUTER_SMARTHOST ifdef around the choice of routing for
# off-site mail.
.endif

system_aliases:
  driver = redirect
  allow_fail
  allow_defer
  data = ${lookup{$local_part}lsearch{SYSTEM_ALIASES_FILE}}
  file_transport = address_file
  pipe_transport = address_pipe

userforward:
  driver = redirect
  check_local_user
  file = $home/.forward
  no_verify
  no_expn
  check_ancestor
  file_transport = address_file
  pipe_transport = address_pipe
  reply_transport = address_reply

localuser:
  driver = accept
  check_local_user
  transport = local_delivery
  cannot_route_message = Unknown user

#------------------------------------------------------------------------

begin transports

# This transport is used for delivering messages over SMTP connections.

remote_smtp:
  driver = smtp
.ifdef _HAVE_TLS_RESUME
  tls_resumption_hosts = *
.endif

# This transport is used for delivering messages to a smarthost, if the
# smarthost router is enabled.  This starts from the same basis as
# "remote_smtp" but then turns on various security options, because
# we assume that if you're told "use smarthost.example.org as the smarthost"
# then there will be TLS available, with a verifiable certificate for that
# hostname, using decent TLS.

smarthost_smtp:
  driver = smtp
  multi_domain
.ifdef SYSTEM_AUTH_FILE
  # Try to authenticate with smarthost
  hosts_try_auth = ROUTER_SMARTHOST
.endif
.ifdef _HAVE_TLS
  # Comment out any of these which you have to, then file a Support
  # request with your smarthost provider to get things fixed:
  hosts_require_tls = *
  tls_verify_hosts = *
  # As long as tls_verify_hosts is enabled this will have no effect,
  # but if you have to comment it out then this will at least log whether
  # you succeed or not:
  tls_try_verify_hosts = *
  #
  # The SNI name should match the name which we'll expect to verify;
  # many mail systems don't use SNI and this doesn't matter, but if it does,
  # we need to send a name which the remote site will recognize.
  # This _should_ be the name which the smarthost operators specified as
  # the hostname for sending your mail to.
  tls_sni = ROUTER_SMARTHOST
  #
.ifdef _HAVE_OPENSSL
  tls_require_ciphers = HIGH:!aNULL:@STRENGTH
.endif
.ifdef _HAVE_GNUTLS
  tls_require_ciphers = SECURE192:-VERS-SSL3.0:-VERS-TLS1.0:-VERS-TLS1.1
.endif
.ifdef _HAVE_TLS_RESUME
  tls_resumption_hosts = *
.endif
.endif

# This transport is used for local delivery to user mailboxes in traditional
# BSD mailbox format. By default it will be run under the uid and gid of the
# local user, and requires the sticky bit to be set on the /var/mail directory.
# Some systems use the alternative approach of running mail deliveries under a
# particular group instead of using the sticky bit. The commented options below
# show how this can be done.

local_delivery:
  driver = appendfile
  file = /var/mail/$local_part_data
  delivery_date_add
  envelope_to_add
  return_path_add

# 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 in the routers
# section above.

address_pipe:
  driver = pipe
  return_output


# This transport is used for handling deliveries directly to files that are
# generated by aliasing or forwarding.

address_file:
  driver = appendfile
  delivery_date_add
  envelope_to_add
  return_path_add


# This transport is used for handling autoreplies generated by the filtering
# option of the userforward router.

address_reply:
  driver = autoreply

#------------------------------------------------------------------------

begin authenticators

PLAIN:
  driver                     = plaintext
  client_send = "${extract{auth_plain}{${lookup{$host}lsearch{SYSTEM_AUTH_FILE}{$value}fail} }}"

LOGIN:
  driver                     = plaintext
  client_send = : "${extract{auth_name}{${lookup{$host}lsearch{SYSTEM_AUTH_FILE}{$value}fail} }}" : "${extract{auth_pass}{${lookup{$host}lsearch{SYSTEM_AUTH_FILE}{$value}fail} }}"

Create /etc/exim/smtp_users

(skip if the smarthost does not require authentication)

As discussed above, my smarthost requires a client to authenticate with a username and password. I set these in file /etc/exim/smtp_users as eg

sudo -s # become root
mkdir -p /etc/exim
echo "$(exim -be 'ROUTER_SMARTHOST') auth_name=USERNAME auth_pass=PASSWORD auth_plain=^USERNAME^PASSWORD" > /etc/exim/smtp_users
chmod 640 /etc/exim/smtp_users
chown root:exim /etc/exim/smtp_users
exit # leave root

Change USERNAME and PASSWORD to those that allow access to the smarthost.


  1. Other distributions will be similar (but different) in how to install packages, and in the file locations.↩︎

  2. Any emails with a local recipient will be saved on the client, unless exim’s aliases file or the local recipient’s ~/.forward file has been set up to forward to an external address (which should normally be done - see main text).↩︎