diff options
| -rw-r--r-- | src/mailman/config/schema.cfg | 40 | ||||
| -rw-r--r-- | src/mailman/rules/dmarc.py | 46 |
2 files changed, 46 insertions, 40 deletions
diff --git a/src/mailman/config/schema.cfg b/src/mailman/config/schema.cfg index fdf4c2b6f..33fac3716 100644 --- a/src/mailman/config/schema.cfg +++ b/src/mailman/config/schema.cfg @@ -20,6 +20,7 @@ # formats under the lazr.config regime to define all system configuration # options. See <https://launchpad.net/lazr.config> for details. + [mailman] # This address is the "site owner" address. Certain messages which must be # delivered to a human, but which can't be delivered to a list owner (e.g. a @@ -74,22 +75,6 @@ filtered_messages_are_preservable: no # The command should print the converted text to stdout. html_to_plain_text_command: /usr/bin/lynx -dump $filename -# Parameters for DMARC DNS lookups. If you are seeing 'DNSException: Unable -# to query DMARC policy ...' entries in your error log, you may need to adjust -# these. -# -# The time to wait for a response from a name server before timeout. -dmarc_resolver_timeout: 3s -# The total time to spend trying to get an answer to the DNS question. -dmarc_resolver_lifetime: 5s - -# A URL from which to retrieve the data for the algorithm that computes -# Organizational Domains for DMARC policy lookup purposes. This can be -# anything handled by the Python urllib.request.urlopen function. See -# https://publicsuffix.org/list/ for info. -dmarc_org_domain_data: https://publicsuffix.org/list/public_suffix_list.dat - - [shell] # `mailman shell` (also `withlist`) gives you an interactive prompt that you @@ -226,6 +211,7 @@ max_restarts: 10 # ignore this. sleep_time: 1s + [database] # The class implementing the IDatabase. class: mailman.database.sqlite.SQLiteDatabase @@ -238,6 +224,7 @@ class: mailman.database.sqlite.SQLiteDatabase url: sqlite:///$DATA_DIR/mailman.db debug: no + [logging.template] # This defines various log settings. The options available are: # @@ -334,7 +321,6 @@ success: $msgid post to $listname from $sender, $size bytes refused: $msgid post to $listname from $sender, $size bytes, $refused failures failure: $msgid delivery to $recip failed with code $smtpcode, $smtpmsg - [logging.subscribe] [logging.vette] @@ -863,3 +849,23 @@ rewrite_duplicate_headers: CC X-Original-CC Content-Transfer-Encoding X-Original-Content-Transfer-Encoding MIME-Version X-MIME-Version + + +[dmarc] +# RFC 7489 - Domain-based Message Authentication, Reporting, and Conformance. +# https://en.wikipedia.org/wiki/DMARC + +# Parameters for DMARC DNS lookups. If you are seeing 'DNSException: Unable +# to query DMARC policy ...' entries in your error log, you may need to adjust +# these. +# +# The time to wait for a response from a name server before timeout. +resolver_timeout: 3s +# The total time to spend trying to get an answer to the DNS question. +resolver_lifetime: 5s + +# A URL from which to retrieve the data for the algorithm that computes +# Organizational Domains for DMARC policy lookup purposes. This can be +# anything handled by the Python urllib.request.urlopen function. See +# https://publicsuffix.org/list/ for info. +org_domain_data: https://publicsuffix.org/list/public_suffix_list.dat diff --git a/src/mailman/rules/dmarc.py b/src/mailman/rules/dmarc.py index d7e76bf0f..c8c4e56f4 100644 --- a/src/mailman/rules/dmarc.py +++ b/src/mailman/rules/dmarc.py @@ -83,7 +83,7 @@ def _get_org_dom(domain): # Domain which may be the same as the input. global s_dict if not s_dict: - _get_suffixes(config.mailman.dmarc_org_domain_data) + _get_suffixes(config.dmarc.org_domain_data) hits = [] d = domain.lower().split('.') d.reverse() @@ -108,32 +108,12 @@ def _get_org_dom(domain): return _get_dom(d, l) -def _IsDMARCProhibited(mlist, email): - # This takes an email address, and returns True if DMARC policy is - # p=reject or quarantine. - email = email.lower() - # Scan from the right in case quoted local part has an '@'. - local, at, from_domain = email.rpartition('@') - if at != '@': - return False - x = _DMARCProhibited(mlist, email, '_dmarc.{}'.format(from_domain)) - if x is not KEEP_LOOKING: - return x - org_dom = _get_org_dom(from_domain) - if org_dom != from_domain: - x = _DMARCProhibited( - mlist, email, '_dmarc.{}'.format(org_dom), org=True) - if x is not KEEP_LOOKING: - return x - return False - - def _DMARCProhibited(mlist, email, dmarc_domain, org=False): resolver = dns.resolver.Resolver() resolver.timeout = as_timedelta( - config.mailman.dmarc_resolver_timeout).total_seconds() + config.dmarc.resolver_timeout).total_seconds() resolver.lifetime = as_timedelta( - config.mailman.dmarc_resolver_lifetime).total_seconds() + config.dmarc.resolver_lifetime).total_seconds() try: txt_recs = resolver.query(dmarc_domain, dns.rdatatype.TXT) except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer): @@ -210,6 +190,26 @@ def _DMARCProhibited(mlist, email, dmarc_domain, org=False): return False +def _IsDMARCProhibited(mlist, email): + # This takes an email address, and returns True if DMARC policy is + # p=reject or quarantine. + email = email.lower() + # Scan from the right in case quoted local part has an '@'. + local, at, from_domain = email.rpartition('@') + if at != '@': + return False + x = _DMARCProhibited(mlist, email, '_dmarc.{}'.format(from_domain)) + if x is not KEEP_LOOKING: + return x + org_dom = _get_org_dom(from_domain) + if org_dom != from_domain: + x = _DMARCProhibited( + mlist, email, '_dmarc.{}'.format(org_dom), org=True) + if x is not KEEP_LOOKING: + return x + return False + + @public @implementer(IRule) class DMARCMitigation: |
