summaryrefslogtreecommitdiff
path: root/src/mailman/handlers/dmarc.py
diff options
context:
space:
mode:
authorBarry Warsaw2017-01-04 00:26:59 -0500
committerBarry Warsaw2017-01-04 00:26:59 -0500
commit4afccbf0b1800eec6042bbe4f6dcc300165184ad (patch)
tree4c23617ce9de374d76be3859c67afd19dbebbe7c /src/mailman/handlers/dmarc.py
parentb9883ecec0f8ad6dd4d4b84b4c7bac35183172c7 (diff)
downloadmailman-4afccbf0b1800eec6042bbe4f6dcc300165184ad.tar.gz
mailman-4afccbf0b1800eec6042bbe4f6dcc300165184ad.tar.zst
mailman-4afccbf0b1800eec6042bbe4f6dcc300165184ad.zip
Diffstat (limited to 'src/mailman/handlers/dmarc.py')
-rw-r--r--src/mailman/handlers/dmarc.py97
1 files changed, 51 insertions, 46 deletions
diff --git a/src/mailman/handlers/dmarc.py b/src/mailman/handlers/dmarc.py
index 99d8e08df..13707d397 100644
--- a/src/mailman/handlers/dmarc.py
+++ b/src/mailman/handlers/dmarc.py
@@ -43,6 +43,7 @@ from zope.interface import implementer
log = logging.getLogger('mailman.error')
COMMASPACE = ', '
+EMPTYSTRING = ''
MAXLINELEN = 78
NONASCII = re.compile('[^\s!-~]')
# Headers from the original that we want to keep in the wrapper. These are
@@ -84,48 +85,49 @@ def munged_headers(mlist, msg, msgdata):
# goals is priority order.
#
# Be as robust as possible here.
- faddrs = getaddresses(msg.get_all('from', []))
+ all_froms = getaddresses(msg.get_all('from', []))
# Strip the nulls and bad emails.
- faddrs = [x for x in faddrs if x[1].find('@') > 0]
- if len(faddrs) == 1:
- realname, email = o_from = faddrs[0]
+ froms = [email for email in all_froms if '@' in email[1]]
+ if len(froms) == 1:
+ realname, email = original_from = froms[0]
else:
# No From: or multiple addresses. Just punt and take
# the get_sender result.
realname = ''
email = msgdata['original_sender']
- o_from = (realname, email)
+ original_from = (realname, email)
+ # If there was no display name in the email header, see if we have a
+ # matching member with a display name.
if len(realname) == 0:
member = mlist.members.get_member(email)
if member:
realname = member.display_name or email
else:
realname = email
- # Remove domain from realname if it looks like an email address.
+ # Remove the domain from realname if it looks like an email address.
realname = re.sub(r'@([^ .]+\.)+[^ .]+$', '---', realname)
# Make a display name and RFC 2047 encode it if necessary. This is
- # difficult and kludgy. If the realname came from From: it should be
- # ascii or RFC 2047 encoded. If it came from the list, it should be
- # a string. If it's from the email address, it should be an ascii string.
- # In any case, ensure it's an unencoded string.
- srn = ''
- for frag, cs in decode_header(realname):
- if not cs:
- # Character set should be ascii, but use iso-8859-1 anyway.
- cs = 'iso-8859-1'
- if not isinstance(frag, str):
- srn += str(frag, cs, errors='replace')
+ # difficult and kludgy. If the realname came from From: it should be
+ # ASCII or RFC 2047 encoded. If it came from the member record, it should
+ # be a string. If it's from the email address, it should be an ASCII
+ # string. In any case, ensure it's an unencoded string.
+ realname_bits = []
+ for fragment, charset in decode_header(realname):
+ if not charset:
+ # Character set should be ASCII, but use iso-8859-1 anyway.
+ charset = 'iso-8859-1'
+ if not isinstance(fragment, str):
+ realname_bits.append(str(fragment, charset, errors='replace'))
else:
- srn += frag
- # The list's real_name is a string.
- lrn = mlist.display_name # noqa F841
- realname = srn
+ realname_bits.append(fragment)
+ # The member's display name is a string.
+ realname = EMPTYSTRING.join(realname_bits)
# Ensure the i18n context is the list's preferred_language.
with _.using(mlist.preferred_language.code):
- via = _('$realname via $lrn')
+ via = _('$realname via $mlist.display_name')
# Get an RFC 2047 encoded header string.
- dn = str(Header(via, mlist.preferred_language.charset))
- retn = [('From', formataddr((dn, mlist.posting_address)))]
+ display_name = str(Header(via, mlist.preferred_language.charset))
+ value = [('From', formataddr((display_name, mlist.posting_address)))]
# We've made the munged From:. Now put the original in Reply-To: or Cc:
if mlist.reply_goes_to_list is ReplyToMunging.no_munging:
# Add original from to Reply-To:
@@ -133,52 +135,54 @@ def munged_headers(mlist, msg, msgdata):
else:
# Add original from to Cc:
add_to = 'Cc'
- orig = getaddresses(msg.get_all(add_to, []))
- if o_from[1] not in [x[1] for x in orig]:
- orig.append(o_from)
- retn.append((add_to, COMMASPACE.join(formataddr(x) for x in orig)))
- return retn
+ original = getaddresses(msg.get_all(add_to, []))
+ if original_from[1] not in [x[1] for x in original]:
+ original.append(original_from)
+ value.append((add_to, COMMASPACE.join(formataddr(x) for x in original)))
+ return value
def munge_from(mlist, msg, msgdata):
- for k, v in munged_headers(mlist, msg, msgdata):
- del msg[k]
- msg[k] = v
+ for key, value in munged_headers(mlist, msg, msgdata):
+ del msg[key]
+ msg[key] = value
return
def wrap_message(mlist, msg, msgdata):
# Create a wrapper message around the original.
+ #
# There are various headers in msg that we don't want, so we basically
- # make a copy of the msg, then delete almost everything and set/copy
+ # make a copy of the message, then delete almost everything and set/copy
# what we want.
- omsg = copy.deepcopy(msg)
+ original_msg = copy.deepcopy(msg)
for key in msg:
keep = False
for keeper in KEEPERS:
- if re.match(keeper, key, re.I):
+ if re.match(keeper, key, re.IGNORECASE):
keep = True
break
if not keep:
del msg[key]
msg['MIME-Version'] = '1.0'
msg['Message-ID'] = make_msgid()
- for k, v in munged_headers(mlist, omsg, msgdata):
- msg[k] = v
+ for key, value in munged_headers(mlist, original_msg, msgdata):
+ msg[key] = value
# Are we including dmarc_wrapped_message_text?
if len(mlist.dmarc_wrapped_message_text) > 0:
- part1 = MIMEText(wrap(mlist.dmarc_wrapped_message_text),
- 'plain',
- mlist.preferred_language.charset)
+ part1 = MIMEText(
+ wrap(mlist.dmarc_wrapped_message_text),
+ 'plain',
+ mlist.preferred_language.charset)
part1['Content-Disposition'] = 'inline'
- part2 = MIMEMessage(omsg)
+ part2 = MIMEMessage(original_msg)
part2['Content-Disposition'] = 'inline'
msg['Content-Type'] = 'multipart/mixed'
msg.set_payload([part1, part2])
else:
msg['Content-Type'] = 'message/rfc822'
msg['Content-Disposition'] = 'inline'
- msg.set_payload([omsg])
+ msg.set_payload([original_msg])
return
@@ -186,7 +190,7 @@ def process(mlist, msg, msgdata):
# If we're mitigating on policy and we have no hit, return.
if not msgdata.get('dmarc') and not mlist.dmarc_mitigate_unconditionally:
return
- # if we're not mitigating, return.
+ # If we're not mitigating, return.
if mlist.dmarc_mitigate_action is DMARCMitigateAction.no_mitigation:
return
if mlist.anonymous_list:
@@ -198,10 +202,11 @@ def process(mlist, msg, msgdata):
elif mlist.dmarc_mitigate_action is DMARCMitigateAction.wrap_message:
wrap_message(mlist, msg, msgdata)
else:
- # We can get here if DMARCMitigateAction is reject or discard
- # but the From: domain has no reject or quarantine policy and
- # mlist.dmarc_mitigate_unconditionally is True. We just ignore
+ # We can get here if DMARCMitigateAction is reject or discard but
+ # the From: domain has no reject or quarantine policy and
+ # mlist.dmarc_mitigate_unconditionally is True. Log and ignore
# this.
+ log.error('Invalid DMARC combination for list: %s', mlist)
return
else:
raise AssertionError(