Re: [exim] Consider local_domains only if DNS matches

Top Page
Delete this message
Reply to this message
Author: Phil Pennock
Date:  
To: exim-users
Subject: Re: [exim] Consider local_domains only if DNS matches
On 2007-05-13 at 22:55 +0200, Peter Thomassen wrote:
> So, how do I configure Exim to only deliver mail locally if the domain is in
> local_domains AND a DNS lookup confirms that our machine is in the MX
> records of the domain in question, hence serving the domain?


Various ways. If your configuration looks anything like the default
supplied with Exim, then your Routers first send remote mail off and
then continue to process local mail. In this case, the last Router
handling non-local mail will be "dnslookup" or a smarthost-style
equivalent. So the easiest and simplest way would be to add a second
Router similar to that last one, designed to handle "local_domains but
no MX points to us"; this has the advantage that the Exim mainlog will
include this Router's name in in the delivery line so you'll be easily
able to see _why_ a mail was sent off externally. "Oh,
R=not_yet_hosted, we'd better check the DNS for this customer."

This does mean though that your mail-system is now a relay for this
domain and not listed in the MX records, so potentially another customer
doing relay-testing could submit your systems as being an open relay to
one of the black-lists; you might want to amend the below to also check
that the mail was received from an internal system or localhost,
excluding systems to which customers have access. Ah, memories of the
joys of customers who submit their smarthosts as being open relays ...

Something like this untested example after dnslookup (adjusting
appropriately if using a smarthost for outbound mail):

not_yet_hosted:
driver = dnslookup
domains = +local_domains
self = pass
transport = remote_smtp
ignore_target_hosts = 0.0.0.0 : 127.0.0.0/8

The "self = pass" ensures that if the DNS MX highest priority item
points to the local host then processing continues as normal with the
next Router in the file.

Some people might not like this approach. You might instead use a
different domains option or a "condition = XYZ" option to restrict when
this is triggered; that last one is valid, if more complex.

If you have a higher-priority MX host which you don't want to use if the
mail has already reached you here, then you can't use self=host. If you
have a cluster of hosts then you *MUST* use "hosts_treat_as_local" in
the global configuration so that this Exim instance knows that any of
the IP addresses listed are equivalent and local; otherwise, "self =
pass" would just bounce the mails between the hosts in the cluster until
it reaches the maximum forwarding limit and then bounces.

The next approach is to say "All non-local mail has been handled, so
all mail left should match +local_domains; so I don't need to specify
that, so I can use domains differently"; you would then remove the
self=pass option and change domains to be:

domains = ! @mx_any/ignore=127.0.0.1

So, we send off the mail unless the domain has MX records where one of
my IP addresses can be reached by MX resolution, unless that's the
loopback address. In an IPv6-aware world, instead use:

domains = ! @mx_any/ignore=<;127.0.0.1;::1

If you add the "no_more" option to the Router then failures to handle
the address would result in a bounce; because sometimes during domain
migrations people manage to nuke their MX records entirely or lose mail
acceptance at the previous hoster before getting the MX records changed,
I suspect that you're better off treating this as "accept the mail by
contining on to local processing". I forget how no_more interacts with
an SMTP delivery which returns a 5xx error, you'd probably want to do
more testing about this corner case since the whole reason that you want
the special handling is for the set of corner cases around domain
migration.

You _could_ instead just use @[] in the ignore_target_hosts option, so
that any result pointing to this host is ignored and the router can be
declined; however this will create mail-loops if you have backup MX
records for the domain (and you definitely can't use no_more in this
case). @mx_any is far more resilient.

The rest of this mail is concerned with the condition approach; I don't
recommend doing this. I'm including it as an exploration of the power
exposed by the new list operators in Exim 4.67 and to show just how much
Exim normally does "under the hood" to keep the exposed interface
simple. Any fool can provide a complex interface to complex work but it
takes true talent to manage to provide a powerful simple interface to
complexity as Exim does.

So, you want either "does any address resolving to an IP I have appear
anywhere in the MX" or the "I support only one explicit hostname and my
requirement is that this appears in the MX" as reasons to *NOT* send
away. I'll label these the "aargh" and the "disciplined" approaches.
If you've managed to publicise only one hostname and all customers are
using it and there's no backwards compatibility issues to deal with,
then you can use the disciplined approach.

If your customers can end up with an A record for the domain pointing to
your system and no MX records then this won't be sufficient and you'll
need to add a lot more complexity.

disciplined:
condition = ${if forany{${lookup dnsdb{mxh=$domain}}}{eq{OFFICIAL_NAME}{$item}} {no}{yes}}

So, given OFFICIAL_NAME defined as a macro at the start of the
configuration, then for any hostname resulting from looking up the MX
for $domain, the 'forany' is true if that hostname is OFFICIAL_NAME. We
then invert the sense via {no}{yes} so that we don't send the mail away
because we do appear in the local list.

aargh:
  condition = ${if forany{\
      ${map{${lookup dnsdb{mxh=$domain}}}{${lookup dnsdb{a=$item}}}}\
    }{${if match_ip{$item}{@[]}}} {no}{yes}}


This assumes that you're not using IPv6 :^( and might well have a
mismatched {} somewhere in it. So, get a list of the hostnames in the
MX records for the domain, change that list to IP addresses by mapping
an IPv4 A record lookup against that list, and for each item in the
resulting list, check if that item is in the list of IP addresses which
the host system has (@[]). It just so happens though that dnsdb lookups
explicitly handle a list as input and internally handle the 'map'
semantics for you. This simplification is left as an exercise to the
reader ;^) with the hint to look at "Multiple dnsdb lookups" in the Exim
Specification.

So, all of that map, with nested lookups, conditionals handling
IPv6/IPv4 and other stuff is provided in the @mx_any domain-list, and
more besides.

In summary: Exim is powerful, Exim 4.67 is more powerful, and I
recommend this new Router immediately after dnslookup has handled all
domains other than +local_domains:

not_yet_hosted:
driver = dnslookup
domains = ! @mx_any/ignore=<;127.0.0.1;::1
transport = remote_smtp
ignore_target_hosts = 0.0.0.0 : 127.0.0.0/8

and setting hosts_treat_as_local in the main section if you have
multiple machines. Oh, I can't remember what happens if you specify a
given precondition twice; it might be possible to say:
domains = +local_domains
domains = ! @mx_any/ignore=<;127.0.0.1;::1
and have the desired effect. Pass.

-Phil