salmon icon indicating copy to clipboard operation
salmon copied to clipboard

Salmon breaks DKIM signatures

Open talwrii opened this issue 6 years ago • 3 comments

DKIM is a standard for signing emails that uses DNS as a certificate store. DKIM's related standard DMARC may result in your mail server silently refusing to accept email messages that have been modified if an email has been in any way modified, assuming that your mail server is adopting the standard (which, for example, gmail does).

This is somewhat problematic for forwarders like salmon, since they must be careful in some circumstances not to modify email messages.

By default salmon aggressively normalizes messages, potentially breaking DKIM forwarding. This normalization can be avoided by setting

Examples of problems this causes: Airbnb/stackoverlay/yahoo mail have a DKIM policy that result in Gmail dropping mail.

An alternative approach might be to remove DKIM header changed the from address and use ReplyTo for this purpose.

talwrii avatar May 24 '18 01:05 talwrii

This branch https://github.com/talwrii/salmon/tree/talwrii--2018-05-17--dkim fixes this issue by letting one switch off salmon's mail normalisation.

The approach used here is quite aggressive - but it seemed a bit perverse to try and get the mail canonicalisation code to do nothing.

talwrii avatar May 24 '18 01:05 talwrii

If you're only wanting to forward a message as-is, the original message is preserved in message.Data:

@route(r"(local)@(domain)", local=r".+", domain=r".+")
def FORWARD(message, local=None, domain=None):
    Relay().deliver(message.Data, To="[email protected]", From="[email protected]")

If you've added some headers of your own before forwarding, but still want to avoid Salmon's sanitization, replace message.Data with message.base.mime_part.

As for your branch, we should parse the DKIM header and work out which headers need to be preserved. This should not be the default, but I could see it being useful.

moggers87 avatar May 24 '18 08:05 moggers87

Thanks for that. It looks like there is no need for this feature. I got tripped up by the existence of two mail classes (salmon.mail.MailRequest and salmon.encoding.MailBase) and didn't realise the deliver accepts strings.

A couple of points for other users.

The From argument to deliver must be provided if message.Data is used as the message object.

    [user@XXXXXXXXXXX] out: Traceback (most recent call last):
    [user@XXXXXXXXXXX] out:   File "/home/user/pymail/app/handlers/sample.py", line 41, in receive
    [user@XXXXXXXXXXX] out:     config.settings.tatw_relay.deliver(message.Data, To="[email protected]")
    [user@XXXXXXXXXXX] out:   File "/home/user/.local/lib/python2.7/site-packages/salmon/server.py", line 126, in deliver
    [user@XXXXXXXXXXX] out:     sender = From or getattr(message, 'From', None) or message['From']
    [user@XXXXXXXXXXX] out: TypeError: string indices must be integers, not unicode

Also, I imagine the From header will be part of the DMARC signed headers (h=From:Subject:Date:To:MIME-Version:Content-Type * and I believe is used to verify the signature.

So the sample one wants is:

@route(r"(local)@(domain)", local=r".+", domain=r".+")
def FORWARD(message, local=None, domain=None):
    Relay().deliver(message.Data, To="[email protected]", From=message.From)

If one wants to change From on might prefer to use the ReplyTo heading - there is an edge case where the DKIM header prevents you from changing this. Though to do this one must de-encoding and re-encoding the message. I had success doing this email itself. Although, if you are changing From you can just get rid of the DKIM signature itself (assuming you don't have your own DNS DKIM message)

talwrii avatar Jun 05 '18 11:06 talwrii