newspaint

Documenting Problems That Were Difficult To Find The Answer To

Testing a New Exim Configuration File Before Deployment

Crafting an Exim (open-source SMTP mail server) configuration or even making minor changes is no subtle feat!

Given how critical e-mail is to your business or organisation there is no question that you want to thoroughly test your configuration before deploying to production.

This article makes heavy use of the https://github.com/Exim/exim/wiki/TestingExim page. Note that http://bradthemad.org/tech/notes/exim_cheatsheet.php is a great resource, too. The Exim official command line documentation is invaluable.

Verify Configuration

With a very basic introductory configuration:

CONFDIR = /etc/exim4
daemon_smtp_port = 25 : 465
tls_on_connect_ports = 465
disable_ipv6

Let’s ensure that it parses:

root@myhost:/tmp/exim# exim -C test.conf -bV
Exim version 4.76 #1 built 28-Dec-2012 16:46:04
Copyright (c) University of Cambridge, 1995 - 2007
Berkeley DB: Berkeley DB 5.1.25: (January 28, 2011)
Support for: crypteq iconv() IPv6 PAM Perl Expand_dlfunc GnuTLS move_frozen_messages Content_Scanning DKIM Old_Demime
Lookups (built-in): lsearch wildlsearch nwildlsearch iplsearch cdb dbm dbmnz dnsdb dsearch ldap ldapdn ldapm mysql nis nis0 passwd pgsql sqlite
Authenticators: cram_md5 cyrus_sasl dovecot plaintext spa
Routers: accept dnslookup ipliteral iplookup manualroute queryprogram redirect
Transports: appendfile/maildir/mailstore/mbx autoreply lmtp pipe smtp
Fixed never_users: 0
Size of off_t: 8
2014-08-22 14:32:26 Exim configuration file test.conf has the wrong owner, group, or mode

Ah, looks like we have to change the mode of the file:

root@myhost:/tmp/exim# chmod 600 test.conf
root@myhost:/tmp/exim# exim -C test.conf -bV
Configuration file is test.conf

If you were to make a spelling error:

CONFDIR = /etc/exim4
daemon_smtp_port = 25 : 465
tls_on_connect_ports = 465
isable_ipv6

then…

root@myhost:/tmp/exim# exim -C test.conf -bV
2014-08-22 14:37:20 Exim configuration error in line 4 of test.conf:
  main option "isable_ipv6" unknown

Test Access Control Lists

The -bh <ip_address> option runs a fake SMTP session as if it were from the given IP address.

This is crucial in differentiating between behaviour of your mailer to connections from locally (and presumably allowed to send mail anywhere out into the Internet) and from remotely (and presumably subject to tight controls such as no relaying or bans depending on IP address).

The following test configuration will be used in the following examples:

CONFDIR = /etc/exim4
daemon_smtp_port = 25 : 465
tls_on_connect_ports = 465
disable_ipv6
exim_path = /usr/sbin/exim4
hostlist hl_localhost = @ : 127.0.0.1 : ::::1
domainlist dl_local = @ : localhost
hostlist hl_relay_from = +hl_localhost
local_interfaces = 127.0.0.1 : ::::1
gecos_pattern = ^([^,:]*)
gecos_name = $1
smtp_banner = newspaint blog test ESMTP
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 CONFIGURATION
##############################################################################
begin acl

acl_check_connect:
  # accept from local hosts
  accept
    hosts = +hl_localhost

  # accept from all others after a connection delay
  # some spammers will not wait for a connection
  accept
    delay = 1s

acl_check_helo:
  # accept from local hosts
  accept
    hosts = +hl_localhost

  # prevent remote site claiming to be local site
  deny
    message = invalid HELO host
    condition = ${if \
      match_domain {${lc:$sender_helo_name}}{+dl_local} \
      {true}{false} \
    }

  accept

acl_check_mail:
  # deny if no HELO command given
  deny
    condition = ${if def:sender_helo_name {no}{yes}}
    delay = 10s
    message = no HELO given before MAIL command

  accept

acl_check_rcpt:
  # accept if the source is local SMTP (not TCP/IP)
  accept
    hosts = :

  deny
    domains = +dl_local
    local_parts = ^[.] : ^.*[@%!/|`\#&?]
    message = restricted characters in address

  deny
    domains = !+dl_local
    local_parts = ^[./|] : ^.*[@%!`\#&?] : ^.*/\\.\\./
    message = restricted characters in address from non-local connection

  # accept for relay hosts
  accept
    hosts = +hl_relay_from
    control = submission/sender_retain

  # allow relaying for authenticated connection
  accept
    authenticated = *
    control = submission/sender_retain

  deny
    !domains = +dl_local
    message = relay not permitted

  deny
    !verify = recipient

acl_check_data:
  accept

##############################################################################
# ROUTERS CONFIGURATION
##############################################################################
begin routers

dnslookup:
  debug_print = "R: dnslookup for $local_part@$domain"
  driver = dnslookup
  domains = !+dl_local
  transport = remote_smtp
  same_domain_copy_routing = yes
  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 :\
			255.255.255.255
  no_more

  # The "no_more" above means that all later routers are for
  # domains in the local_domains list, i.e. just like Exim 3 directors.

localuser:
  debug_print = "R: localuser for $local_part@$domain"
  driver = accept
  domains = +dl_local
  check_local_user
  transport = maildir_home

##############################################################################
# TRANSPORTS CONFIGURATION
##############################################################################
begin transports

remote_smtp:
  debug_print = "T: remote_smtp for $local_part@$domain"
  driver = smtp

maildir_home:
  debug_print = "T: maildir_home for $local_part@$domain"
  driver = appendfile
  directory = $home/Maildir
  #create_directory
  delivery_date_add
  envelope_to_add
  return_path_add
  maildir_format
  directory_mode = 0770
  mode = 0660

So the acl_check_helo access control list (ACL) should permit local connections to give any name they like with the HELO command but external connections should reject a HELO with localhost as the name.

To test this simulate a connection from the Internet (let’s use the fictional address 1.2.3.4):

root@myhost:/tmp/exim# exim -C test.conf -bh 1.2.3.4
**** SMTP testing session as if from host 1.2.3.4
>>> using ACL "acl_check_connect"
>>> processing "accept"
>>> check hosts = +hl_localhost
>>> gethostbyname2 looked up these IP addresses:
>>>   name=myhost address=127.0.0.1
>>>   name=myhost address=127.0.1.1
>>> host in "@ : 127.0.0.1 : ::::1"? no (end of list)
>>> host in "+hl_localhost"? no (end of list)
>>> accept: condition test failed
>>> processing "accept"
>>> check delay = 1s
>>> delay modifier requests 1-second delay
>>> delay skipped in -bh checking mode
>>> accept: condition test succeeded
220 newspaint blog test ESMTP
HELO localhost
>>> localhost in helo_lookup_domains? no (end of list)
>>> using ACL "acl_check_helo"
>>> processing "accept"
>>> check hosts = +hl_localhost
>>> host in "+hl_localhost"? no (end of list)
>>> accept: condition test failed
>>> processing "deny"
>>> localhost in "@ : localhost"? yes (matched "localhost")
>>> localhost in "+dl_local"? yes (matched "+dl_local")
>>> check condition = ${if match_domain {${lc:$sender_helo_name}}{+dl_local} {true}{false} }
>>>                 = true
>>> deny: condition test succeeded
550 invalid HELO host
QUIT
root@myhost:/tmp/exim#

Success! We were denied when trying to give the HELO localhost command when connecting from the outside Internet.

Let’s check that we can HELO as a different host:

root@myhost:/tmp/exim# exim -C test.conf -bh 1.2.3.4
  ...
220 newspaint blog test ESMTP
HELO test.com
  ...
>>> accept: condition test succeeded
250 myhost Hello test.com [1.2.3.4]
QUIT
root@myhost:/tmp/exim#

Success! Okay, now can we connect from localhost and issue the HELO localhost command?

root@myhost:/tmp/exim# exim -C test.conf -bh 127.0.0.1
  ...
220 newspaint blog test ESMTP
HELO localhost
  ...
>>> accept: condition test succeeded
250 myhost Hello localhost [127.0.0.1]
QUIT
root@myhost:/tmp/exim#

Success again! You should go through each access control list and test all combinations of expected results as an external and as a local IP address – verifying expected behaviour.

Test Routing

Test of routing as a local root user can be performed using the -bt <recipient_address> command-line option.

e.g. let’s test the above configuration mailing to the local root user:

root@myhost:/tmp/exim# exim -C test.conf -bt root@localhost
R: localuser for root@localhost
root@localhost
  router = localuser, transport = maildir_home

Looks good. How about trying to mail to a non-existent local user account?

root@myhost:/tmp/exim# exim -C test.conf -bt fictional@localhost
fictional@localhost is undeliverable: Unrouteable address

Good, we expected that. Now let’s try routing a message to the Internet:

root@myhost:/tmp/exim# exim -C test.conf -bt test@gmail.com
R: dnslookup for test@gmail.com
test@gmail.com
  router = dnslookup, transport = remote_smtp
  host gmail-smtp-in.l.google.com      [173.194.66.27] MX=5
  host alt1.gmail-smtp-in.l.google.com [64.233.164.27] MX=10
  host alt2.gmail-smtp-in.l.google.com [74.125.130.27] MX=20
  host alt3.gmail-smtp-in.l.google.com [74.125.203.27] MX=30
  host alt4.gmail-smtp-in.l.google.com [173.194.72.27] MX=40

Okay well let’s try e-mailing to a domain that doesn’t exist:

root@myhost:/tmp/exim# exim -C test.conf -bt test@zzzfailzzz.com
test@zzzfailzzz.com is undeliverable: Unrouteable address

Just as we expected – cannot route to that domain.

Mysterious Reasons Why Router Conditions Silently Fail

Exim will sometimes just ignore errors in router condition syntax and silently fail. See Exim bug #1518.

If you have too many reverse curly braces the condition will be ignored with no error (i.e. the router will not fail the condition ever):

begin routers

dnslookup:
  debug_print = "R: dnslookup for $local_part@$domain"
  driver = dnslookup
  domains = !+dl_local
  condition = ${if eq {${lc:$domain}} {google.com}} {yes} {no}}
  transport = remote_smtp
  no_more

If you try running:

root@myhost:/tmp/exim# exim -C test.conf -d -bt test@googb.com
Exim version 4.76 uid=0 gid=0 pid=28972 D=fbb95cfd
...
routing test@googb.com
--------> dnslookup router <--------
R: dnslookup for test@googb.com
checking "condition"
calling dnslookup router
set transport remote_smtp
queued for remote_smtp transport: local_part = test

This is bad. The condition succeeds where it should fail and, even worse, there’s absolutely no warning message from Exim to tell you the condition is not being processed.

The fix in this example is simple, remove the extra reverse brace after google.com:

condition = ${if eq {${lc:$domain}} {google.com} {yes} {no}}

Now the condition correctly fails and the router does not run (whereas previously, with the syntax error that wasn’t detected, the condition would be ignored and the router would incorrectly run).

Testing Local Deliveries

Local deliveries often require Exim to change user permissions and privileges. If you attempt to test a custom configuration local delivery you might get the following errors:

root@myhost:/tmp/exim# exim -C test.conf testuser@mydomain.com <message.txt
...
LOG: MAIN PANIC
  exim user lost privilege for using -C option
...
LOG: MAIN PANIC DIE
  unable to set gid=8 or uid=125 (euid=125): local delivery to testuser  transport=mylocaltransport

The top two reasons on the Internet for this message are:

  • You have forgotten to make the exim binary setuid to root. This means that it can never change uid/gid in any situation. Also, the setuid binary must reside on a disk partition that does not have the nosuid mount option set.
  • The exim binary is setuid, but you have configured Exim so that, while trying to verify an address at SMTP time, it runs a router that needs to change uid/gid. Because Exim runs as exim and not root while receiving messages, the router is unable to change uid and therefore it cannot operate. The usual example of this is a redirect router for users’ filter files.

But if you check for suid on the Exim binary and it is okay:

root@myhost:/tmp/exim# ls -al /usr/sbin/exim4
-rwsr-xr-x 1 root root 1073912 Dec 28  2012 /usr/sbin/exim4

… and your routers are not setting uid or gid then there’s a third possibility: the -C <file>:

When this option is used by a caller other than root, and the list is different from the compiled-in
list, Exim gives up its root privilege immediately, and runs with the real and effective uid and gid
set to those of the caller. However, if a TRUSTED_CONFIG_LIST file is defined in
Local/Makefile, that file contains a list of full pathnames, one per line, for configuration files which
are trusted. Root privilege is retained for any configuration file so listed, as long as the caller is the
Exim user (or the user specified in the CONFIGURE_OWNER option, if any), and as long as the
configuration file is not writeable by inappropriate users or groups.

If ALT_CONFIG_PREFIX is defined in Local/Makefile, it specifies a prefix string with which any
file named in a -C command line option must start. In addition, the file name must not contain the
sequence /../. However, if the value of the -C option is identical to the value of CONFIGURE_
FILE in Local/Makefile, Exim ignores -C and proceeds as usual. There is no default setting for
ALT_CONFIG_PREFIX; when it is unset, any file name can be used with -C.

ALT_CONFIG_PREFIX can be used to confine alternative configuration files to a directory to
which only root has access. This prevents someone who has broken into the Exim account from
running a privileged Exim with an arbitrary configuration file.

I checked my Operating System:

root@myhost:/tmp/exim# lsb_release -a
Distributor ID: Ubuntu
Description:    Ubuntu 12.04.5 LTS
Release:        12.04
Codename:       precise

There is a file for Ubuntu named /etc/exim4/trusted_configs. In there I put the full path to my test Exim configuration.

/tmp/exim/test.conf

Then I had to run my delivery test with the full path to the test configuration:

root@myhost:/tmp/exim# exim -C /tmp/exim/test.conf testuser@mydomain.com <message.txt
  ...

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: