remail: Add X-Original-From header

The 'From:' header mangling makes it unnecessarily hard to figure out the
name and email address of the person who posted to a list.

Save the original sender in the 'X-Original-From:' header.

Suggested-by: Linus Torvalds <torvalds@linux-foundation.org>
Suggested-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Link: https://wiki.ietf.org/group/dmarc/XOriginalFrom
This commit is contained in:
Thomas Gleixner 2023-06-09 23:12:58 +02:00
parent ea4b0b4632
commit 424944ad36
2 changed files with 21 additions and 6 deletions

View file

@ -11,6 +11,7 @@ from email.generator import Generator
from email.message import Message, EmailMessage from email.message import Message, EmailMessage
from email.policy import EmailPolicy from email.policy import EmailPolicy
from email.policy import default as DefaultPolicy from email.policy import default as DefaultPolicy
from email.headerregistry import UniqueSingleAddressHeader
import smtplib import smtplib
import mailbox import mailbox
@ -75,13 +76,21 @@ def msg_copy_headers(msgout, msg, headers):
if val: if val:
msgout[key] = val msgout[key] = val
def send_mail(msg, account, mfrom, sender, listheaders, use_smtp, logger): def send_mail(msg, account, mfrom, sender, listheaders, use_smtp, logger,
origfrom=None):
''' '''
Send mail to the account. Make sure that the message is correct and all Send mail to the account. Make sure that the message is correct and all
required headers and only necessary headers are in the outgoing mail. required headers and only necessary headers are in the outgoing mail.
''' '''
# Convert to EmailMessage with default policy (utf-8=false) so both # Clone the default policy (utf-8=false) and map the X-Original-From
# header to UniqueSingleAddressHeader. Otherwise this is treated as
# unspecified header.
policy = DefaultPolicy.clone()
policy.header_factory.map_to_type('X-Original-From',
UniqueSingleAddressHeader)
# Convert to EmailMessage with the modified default policy so both
# smptlib.send_message() and the stdout dump keep the headers properly # smptlib.send_message() and the stdout dump keep the headers properly
# encoded. # encoded.
parser = BytesFeedParser(policy=DefaultPolicy) parser = BytesFeedParser(policy=DefaultPolicy)
@ -109,6 +118,10 @@ def send_mail(msg, account, mfrom, sender, listheaders, use_smtp, logger):
msg_copy_headers(msgout, msg, ['Content-Type', 'Content-Disposition', msg_copy_headers(msgout, msg, ['Content-Type', 'Content-Disposition',
'Content-Transfer-Encoding', 'Content-Transfer-Encoding',
'Content-Language']) 'Content-Language'])
if origfrom:
msgout['X-Original-From'] = origfrom
msgout['Envelope-To'] = get_raw_email_addr(account.addr) msgout['Envelope-To'] = get_raw_email_addr(account.addr)
# Set unixfrom with the current date/time # Set unixfrom with the current date/time

View file

@ -86,11 +86,12 @@ class maillist(object):
self.gpg.encrypt(msg, account) self.gpg.encrypt(msg, account)
return msg return msg
def send_encrypted_mail(self, msg_plain, account, mfrom): def send_encrypted_mail(self, msg_plain, account, mfrom, origfrom=None):
try: try:
msg_out = self.encrypt(msg_plain, account) msg_out = self.encrypt(msg_plain, account)
send_mail(msg_out, account, mfrom, self.config.listaddrs.bounce, send_mail(msg_out, account, mfrom, self.config.listaddrs.bounce,
self.config.listheaders, self.use_smtp, self.logger) self.config.listheaders, self.use_smtp, self.logger,
origfrom)
except (RemailGPGException, RemailSmimeException) as ex: except (RemailGPGException, RemailSmimeException) as ex:
''' '''
@ -243,7 +244,8 @@ class maillist(object):
msgid = msg.get('Message-Id', '<No ID>') msgid = msg.get('Message-Id', '<No ID>')
msgto = msg.get('To') msgto = msg.get('To')
msgfrom = get_raw_email_addr(msg.get('From')) origfrom = msg.get('From')
msgfrom = get_raw_email_addr(origfrom)
sinfo = sender_info(msg) sinfo = sender_info(msg)
# Archive the incoming mail # Archive the incoming mail
@ -272,7 +274,7 @@ class maillist(object):
for account in dest.accounts.values(): for account in dest.accounts.values():
if not account.enabled: if not account.enabled:
continue continue
self.send_encrypted_mail(msg_plain, account, mfrom) self.send_encrypted_mail(msg_plain, account, mfrom, origfrom)
return True return True
def handle_log(self): def handle_log(self):