From dc68e97937bf589b31bde1b083c6530c94ec14b4 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Fri, 9 Jun 2023 23:17:26 +0200 Subject: [PATCH] remail: Make From header mangling less convoluted Due to the requirement to reencrypt the incoming mail, remail must rewrite the mail headers and create a new email with the 'From:' header being the list address. So remail implemented a scheme to mangle the original senders name into the reencrypted mails 'From:' header. That fell flat on its nose when there was an incoming mail which had no name part and just consisted of the actual email address. Instead of differentiating between these cases the authors sleep deprived and grump laden brain decided to implement 'From:' mangling in the way it is now. It converts the original sender mail address '[Name] ' to: 'list-name for mailname_at_sender.domain' This is hard to read and follow. Use the common format: 'Name via list-name' Except for the case where the original 'From:' header is a plain email address without a name. This will mangle it to: 'mailname at sender.domain via list-name' The 'via list-name' add on is technically not required but is useful to visually differentiate the name in auto-completion. Suggested-by: Linus Torvalds Signed-off-by: Thomas Gleixner --- remail/maillist.py | 55 ++++++++++++++++++++++++++++++++++++---------- 1 file changed, 44 insertions(+), 11 deletions(-) diff --git a/remail/maillist.py b/remail/maillist.py index b399ff7..c87a986 100644 --- a/remail/maillist.py +++ b/remail/maillist.py @@ -14,7 +14,7 @@ from remail.gpg import gpg_crypt, RemailGPGException from remail.tracking import account_tracking from remail.config import accounts_config, gpg_config, smime_config -from email.utils import make_msgid, formatdate, getaddresses +from email.utils import make_msgid, formatdate, getaddresses, parseaddr from email.policy import EmailPolicy from flufl.bounce import all_failures @@ -22,6 +22,10 @@ from ruamel.yaml import YAML import mailbox import os +import re + +mustquote = re.compile(r'[][\\()<>@,:;".]') +escapedquote = re.compile(r'[\\"]') class maillist(object): ''' @@ -228,24 +232,52 @@ class maillist(object): dest.toadmins = True dest.accounts = self.config.admins - def mangle_from(self, msg, mfrom): + def mangle_from(self, msg, name, mfrom): ''' - Build 'From' string so the original 'From' is 'visible': - From: $LISTNAME for $ORIGINAL_FROM <$LISTADDRESS> + If @name is non-empty then replace the 'From:' header with: - If $ORIGINAL_FROM does not contain a name, mangle the email - address by replacing @ with _at_ + '@name via list-name' + + If @name is empty mangle the email address @mfrom by replacing + '@' with at and setting the 'From:' header to: + + '@mfrom via list-name' ''' - mfrom = mfrom.replace('@','_at_') - return '%s for %s <%s>' % (self.config.name, mfrom, - self.config.listaddrs.post) + if not len(name): + name = mfrom.replace('@',' at ') + + # Compose the display name + name = '%s via %s' %(name, self.config.name) + + # Check whether it must be quoted + # + # email.utils.formataddr() fails to do so for non-ascii display + # names which causes some email-clients to display completely + # nonsensical names. Especially for the typical big corporate + # "name, surname" patterns as the resulting header entry becomes + # + # From: =?utf-8?q?NAME?=, =?utf-8?q?SURNAME?= + # which some clients render as: + # NAME@$SOMEMADEUPDOMAIN, SURNAME + # + # instead of + # From: =?utf-8?q?NAME=2C_SURNAME?= + # which they correctly render as: + # "NAME, SURNAME" + # + if mustquote.search(name): + name = escapedquote.sub(r'\\\g<0>', name) + name = '"%s"' %name + + # The result is correcly handled by msg['From'] = result + return '%s <%s>' %(name, self.config.listaddrs.post) def do_process_mail(self, msg, dest): msgid = msg.get('Message-Id', '') msgto = msg.get('To') origfrom = msg.get('From') - msgfrom = get_raw_email_addr(origfrom) + name, msgfrom = parseaddr(origfrom) sinfo = sender_info(msg) # Archive the incoming mail @@ -264,7 +296,8 @@ class maillist(object): self.archive_mail(msg_plain, admin=dest.toadmin) - mfrom = self.mangle_from(msg, msgfrom) + mfrom = self.mangle_from(msg, name, msgfrom) + # Save sender information in the outgoing message? if self.config.attach_sender_info: # Only do so for non-subscribers