Exim's Router Configuration

I have already explained a little about routers, the section begins with the heading "begin routers ". The order of the list is important the address passes through each router until it is accepted, so you are trying to get your address accepted ASAP thus not to decrease performance (quicker is better).

 
######################################################################
#                      ROUTERS CONFIGURATION                         #
#               Specifies how addresses are handled                  #
######################################################################
#     THE ORDER IN WHICH THE ROUTERS ARE DEFINED IS IMPORTANT!       #
# An address is passed to each router in turn until it is accepted.  #
######################################################################

begin routers

#####################################################
### end router/00_exim-config_header
#####################################################
#####################################################
### router/100_exim-config_domain_literal
#####################################################

### router/100_exim-config_domain_literal
#################################

# This router handles e-mail addresses in "domain literal" form like
# user@[10.11.12.13]. The RFCs require this facility, but it is disabled
# in the default config since it is seldomly used and frequently abused.
# Domain literal support also needs to be enabled in the main config,
# which is automatically done if you use the enable macro
# MAIN_ALLOW_DOMAIN_LITERALS.

.ifdef MAIN_ALLOW_DOMAIN_LITERALS
domain_literal:
  debug_print = "R: domain_literal for $local_part@$domain"
  driver = ipliteral
  domains = ! +local_domains
  transport = remote_smtp
  headers_remove = received
.endif
#####################################################
### end router/100_exim-config_domain_literal
#####################################################
#####################################################
### router/150_exim-config_hubbed_hosts
#####################################################

# router/150_exim-config_hubbed_hosts
#################################

# route specific domains manually.
#
# The most common application of this router is to handle relaying to nonlocal
# domains that the local host is primary MX for. That means that local
# information needs to be present for a domain to be handled correctly.
#
# That information is put into the optional file /etc/exim/hubbed_hosts
# which contains key-value pairs of domain pattern and route data.
#
# foo.example: internal.mail.example.com
# bar.example: 192.168.183.3
#
# will cause mail for foo.example to be sent to the host
# internal.mail.example (IP address derived from A record only), and
# mail to bar.example to be sent to 192.168.183.3.
#
# If the file /etc/exim/hubbed_hosts does not exist, this router is a
# no-op.

hubbed_hosts:
  debug_print = "R: hubbed_hosts for $domain"
  driver = manualroute
  domains = "${if exists{CONFDIR/hubbed_hosts}\
                   {partial-lsearch;CONFDIR/hubbed_hosts}\
              fail}"
  same_domain_copy_routing = yes
  route_data = ${lookup{$domain}partial-lsearch{CONFDIR/hubbed_hosts}}
  transport = remote_smtp
#####################################################
### end router/150_exim-config_hubbed_hosts
#####################################################
#####################################################
### router/200_exim-config_primary
#####################################################

### router/200_exim-config_primary
#################################
# This file holds the primary router, responsible for nonlocal mails

.ifdef DCconfig_internet
# configtype=internet
#
# deliver mail to the recipient if recipient domain is a domain we
# relay for. We do not ignore any target hosts here since delivering to
# a site local or even a link local address might be wanted here, and if
# such an address has found its way into the MX record of such a domain,
# the local admin is probably in a place where that broken MX record
# could be fixed.

dnslookup_relay_to_domains:
  debug_print = "R: dnslookup_relay_to_domains for $local_part@$domain"
  driver = dnslookup
  domains = ! +local_domains : +relay_to_domains
  transport = remote_smtp
  same_domain_copy_routing = yes
  no_more

# deliver mail directly to the recipient. This router is only reached
# for domains that we do not relay for. Since we most probably can't
# have broken MX records pointing to site local or link local IP
# addresses fixed, we ignore target hosts pointing to these addresses.

dnslookup:
  debug_print = "R: dnslookup for $local_part@$domain"
  driver = dnslookup
  domains = ! +local_domains
  transport = remote_smtp
  headers_remove = received
  same_domain_copy_routing = yes
  # ignore private rfc1918 and APIPA addresses
  ignore_target_hosts = 0.0.0.0 : 127.0.0.0/8 : 192.168.0.0/16 :\
                        172.16.0.0/12 : 10.0.0.0/8 : 169.254.0.0/16
  no_more

.endif


.ifdef DCconfig_local
# configtype=local
#
# Stand-alone system, so generate an error for mail to a non-local domain
nonlocal:
  debug_print = "R: nonlocal for $local_part@$domain"
  driver = redirect
  domains = ! +local_domains
  allow_fail
  data = :fail: Mailing to remote domains not supported
  no_more

.endif


.ifdef DCconfig_smarthost DCconfig_satellite
# configtype=smarthost or configtype=satellite
#
# Send all non-local mail to a single other machine (smarthost).
#
# This means _ALL_ non-local mail goes to the smarthost. This will most
# probably not do what you want for domains that are listed in
# relay_domains. The most typical use for relay_domains is to control
# relaying for incoming e-mail on secondary MX hosts. In that case,
# it doesn't make sense to send the mail to the smarthost since the
# smarthost will probably send the message right back here, causing a
# loop.
#
# If you want to use a smarthost while being secondary MX for some
# domains, you'll need to copy the dnslookup_relay_to_domains router
# here so that mail to relay_domains is handled separately.

smarthost:
  debug_print = "R: smarthost for $local_part@$domain"
  driver = manualroute
  domains = ! +local_domains
  transport = remote_smtp_smarthost
  route_list = * DCsmarthost byname
  host_find_failed = defer
  same_domain_copy_routing = yes
  no_more

.endif


# The "no_more" above means that all later routers are for
# domains in the local_domains list, i.e. just like Exim 3 directors.
#####################################################
### end router/200_exim-config_primary
#####################################################
#####################################################
### router/300_exim-config_real_local
#####################################################

### router/400_exim-config_real_local
#################################

# This router allows reaching a local user while avoiding local
# processing. This can be used to inform a user of a broken .forward
# file, for example. The userforward router does this.

real_local:
  debug_print = "R: real_local for $local_part@$domain"
  driver = accept
  domains = +local_domains
  local_part_prefix = real-
  check_local_user
  transport = LOCAL_DELIVERY

#####################################################
### end router/300_exim-config_real_local
#####################################################
#####################################################
### router/400_exim-config_system_aliases
#####################################################

### router/400_exim-config_system_aliases
#################################

# This router handles aliasing using a traditional /etc/aliases file.
#
##### NB  You must ensure that /etc/aliases exists. It used to be the case
##### NB  that every Unix had that file, because it was the Sendmail default.
##### NB  These days, there are systems that don't have it. Your aliases
##### NB  file should at least contain an alias for "postmaster".
#
# This router handles the local part in a case-insensitive way which
# satisfies the RFCs requirement that postmaster be reachable regardless
# of case. If you decide to handle /etc/aliases in a caseful way, you
# need to make arrangements for a caseless postmaster.
#
# Piping to programs in /etc/aliases is disabled per default.
# If that is a problem for you, see
#   /usr/share/doc/exim-config/README.system_aliases
# for explanation and some workarounds.
#
# Note that the transports listed below are the same as are used for
# .forward files; you might want to set up different ones for pipe and
# file deliveries from aliases.

system_aliases:
  debug_print = "R: system_aliases for $local_part@$domain"
  driver = redirect
  domains = +local_domains
  allow_fail
  allow_defer
  data = ${lookup{$local_part}lsearch{/etc/aliases}}
  .ifdef SYSTEM_ALIASES_USER
  user = SYSTEM_ALIASES_USER
  .endif
  .ifdef SYSTEM_ALIASES_GROUP
  group = SYSTEM_ALIASES_GROUP
  .endif
  .ifdef SYSTEM_ALIASES_FILE_TRANSPORT
  file_transport = SYSTEM_ALIASES_FILE_TRANSPORT
  .endif
  .ifdef SYSTEM_ALIASES_PIPE_TRANSPORT
  pipe_transport = SYSTEM_ALIASES_PIPE_TRANSPORT
  .endif
  .ifdef SYSTEM_ALIASES_DIRECTORY_TRANSPORT
  directory_transport = SYSTEM_ALIASES_DIRECTORY_TRANSPORT
  .endif
#####################################################
### end router/400_exim-config_system_aliases
#####################################################
#####################################################
### router/500_exim-config_hubuser
#####################################################

### router/500_exim-config_hubuser
#################################

.ifdef DCconfig_satellite
# This router is only used for configtype=satellite.
# It takes care to route all mail targetted to somelocaluser@this.machine
# to the host where we read our mail
#
hub_user:
  debug_print = "R: hub_user for $local_part@$domain"
  driver = redirect
  domains = +local_domains
  data = ${local_part}@DCreadhost
  check_local_user

# Grab the redirected mail and deliver it.
# This is a duplicate of the smarthost router, needed because
# DCreadhost might end up as part of +local_domains
hub_user_smarthost:
  debug_print = "R: hub_user_smarthost for $local_part@$domain"
  driver = manualroute
  domains = DCreadhost
  transport = remote_smtp_smarthost
  route_list = * DCsmarthost byname
  host_find_failed = defer
  same_domain_copy_routing = yes
  check_local_user
.endif

#####################################################
### end router/500_exim-config_hubuser
#####################################################
#####################################################
### router/600_exim-config_userforward
#####################################################

# router/600_exim-config_userforward
#################################

# This router handles forwarding using traditional .forward files in users'
# home directories and filtering with exim's builtin filter language.
#
# The no_verify setting means that this router is skipped when Exim is
# verifying addresses. Similarly, no_expn means that this router is skipped if
# Exim is processing an EXPN command.
#
# The check_ancestor option means that if the forward file generates an
# address that is an ancestor of the current one, the current one gets
# passed on instead. This covers the case where A is aliased to B and B
# has a .forward file pointing to A.
#
# The four transports specified at the end are those that are used when
# forwarding generates a direct delivery to a directory, or a file, or to a
# pipe, or sets up an auto-reply, respectively.
#
userforward:
  debug_print = "R: userforward for $local_part@$domain"
  driver = redirect
  domains = +local_domains
  check_local_user
  file = $home/.forward
  no_verify
  no_expn
  check_ancestor
  allow_filter
  directory_transport = address_directory
  file_transport = address_file
  pipe_transport = address_pipe
  reply_transport = address_reply
  skip_syntax_errors
  syntax_errors_to = real-$local_part@$domain
  syntax_errors_text = \
    This is an automatically generated message. An error has\n\
    been found in your .forward file. Details of the error are\n\
    reported below. While this error persists, you will receive\n\
    a copy of this message for every message that is addressed\n\
    to you. If your .forward file is a filter file, or if it is\n\
    a non-filter file containing no valid forwarding addresses,\n\
    a copy of each incoming message will be put in your normal\n\
    mailbox. If a non-filter file contains at least one valid\n\
    forwarding address, forwarding to the valid addresses will\n\
    happen, and those will be the only deliveries that occur.

#####################################################
### end router/600_exim-config_userforward
#####################################################
#####################################################
### router/700_exim-config_procmail
#####################################################

procmail:
  debug_print = "R: procmail for $local_part@$domain"
  driver = accept
  domains = +local_domains
  check_local_user
  transport = procmail_pipe
  # emulate OR with "if exists"-expansion
  require_files = ${local_part}:\
                  ${if exists{/etc/procmailrc}\
                    {/etc/procmailrc}{${home}/.procmailrc}}:\
                  +/usr/bin/procmail
  no_verify
  no_expn

#####################################################
### end router/700_exim-config_procmail
#####################################################
#####################################################
### router/800_exim-config_maildrop
#####################################################

### router/800_exim-config_maildrop
#################################

maildrop:
  debug_print = "R: maildrop for $local_part@$domain"
  driver = accept
  domains = +local_domains
  check_local_user
  transport = maildrop_pipe
  require_files = ${local_part}:${home}/.mailfilter:+/usr/bin/maildrop
  no_verify
  no_expn

#####################################################
### end router/800_exim-config_maildrop
#####################################################
#####################################################
### router/900_exim-config_local_user
#####################################################

### router/900_exim-config_local_user
#################################

local_user:
  debug_print = "R: local_user for $local_part@$domain"
  driver = accept
  domains = +local_domains
  check_local_user
  local_parts = ! root
  transport = LOCAL_DELIVERY

#####################################################
### end router/900_exim-config_local_user
#####################################################
#####################################################
### router/mmm_mail4root
#####################################################

### router/mmm_mail4root
#################################
# deliver mail addressed to root to /var/mail/mail as user mail:mail
# if it was not redirected in /etc/aliases or by other means
# Exim cannot deliver as root since 4.24 (FIXED_NEVER_USERS)

mail4root:
  debug_print = "R: mail4root for $local_part@$domain"
  driver = redirect
  domains = +local_domains
  data = /var/mail/mail
  file_transport = address_file
  local_parts = root
  user = mail
  group = mail

#####################################################
### end router/mmm_mail4root
#####################################################
#####################################################
### transport/00_exim-config_header
#####################################################

Router Definition

This should now be familiar

  begin routers

Domain_Literal Router

This router handles e-mail addresses in "domain literal" form like user@[10.11.12.13]. The RFCs require this facility, but it is disabled in the default config since it is seldomly used and frequently abused. Domain literal support also needs to be enabled in the main config, which is automatically done if you use the enable macro MAIN_ALLOW_DOMAIN_LITERALS.

 
.ifdef MAIN_ALLOW_DOMAIN_LITERALS
  domain_literal:
     debug_print = "R: domain_literal for $local_part@$domain"
     driver = ipliteral
     domains = ! +local_domains
     transport = remote_smtp
     headers_remove = received
.endif

Hubbed_Hosts Router

Route specific domains manually. The most common application of this router is to handle relaying to nonlocal domains that the local host is primary MX for. That means that local information needs to be present for a domain to be handled correctly. That information is put into the optional file /etc/exim/hubbed_hosts which contains key-value pairs of domain pattern and route data. foo.example: internal.mail.example.com bar.example: 192.168.183.3 will cause mail for foo.example to be sent to the host internal.mail.example (IP address derived from A record only), and mail to bar.example to be sent to 192.168.183.3. If the file /etc/exim/hubbed_hosts does not exist, this router is a no-op.

In my case I route through a mailhost which is defined in the /etc/exim/hubbed_hosts file.

 
hubbed_hosts:
  debug_print = "R: hubbed_hosts for $domain"
  driver = manualroute
  domains = "${if exists{CONFDIR/hubbed_hosts} {partial-lsearch;CONFDIR/hubbed_hosts} fail}"
  same_domain_copy_routing = yes
  route_data = ${lookup{$domain}partial-lsearch{CONFDIR/hubbed_hosts}}
  transport = remote_smtp


Note: the same_domain_copy_routing increases performance, in that it copies the routing for one address 
to all others in the same message that have the same name.

We use the manualroute driver because we are using locally supplied information via the 
/etc/exim/hubbed_hosts file

dnslookup_relay_to_domains and dnslookup Routers

This file holds the primary router, responsible for nonlocal mails

 
.ifdef DCconfig_internet


# configtype=internet
#
# deliver mail to the recipient if recipient domain is a domain we
# relay for. We do not ignore any target hosts here since delivering to
# a site local or even a link local address might be wanted here, and if
# such an address has found its way into the MX record of such a domain,
# the local admin is probably in a place where that broken MX record
# could be fixed.


dnslookup_relay_to_domains:
  debug_print = "R: dnslookup_relay_to_domains for $local_part@$domain"
  driver = dnslookup
  domains = ! +local_domains : +relay_to_domains
  transport = remote_smtp
  same_domain_copy_routing = yes
  no_more

# deliver mail directly to the recipient. This router is only reached
# for domains that we do not relay for. Since we most probably can't
# have broken MX records pointing to site local or link local IP
# addresses fixed, we ignore target hosts pointing to these addresses.

dnslookup:
  debug_print = "R: dnslookup for $local_part@$domain"
  driver = dnslookup
  domains = ! +local_domains
  transport = remote_smtp
  headers_remove = received
  same_domain_copy_routing = yes
  # ignore private rfc1918 and APIPA addresses
  ignore_target_hosts = 0.0.0.0 : 127.0.0.0/8 : 192.168.0.0/16 :172.16.0.0/12 : 10.0.0.0/8 : 169.254.0.0/16
  no_more

.endif


Note: the driver is dnslookup and we only lookup for non local/relay domains. the no_more option means
if this fails don't run any more routers, we also remove the received header (headers_remove)

Nonlocal Router

Here we redirect to what's in the data option providing the address is not in the local_domains, in this case we force a failure (:fail:), you can only use the :fail: option providing that the allow_fail option has been set.

 
.ifdef DCconfig_local
# configtype=local
#
# Stand-alone system, so generate an error for mail to a non-local domain
nonlocal:
  debug_print = "R: nonlocal for $local_part@$domain"
  driver = redirect
  domains = ! +local_domains
  allow_fail
  data = :fail: Mailing to remote domains not supported
  no_more

.endif


Note: in order to use :fail: you must set allow_fail

Smarthost Router

This means _ALL_ non-local mail goes to the smarthost. This will most probably not do what you want for domains that are listed in relay_domains. The most typical use for relay_domains is to control relaying for incoming e-mail on secondary MX hosts. In that case, it doesn't make sense to send the mail to the smarthost since the smarthost will probably send the message right back here, causing a loop. If you want to use a smarthost while being secondary MX for some domains, you'll need to copy the dnslookup_relay_to_domains router here so that mail to relay_domains is handled separately.

 
.ifdef DCconfig_smarthost DCconfig_satellite
# configtype=smarthost or configtype=satellite
#
# Send all non-local mail to a single other machine (smarthost).
#


smarthost:
  debug_print = "R: smarthost for $local_part@$domain"
  driver = manualroute                                         ## look for the transport
  domains = ! +local_domains
  transport = remote_smtp_smarthost
  route_list = * DCsmarthost byname
  host_find_failed = defer
  same_domain_copy_routing = yes
  no_more

.endif


Note: the no_more means that no more routers will be tried. The host_find_failed means only a definite does 
not exist state.

Real_local Router

This router allows reaching a local user while avoiding local processing. This can be used to inform a user of a broken .forward file, for example. The userforward router does this. The local_prefix and check_local_user we have discussed in generic router options, the accept in the driver means accept any message providing it meets the preconditions.

 
real_local:
  debug_print = "R: real_local for $local_part@$domain"
  driver = accept
  domains = +local_domains
  local_part_prefix = real-
  check_local_user
  transport = LOCAL_DELIVERY

System_aliases Router

This router handles the local part in a case-insensitive way which satisfies the RFCs requirement that postmaster be reachable regardless of case. If you decide to handle /etc/aliases in a caseful way, you need to make arrangements for a caseless postmaster. Piping to programs in /etc/aliases is disabled per default. If that is a problem for you, see /usr/share/doc/exim-config/README.system_aliases for explanation and some workarounds. Note that the transports listed below are the same as are used for .forward files; you might want to set up different ones for pipe and file deliveries from aliases.

 
system_aliases:
  debug_print = "R: system_aliases for $local_part@$domain"
  driver = redirect
  domains = +local_domains
  allow_fail
  allow_defer
  data = ${lookup{$local_part}lsearch{/etc/aliases}}
  .ifdef SYSTEM_ALIASES_USER
  user = SYSTEM_ALIASES_USER
  .endif
  .ifdef SYSTEM_ALIASES_GROUP
  group = SYSTEM_ALIASES_GROUP
  .endif
  .ifdef SYSTEM_ALIASES_FILE_TRANSPORT
  file_transport = SYSTEM_ALIASES_FILE_TRANSPORT
  .endif
  .ifdef SYSTEM_ALIASES_PIPE_TRANSPORT
  pipe_transport = SYSTEM_ALIASES_PIPE_TRANSPORT
  .endif
  .ifdef SYSTEM_ALIASES_DIRECTORY_TRANSPORT
  directory_transport = SYSTEM_ALIASES_DIRECTORY_TRANSPORT
  .endif

Hub_user Router

This router is only used for configtype=satellite. It takes care to route all mail targetted to somelocaluser@this.machine to the host where we read our mail. Grab the redirected mail and deliver it. This is a duplicate of the smarthost router, needed because DCreadhost might end up as part of +local_domains

 
.ifdef DCconfig_satellite

hub_user:
  debug_print = "R: hub_user for $local_part@$domain"
  driver = redirect
  domains = +local_domains
  data = ${local_part}@DCreadhost
  check_local_user

hub_user_smarthost:
  debug_print = "R: hub_user_smarthost for $local_part@$domain"
  driver = manualroute
  domains = DCreadhost
  transport = remote_smtp_smarthost
  route_list = * DCsmarthost byname
  host_find_failed = defer
  same_domain_copy_routing = yes
  check_local_user
.endif


Note: if the first router succeeds then the next router delivers the message.

Userforward Router

This router handles forwarding using traditional .forward files in users' home directories and filtering with exim's builtin filter language. The no_verify setting means that this router is skipped when Exim is verifying addresses. Similarly, no_expn means that this router is skipped if Exim is processing an EXPN command. The check_ancestor option means that if the forward file generates an address that is an ancestor of the current one, the current one gets passed on instead. This covers the case where A is aliased to B and B has a .forward file pointing to A. The four transports specified at the end are those that are used when forwarding generates a direct delivery to a directory, or a file, or to a pipe, or sets up an auto-reply, respectively.

 
userforward:
  debug_print = "R: userforward for $local_part@$domain"
  driver = redirect
  domains = +local_domains
  check_local_user
  file = $home/.forward
  no_verify
  no_expn
  check_ancestor
  allow_filter
  directory_transport = address_directory
  file_transport = address_file
  pipe_transport = address_pipe
  reply_transport = address_reply
  skip_syntax_errors
  syntax_errors_to = real-$local_part@$domain
  syntax_errors_text = \
    This is an automatically generated message. An error has\n\
    been found in your .forward file. Details of the error are\n\
    reported below. While this error persists, you will receive\n\
    a copy of this message for every message that is addressed\n\
    to you. If your .forward file is a filter file, or if it is\n\
    a non-filter file containing no valid forwarding addresses,\n\
    a copy of each incoming message will be put in your normal\n\
    mailbox. If a non-filter file contains at least one valid\n\
    forwarding address, forwarding to the valid addresses will\n\
    happen, and those will be the only deliveries that occur.

Procmail Router

If you use procmail than you can point to the procmail transport which will be discuss in the transport config section. Obviously we check that the procmail does exists first before wasting resouces passing to the transport.

 
procmail:
  debug_print = "R: procmail for $local_part@$domain"
  driver = accept
  domains = +local_domains
  check_local_user
  transport = procmail_pipe
  # emulate OR with "if exists"-expansion
  require_files = ${local_part}:\
                  ${if exists{/etc/procmailrc}\
                    {/etc/procmailrc}{${home}/.procmailrc}}:\
                  +/usr/bin/procmail
  no_verify
  no_expn

Maildrop Router

This is similar to the procmail above but using maildrop.

 
maildrop:
  debug_print = "R: maildrop for $local_part@$domain"
  driver = accept
  domains = +local_domains
  check_local_user
  transport = maildrop_pipe
  require_files = ${local_part}:${home}/.mailfilter:+/usr/bin/maildrop
  no_verify
  no_expn

Local_user Router

Here we use a local delivery to delivery all local messages but we check that the user is not root.

 
local_user:
  debug_print = "R: local_user for $local_part@$domain"
  driver = accept
  domains = +local_domains
  check_local_user
  local_parts = ! root
  transport = LOCAL_DELIVERY

Mail4root Router

Deliver mail addressed to root to /var/mail/mail as user mail:mail if it was not redirected in /etc/aliases or by other means Exim cannot deliver as root since 4.24 (FIXED_NEVER_USERS)

 
mail4root:
  debug_print = "R: mail4root for $local_part@$domain"
  driver = redirect
  domains = +local_domains
  data = /var/mail/mail
  file_transport = address_file
  local_parts = root
  user = mail
  group = mail