Allow extended processing for MX count.

This commit is contained in:
Stuart Gathman
2005-07-15 20:00:35 +00:00
parent 8ad4b16156
commit ef413913d0
+52 -23
View File
@@ -47,6 +47,23 @@ For news, bugfixes, etc. visit the home page for this implementation at
# Terrence is not responding to email. # Terrence is not responding to email.
# #
# $Log$ # $Log$
# Revision 1.11 2005/07/15 18:03:02 customdesigned
# Fix unknown Received-SPF header broken by result changes
#
# Revision 1.10 2005/07/15 16:17:05 customdesigned
# Start type99 support.
# Make Scott's "/" support in parse_mechanism more elegant as requested.
# Add test case for "/" support.
#
# Revision 1.9 2005/07/15 03:33:14 kitterma
# Fix for bug 1238403 - Crash if non-CIDR / present. Also added
# validation check for valid IPv4 CIDR range.
#
# Revision 1.8 2005/07/14 04:18:01 customdesigned
# Bring explanations and Received-SPF header into line with
# the unknown=PermErr and error=TempErr convention.
# Hope my case-sensitive mech fix doesn't clash with Scotts.
#
# Revision 1.7 2005/07/12 21:43:56 kitterma # Revision 1.7 2005/07/12 21:43:56 kitterma
# Added processing to clarify some cases of unknown # Added processing to clarify some cases of unknown
# qualifier errors (to distinguish between unknown qualifier and # qualifier errors (to distinguish between unknown qualifier and
@@ -189,6 +206,8 @@ RE_CHAR = re.compile(r'%(%|_|-|(\{[a-zA-Z][0-9]*r?[^\}]*\}))')
# Regular expression to break up a macro expansion # Regular expression to break up a macro expansion
RE_ARGS = re.compile(r'([0-9]*)(r?)([^0-9a-zA-Z]*)') RE_ARGS = re.compile(r'([0-9]*)(r?)([^0-9a-zA-Z]*)')
RE_CIDR = re.compile(r'/(1[0-9]*|2[0-9]*|3[0-2]*)$')
# Local parts and senders have their delimiters replaced with '.' during # Local parts and senders have their delimiters replaced with '.' during
# macro expansion # macro expansion
# #
@@ -196,11 +215,12 @@ JOINERS = {'l': '.', 's': '.'}
RESULTS = {'+': 'pass', '-': 'fail', '?': 'neutral', '~': 'softfail', RESULTS = {'+': 'pass', '-': 'fail', '?': 'neutral', '~': 'softfail',
'pass': 'pass', 'fail': 'fail', 'unknown': 'unknown', 'pass': 'pass', 'fail': 'fail', 'unknown': 'unknown',
'neutral': 'neutral', 'softfail': 'softfail', 'error': 'error', 'neutral': 'neutral', 'softfail': 'softfail',
'none': 'none', 'deny': 'fail' } 'none': 'none', 'deny': 'fail' }
EXPLANATIONS = {'pass': 'sender SPF verified', 'fail': 'access denied', EXPLANATIONS = {'pass': 'sender SPF verified', 'fail': 'access denied',
'unknown': 'SPF unknown (PermError)', 'unknown': 'permanent error in processing',
'error': 'temporary error in processing',
'softfail': 'domain in transition', 'softfail': 'domain in transition',
'neutral': 'access neither permitted nor denied', 'neutral': 'access neither permitted nor denied',
'none': '' 'none': ''
@@ -345,11 +365,11 @@ class query(object):
return ('error', 450, 'SPF Temporary Error: ' + str(x)) return ('error', 450, 'SPF Temporary Error: ' + str(x))
except PermError,x: except PermError,x:
self.prob = x.msg self.prob = x.msg
self.mech.append(x.mech) if x.mech:
self.mech.append(x.mech)
# Pre-Lentczner draft treats this as an unknown result # Pre-Lentczner draft treats this as an unknown result
# and equivalent to no SPF record. # and equivalent to no SPF record.
return ('unknown', 550, 'SPF Permanent Error: ' + str(x)) return ('unknown', 550, 'SPF Permanent Error: ' + str(x))
# return ('error', 550, 'SPF Permanent Error: ' + str(x))
def check1(self, spf, domain, recursion): def check1(self, spf, domain, recursion):
# spf rfc: 3.7 Processing Limits # spf rfc: 3.7 Processing Limits
@@ -393,8 +413,7 @@ class query(object):
if len(m) != 2: continue if len(m) != 2: continue
if m[0] == 'exp': if m[0] == 'exp':
exps['fail'] = exps['unknown'] = \ self.set_default_explanation(self.get_explanation(m[1]))
self.get_explanation(m[1])
elif m[0] == 'redirect': elif m[0] == 'redirect':
self.check_lookups() self.check_lookups()
redirect = self.expand(m[1]) redirect = self.expand(m[1])
@@ -502,7 +521,7 @@ class query(object):
# fine tune the error). # fine tune the error).
# eat one character and try again: # eat one character and try again:
m = m[1:] m = m[1:]
if m in ['a', 'mx', 'ptr', 'exists', 'include', 'ip4', 'ip6', 'all']: if m in ('a', 'mx', 'ptr', 'exists', 'include', 'ip4', 'ip6', 'all'):
raise PermError('Unknown qualifier, IETF draft para 4.6.1, found in',mech) raise PermError('Unknown qualifier, IETF draft para 4.6.1, found in',mech)
else: else:
raise PermError('Unknown mechanism found',mech) raise PermError('Unknown mechanism found',mech)
@@ -636,15 +655,18 @@ class query(object):
is found. is found.
""" """
a = [t for t in self.dns_txt(domain) if t.startswith('v=spf1')] a = [t for t in self.dns_txt(domain) if t.startswith('v=spf1')]
if not a:
if DELEGATE:
a = [t
for t in self.dns_txt(domain+'._spf.'+DELEGATE)
if t.startswith('v=spf1')
]
if len(a) == 1: if len(a) == 1:
return a[0] return a[0]
#a = [t for t in self.dns_99(domain) if t.startswith('v=spf1')]
#if len(a) == 1:
# return a[0]
if DELEGATE:
a = [t
for t in self.dns_txt(domain+'._spf.'+DELEGATE)
if t.startswith('v=spf1')
]
if len(a) == 1:
return a[0]
return None return None
def dns_txt(self, domainname): def dns_txt(self, domainname):
@@ -652,6 +674,11 @@ class query(object):
if domainname: if domainname:
return [''.join(a) for a in self.dns(domainname, 'TXT')] return [''.join(a) for a in self.dns(domainname, 'TXT')]
return [] return []
def dns_99(self, domainname):
"Get a list of TYPE99 records for a domain name."
if domainname:
return [''.join(a) for a in self.dns(domainname, 'TYPE99')]
return []
def dns_mx(self, domainname): def dns_mx(self, domainname):
"""Get a list of IP addresses for all MX exchanges for a """Get a list of IP addresses for all MX exchanges for a
@@ -718,7 +745,9 @@ class query(object):
result = self.dns(cname, qtype) result = self.dns(cname, qtype)
return result return result
def get_header(self,res,receiver): def get_header(self,res,receiver=None):
if not receiver:
receiver = self.r
if res in ('pass','fail','softfail'): if res in ('pass','fail','softfail'):
return '%s (%s: %s) client-ip=%s; envelope-from=%s; helo=%s;' % ( return '%s (%s: %s) client-ip=%s; envelope-from=%s; helo=%s;' % (
res,receiver,self.get_header_comment(res),self.i, res,receiver,self.get_header_comment(res),self.i,
@@ -749,10 +778,10 @@ class query(object):
% (self.i,sender) % (self.i,sender)
#"%s does not designate permitted sender hosts" % sender #"%s does not designate permitted sender hosts" % sender
elif res == 'unknown': return \ elif res == 'unknown': return \
"error in processing during lookup of domain of %s: %s" \ "permanent error in processing domain of %s: %s" \
% (sender, self.prob) % (sender, self.prob)
elif res == 'error': return \ elif res == 'error': return \
"error in processing during lookup of %s" % sender "temporary error in processing during lookup of %s" % sender
elif res == 'fail': return \ elif res == 'fail': return \
"domain of %s does not designate %s as permitted sender" \ "domain of %s does not designate %s as permitted sender" \
% (sender,self.i) % (sender,self.i)
@@ -796,19 +825,19 @@ def parse_mechanism(str, d):
>>> parse_mechanism('a/24', 'foo.com') >>> parse_mechanism('a/24', 'foo.com')
('a', 'foo.com', 24) ('a', 'foo.com', 24)
>>> parse_mechanism('a:bar.com/16', 'foo.com')
('a', 'bar.com', 16)
>>> parse_mechanism('A:bar.com/16', 'foo.com') >>> parse_mechanism('A:bar.com/16', 'foo.com')
('a', 'bar.com', 16) ('a', 'bar.com', 16)
>>> parse_mechanism('-exists:%{i}.%{s1}.100/86400.rate.%{d}','foo.com')
('-exists', '%{i}.%{s1}.100/86400.rate.%{d}', 32)
""" """
a = str.split('/') a = RE_CIDR.split(str)
if len(a) == 2: if len(a) == 3:
a, port = a[0], int(a[1]) a, port = a[0], int(a[1])
else: else:
a, port = str, 32 a, port = str, 32
b = a.split(':') b = a.split(':',1)
if len(b) == 2: if len(b) == 2:
return b[0].lower(), b[1], port return b[0].lower(), b[1], port
else: else: