diff options
-rw-r--r-- | Mailman/Gui/Privacy.py | 24 | ||||
-rw-r--r-- | Mailman/Handlers/Moderate.py | 25 | ||||
-rwxr-xr-x | Mailman/MailList.py | 2 | ||||
-rw-r--r-- | Mailman/Utils.py | 39 | ||||
-rwxr-xr-x | Mailman/versions.py | 2 |
5 files changed, 92 insertions, 0 deletions
diff --git a/Mailman/Gui/Privacy.py b/Mailman/Gui/Privacy.py index 75eff2b5..ae78b209 100644 --- a/Mailman/Gui/Privacy.py +++ b/Mailman/Gui/Privacy.py @@ -235,6 +235,30 @@ class Privacy(GUIBase): >rejection notice</a> to be sent to moderated members who post to this list.""")), + ('dmarc_moderation_action', mm_cfg.Radio, + (_('Hold'), _('Reject'), _('Discard')), 0, + _("""Action to take when anyone posts to the + list from a domain with a DMARC Reject Policy."""), + _("""<ul><li><b>Hold</b> -- this holds the message for approval + by the list moderators. + + <p><li><b>Reject</b> -- this automatically rejects the message by + sending a bounce notice to the post's author. The text of the + bounce notice can be <a + href="?VARHELP=privacy/sender/dmarc_moderation_notice" + >configured by you</a>. + + <p><li><b>Discard</b> -- this simply discards the message, with + no notice sent to the post's author. + </ul>""")), + + ('dmarc_moderation_notice', mm_cfg.Text, (10, WIDTH), 1, + _("""Text to include in any + <a href="?VARHELP/privacy/sender/dmarc_moderation_action" + >rejection notice</a> to + be sent to anyone who posts to this list from a domain + with DMARC Reject Policy.""")), + _('Non-member filters'), ('accept_these_nonmembers', mm_cfg.EmailListEx, (10, WIDTH), 1, diff --git a/Mailman/Handlers/Moderate.py b/Mailman/Handlers/Moderate.py index 199c97ac..81b554e4 100644 --- a/Mailman/Handlers/Moderate.py +++ b/Mailman/Handlers/Moderate.py @@ -56,6 +56,31 @@ def process(mlist, msg, msgdata): else: sender = None if sender: + if Utils.IsDmarcProhibited(sender): + # Note that for dmarc_moderation_action, 0==Hold, 1=Reject, + # 2==Discard + if mlist.dmarc_moderation_action == 0: + msgdata['sender'] = sender + Hold.hold_for_approval(mlist, msg, msgdata, + ModeratedMemberPost) + elif mlist.dmarc_moderation_action == 1: + # Reject + text = mlist.dmarc_moderation_notice + if text: + text = Utils.wrap(text) + else: + # Use the default RejectMessage notice string + text = None + raise Errors.RejectMessage, text + elif mlist.dmarc_moderation_action == 2: + raise Errors.DiscardMessage + else: + assert 0, 'bad dmarc_moderation_action' + + # sender's domain has a 'p=reject' _dmarc TXT record, + # we should NOT automatically reflect this email + return + # If the member's moderation flag is on, then perform the moderation # action. if mlist.getMemberOption(sender, mm_cfg.Moderate): diff --git a/Mailman/MailList.py b/Mailman/MailList.py index 2d653acb..a51f4ea6 100755 --- a/Mailman/MailList.py +++ b/Mailman/MailList.py @@ -388,6 +388,8 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin, # 2==Discard self.member_moderation_action = 0 self.member_moderation_notice = '' + self.dmarc_moderation_action = 0 + self.dmarc_moderation_notice = '' self.accept_these_nonmembers = [] self.hold_these_nonmembers = [] self.reject_these_nonmembers = [] diff --git a/Mailman/Utils.py b/Mailman/Utils.py index 93e1fba1..a2cc0caa 100644 --- a/Mailman/Utils.py +++ b/Mailman/Utils.py @@ -71,6 +71,13 @@ except NameError: True = 1 False = 0 +try: + import dns.resolver + from dns.exception import DNSException + dns_resolver = True +except ImportError: + dns_resolver = False + EMPTYSTRING = '' UEMPTYSTRING = u'' NL = '\n' @@ -1057,3 +1064,35 @@ def suspiciousHTML(html): else: return False + +# This takes an email address, and returns True if DMARC policy is p=reject +def IsDmarcProhibited(email): + if not dns_resolver: + return False + + email = email.lower() + at_sign = email.find('@') + if at_sign < 1: + return False + dmarc_domain = '_dmarc.' + email[at_sign+1:] + + try: + resolver = dns.resolver.Resolver() + resolver.timeout = 1 + resolver.lifetime = 5 + txt_recs = resolver.query(dmarc_domain, dns.rdatatype.TXT) + except dns.resolver.NXDOMAIN: + return False + except DNSException, e: + syslog('error', 'DNSException: Unable to query DMARC policy for %s (%s). %s', + email, dmarc_domain, e.__class__) + return False + else: + for txt_rec in txt_recs.response.answer: + assert( txt_rec.rdtype == dns.rdatatype.TXT) + if re.search(r"[^s]p=reject", "".join(txt_rec.items[0].strings), re.IGNORECASE): + return True + + return False + + diff --git a/Mailman/versions.py b/Mailman/versions.py index 84943efc..02d266da 100755 --- a/Mailman/versions.py +++ b/Mailman/versions.py @@ -385,6 +385,8 @@ def NewVars(l): # the current GUI description model. So, 0==Hold, 1==Reject, 2==Discard add_only_if_missing('member_moderation_action', 0) add_only_if_missing('member_moderation_notice', '') + add_only_if_missing('dmarc_moderation_action', 0) + add_only_if_missing('dmarc_moderation_notice', '') add_only_if_missing('new_member_options', mm_cfg.DEFAULT_NEW_MEMBER_OPTIONS) # Emergency moderation flag |