Merge lp:~phil.pennock/mailman/dmarc-reject into lp:~jimpop/mailman/dmarc-reject

Proposed by Phil Pennock
Status: Merged
Approved by: Jim Popovitch
Approved revision: 1376
Merged at revision: 1376
Proposed branch: lp:~phil.pennock/mailman/dmarc-reject
Merge into: lp:~jimpop/mailman/dmarc-reject
Diff against target: 71 lines (+44/-5)
1 file modified
Mailman/Utils.py (+44/-5)
To merge this branch: bzr merge lp:~phil.pennock/mailman/dmarc-reject
Reviewer Review Type Date Requested Status
Jim Popovitch Approve
Review via email: mp+153947@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Jim Popovitch (jimpop) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'Mailman/Utils.py'
2--- Mailman/Utils.py 2013-03-03 08:04:37 +0000
3+++ Mailman/Utils.py 2013-03-18 22:17:21 +0000
4@@ -35,6 +35,7 @@
5 import base64
6 import random
7 import urlparse
8+import collections
9 import htmlentitydefs
10 import email.Header
11 import email.Iterators
12@@ -1081,18 +1082,56 @@
13 resolver.timeout = 1
14 resolver.lifetime = 5
15 txt_recs = resolver.query(dmarc_domain, dns.rdatatype.TXT)
16- except dns.resolver.NXDOMAIN:
17+ except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer):
18 return False
19 except DNSException, e:
20 syslog('error', 'DNSException: Unable to query DMARC policy for %s (%s). %s',
21 email, dmarc_domain, e.__class__)
22 return False
23 else:
24+# people are already being dumb, don't trust them to provide honest DNS
25+# where the answer section only contains what was asked for, nor to include
26+# CNAMEs before the values they point to.
27+ full_record = ""
28+ results_by_name = collections.defaultdict(list)
29+ cnames = {}
30+ want_names = set([dmarc_domain + '.'])
31 for txt_rec in txt_recs.response.answer:
32- assert( txt_rec.rdtype == dns.rdatatype.TXT)
33- if re.search(r"[^s]p=reject", "".join(txt_rec.items[0].strings), re.IGNORECASE):
34- return True
35-
36+ if txt_rec.rdtype == dns.rdatatype.CNAME:
37+ cnames[txt_rec.name.to_text()] = txt_rec.items[0].target.to_text()
38+ if txt_rec.rdtype != dns.rdatatype.TXT:
39+ continue
40+ results_by_name[txt_rec.name.to_text()].append("".join(txt_rec.items[0].strings))
41+ expands = list(want_names)
42+ seen = set(expands)
43+ while expands:
44+ item = expands.pop(0)
45+ if item in cnames:
46+ if cnames[item] in seen:
47+ continue # cname loop
48+ expands.append(cnames[item])
49+ seen.add(cnames[item])
50+ want_names.add(cnames[item])
51+ want_names.discard(item)
52+
53+ if len(want_names) != 1:
54+ syslog('error', 'multiple DMARC entries in results for %s, processing each to be strict',
55+ dmarc_domain)
56+ for name in want_names:
57+ if name not in results_by_name:
58+ continue
59+ dmarcs = filter(lambda n: n.startswith('v=DMARC1;'), results_by_name[name])
60+ if len(dmarcs) == 0:
61+ return False
62+ if len(dmarcs) > 1:
63+ syslog('error', 'RRset of TXT records for %s has %d v=DMARC1 entries; testing them all',
64+ dmarc_domain, len(dmarc))
65+ for entry in dmarcs:
66+ if re.search(r'\bp=reject\b', entry, re.IGNORECASE):
67+ syslog('info', 'DMARC lookup for %s (%s) found p=reject in %s = %s',
68+ email, dmarc_domain, name, entry)
69+ return True
70+
71 return False
72
73

Subscribers

People subscribed via source and target branches

to all changes: