salmon
salmon copied to clipboard
Salmon breaks DKIM signatures
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.
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.
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.
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)