Re: [exim] SPF and DKIM error processing when receiving emai…

Top Page
Delete this message
Reply to this message
Author: Mike Tubby
Date:  
To: exim-users
Subject: Re: [exim] SPF and DKIM error processing when receiving emails


On 06/11/2020 11:53, Mark Elkins via Exim-users wrote:
> I've got the following in exim.conf....
>
> acl_check_dkim:
>     deny dkim_status = fail
>             message = DKIM validation failed: $dkim_verify_status
>             log_message = DKIM validation failed: $dkim_verify_status \
>                 (address=$sender_address, domain=$dkim_cur_signer), \
>                 signature is bad
>     defer dkim_status = invalid
>             message = DKIM signature invalid: $dkim_verify_status
>             log_message = DKIM signature invalid: $dkim_verify_status \
>                 (address=$sender_address, domain=$dkim_cur_signer), \
>                 invalid signature
>     # NOTE: dkim_status = none should never happen in this ACL
>     accept
>             # Add an X-DKIM header to the message
>             add_header = :at_start: X-DKIM: DKIM validation passed: \
>                 (address=$sender_address domain=$dkim_cur_signer), \
>                 signature is good
>             logwrite = DKIM validation passed
>
> This is breaking some of my customers...
> How can I soften the blow? - so they can get their incorrectly signed
> emails from these broken servers (some of which live in my countries
> banking system and are otherwise completely valid)
>
> I've got something similar for SPF....
>
> # SPF Checks
> acl_check_mail:
>
>   # SPF validation
>   deny spf = fail : softfail
>           message = SPF validation failed: \
>                   $sender_host_address is not allowed to send mail from \
>                   ${if def:sender_address_domain \
>                       {$sender_address_domain}{$sender_helo_name}}
>           log_message = SPF validation failed\
>                   ${if eq{$spf_result}{softfail} { (softfail)}{}}: \
>                   $sender_host_address is not allowed to send mail from \
>                   ${if def:sender_address_domain \
>                       {$sender_address_domain}{$sender_helo_name}}
>   deny spf = permerror
>           message = SPF validation failed: \
>                   syntax error in SPF record(s) for \
>                   ${if def:sender_address_domain \
>                       {$sender_address_domain}{$sender_helo_name}}
>           log_message = SPF validation failed (permerror): \
>                   syntax error in SPF record(s) for \
>                   ${if def:sender_address_domain \
>                       {$sender_address_domain}{$sender_helo_name}}
>   defer spf = temperror
>           message = temporary error during SPF validation; \
>                   please try again later
>           log_message = SPF validation failed temporary; deferred
>   # Log SPF none/neutral result
>   warn spf = none : neutral
>           log_message = SPF validation none/neutral
>
>   # Use the lack of reverse DNS to trigger greylisting. Some people
>   # even reject for it but that would be a little excessive.
>
>   warn condition = ${if eq{$sender_host_name}{} {1}}
>        set acl_m_greylistreasons = Host $sender_host_address \
>            lacks reverse DNS\n$acl_m_greylistreasons
>
>   accept
>           # Add an SPF-Received header to the message
>           add_header = :at_start: $spf_received
>           logwrite = SPF validation passed
>
> I think I have to allow this sort of stuff through for now - but would
> love it to come through with readable error messages for now - give
> people a chance to fix their errors.
>




Many suggest that DKIM be used as part of wider DMARC and with layered
policy and the equivalent of 'soft fail' etc. however I developed my
DKIM support and processing very early on, i.e. early days of DKIM and
before DMARC, hence I have an implementation with our company policy
baked in.

We support several hundred domains and everything has a MySQL backend. 
Mail processing is shared between three front-end relay boxes [relay1,
relay2 and relay3] with two on one site and one on a different site
which gives us server, site and power resilience. Relay1 is the primary
and holds the master database rel2 and rely3 replicate from it.

We have the concept of 'known signers' - these are sites (domains) that
we know correctly sign and therefor we can hard enforce, for example:

MariaDB [relay]> select * from dkim_known_signers limit 15;
+------+--------+------------------+
| id   | active | domain           |

+------+--------+------------------+
|    1 |      1 | ebay.com         |
|    3 |      1 | paypal.com       |
|    4 |      1 | paypal.co.uk     |
|    2 |      1 | ebay.co.uk       |
|    5 |      1 | yahoo.com        |
|    6 |      1 | yahoo.co.uk      |
|    7 |      1 | gmail.com        |
|    8 |      1 | googlemail.com   |
|    9 |      1 | google.com       |
|   10 |      1 | facebook.com     |
|   11 |      1 | facebookmail.com |
|   12 |      1 | fwd.facebook.com |
|   13 |      1 | spc.facebook.com |
|   14 |      1 | googlegroups.com |
|   15 |      1 | groups.io        |
+------+--------+------------------+

We have the concept of 'whitelisted domains' - ones we will allow in
with broken DKIM (and potentially other broken stuff).


Our policy can be described:

    if you are known good signer - test for good signatures only - drop
everything else

    if you are white listed, let you in with bad signatures

    if you are neither 'known signer' or 'whitelisted' and have a
signature treat it at face value:
            pass => let you in
            invalid => can't get public key => defer
            fail => reject

    if you don't have a signature, let you in

To get a sample of what's going on out there, Relay1 writes back all the
DKIM it sees to the database - this gives us an insight into what's
failing and why.  We find house-hold names with misconfigured servers
including Microsoft, Amazon, HSBC, Plenty-of-Fish, Capita and government
departments.

In every case that I have investigated from our capture database there
has been a genuine problem at the other end.

You might care to pick some bits out of my DKIM ACL (below).


Mike





DKIM_WHITELIST_DOMAINS = select domain from dkim_whitelist_domains
domainlist dkim_whitelist_domains = ${lookup
mysql{DKIM_WHITELIST_DOMAINS}{${sg{$value}{\\n}{ : }} }}

DKIM_KNOWN_SIGNERS = select domain from dkim_known_signers where active=1;
domainlist dkim_known_signers = ${lookup
mysql{DKIM_KNOWN_SIGNERS}{${sg{$value}{\\n}{ : }} }}




###
### acl_check_dkim: this ACL is used for checking DKIM
###

#
# acl_m2 set to zero on start for normal/full checks, set to 1 if
white-listed domain
#

acl_check_dkim:

        #
        # start of DKIM debug message and clear macro
        #

        #
        # Only do MySQL INSERT on Relay1 !!!
        #
        warn    set acl_m_dummy = ${lookup mysql{INSERT INTO dkim_log
(status,reason,host,domain,identity,selector,algo) VALUES
('${quote_mysql:$dkim_verify_status}',
'${quote_mysql:$dkim_verify_reason}','${quote_mysql:$sender_fullhost}',
'${quote_mysql:$dkim_domain}', '${quote_mysql:$dkim_identity}',
'${quote_mysql:$dkim_selector}', '${quote_mysql:$dkim_algo}' )}}
                log_message = DKIM START: domain=$sender_address_domain
possible_signer=$dkim_cur_signer status=$dkim_verify_status ${if
def:dkim_verify_reason {(reason=$dkim_verify_reason)

        #
        # strict checking on known signers...
        #
        deny    sender_domains = +dkim_known_signers
                dkim_status = none:invalid:fail
                message = Message from $sender_address_domain (known
signer) with invalid or missing signature
                log_message = DKIM DENY: Rejected
$sender_address_domain is known signer (in database) but has
invalid/missing signature

        accept  sender_domains = +dkim_known_signers
                dkim_status = pass
                log_message = DKIM PASS: Accepted
$sender_address_domain is known signer and has good signature
                add_header = :after_received:X-DKIM-Result:
Domain=$sender_address_domain Result=Good and Known Domain

        #
        # ignore noise where we have no signature
        #
        accept  dkim_status = none
#               log_message = DKIM SKIP: Skipping DKIM checks - no
signature for: $dkim_cur_signer

        #
        # skip DKIM if domain whitelisted for DKIM, i.e. known good
domain that has broken DKIM
        #
        accept  sender_domains = +dkim_whitelist_domains
                log_message = DKIM SKIP: Skipping DKIM checks for
whitelisted domain: $sender_address_domain
                set acl_m2 = 1

        #
        # skip DKIM checks on hosts we relay for
        #
        accept  hosts = +relay_from_hosts
                log_message = DKIM SKIP: Skipping DKIM checks for relay
host: $sender_fullhost

        #
        # skip DKIM checks on authenticated hosts (that we also relay for)
        #
        accept  authenticated = *
                log_message = DKIM SKIP: Skipping DKIM checks for
authenticated host: $sender_fullhost

        #
        # defer when message not testable, e.g. can't get public key, etc.
        #
        defer   dkim_status = invalid
                message = Message from $sender_address_domain cannot be
verified
                log_message = DKIM DEFER: domain=$sender_address_domain
cannot obtain public key

        #
        # accept the message (correctly signed)
        #
        accept  dkim_status = pass
                sender_domains = $sender_address_domain
                dkim_signers = $sender_address_domain
                log_message = DKIM PASS: domain=$sender_address_domain
signer=$dkim_cur_signer status=$dkim_verify_status
                add_header = :after_received:X-DKIM-Result:
Domain=$sender_address_domain Result=Signature OK

        #
        # accept the message EVEN IF the signature FAILS! due to white
listing
        #
        accept  condition = ${if eq {$acl_m2}{1}}
                dkim_status = fail
                sender_domains = $sender_address_domain
                dkim_signers = $sender_address_domain
                log_message = DKIM FAIL (BUT WHITELISTED):
domain=$sender_address_domain status=$dkim_verify_status - DKIM failed
but message accepted
                add_header = :after_received:X-DKIM-Result:
Domain=$sender_address_domain Result=FAIL (but whitelisted)

        #
        # deny (strict) when message fails signature test *and* acl_m2
= 0 (not whitelisted)
        #
        deny    condition = ${if eq {$acl_m2}{0}}
                dkim_status = fail
                sender_domains = $sender_address_domain
                dkim_signers = $sender_address_domain
                message = Message from has invalid DKIM signature
                log_message = DKIM FAIL (DENY):
domain=$sender_address_domain - message rejected!

        #
        # accept anything else (should never get here)
        #
        accept  log_message = DKIM DEFAULT:
domain=$sender_address_domain - message accepted (at end of ACL)