Release 0.7.2

This commit is contained in:
Stuart Gathman
2005-05-31 18:10:47 +00:00
parent 16dea6e187
commit 20fb6efab0
11 changed files with 499 additions and 76 deletions
+74 -38
View File
@@ -45,6 +45,25 @@ For news, bugfixes, etc. visit the home page for this implementation at
# Terrence is not responding to email.
#
# $Log$
# Revision 1.21 2004/11/20 16:37:03 stuart
# Handle multi-segment TXT records.
#
# Revision 1.20 2004/11/19 06:10:30 stuart
# Use PermError exception instead of reporting unknown.
#
# Revision 1.19 2004/11/09 23:00:18 stuart
# Limit recursion and DNS lookups separately.
#
#
# Revision 1.17 2004/09/10 18:08:26 stuart
# Return unknown for null mechanism
#
# Revision 1.16 2004/09/04 23:27:06 stuart
# More mechanism aliases.
#
# Revision 1.15 2004/08/30 21:19:05 stuart
# Return unknown for invalid ip syntax in mechanism
#
# Revision 1.14 2004/08/23 02:28:24 stuart
# Remove Perl usage message.
#
@@ -120,7 +139,7 @@ import xml.sax
# Date: 2004-02-25
#
# A complete reverse translation (SPF -> CID) might be impossible, since
# there are no way to handle:
# there are no ways to handle:
# - PTR and EXISTS mechanism
# - MX mechanism with an different domain as argument
# - macros
@@ -284,6 +303,16 @@ except NameError:
# standard default SPF record
DEFAULT_SPF = 'v=spf1 a/24 mx/24 ptr'
# maximum DNS lookups allowed
MAX_LOOKUP = 50
MAX_RECURSION = 20
class TempError(Exception):
"Temporary SPF error"
class PermError(Exception):
"Permanent SPF error"
def check(i, s, h,local=None):
"""Test an incoming MAIL FROM:<s>, from a client with ip address i.
h is the HELO/EHLO domain name.
@@ -329,6 +358,7 @@ class query(object):
self.cache = {}
self.exps = dict(EXPLANATIONS)
self.local = local # local policy
self.lookups = 0
def set_default_explanation(self,exp):
exps = self.exps
@@ -357,27 +387,32 @@ class query(object):
return ('pass', 250, 'local connections always pass')
try:
self.lookups = 0
if not spf:
spf = self.dns_spf(self.d)
if self.local and spf:
spf += ' ' + self.local
return self.check1(spf, self.d, 0)
except DNS.DNSError:
return ('error', 450, 'SPF DNS Error')
except DNS.DNSError,x:
return ('error', 450, 'SPF DNS Error: ' + str(x))
except TempError,x:
return ('error', 450, 'SPF Temporary Error: ' + str(x))
except PermError,x:
return ('error', 550, 'SPF Permanent Error: ' + str(x))
def check1(self, spf, domain, recursion):
# spf rfc: 3.7 Processing Limits
#
if recursion > 20:
self.prob = 'Mechanisms used too many DNS lookups'
if recursion > MAX_RECURSION:
self.prob = 'Too many levels of recursion'
return ('unknown', 250, 'SPF recursion limit exceeded')
try:
tmp, self.d = self.d, domain
return self.check0(spf, recursion)
return self.check0(spf,recursion)
finally:
self.d = tmp
def check0(self, spf, recursion):
def check0(self, spf,recursion):
"""Test this query information against SPF text.
Returns (result, mta-status-code, explanation) where
@@ -425,35 +460,30 @@ class query(object):
m, arg, cidrlength = parse_mechanism(mech, self.d)
# map '?' '+' or '-' to 'unknown' 'pass' or 'fail'
result = RESULTS.get(m[0])
if result:
if m:
result = RESULTS.get(m[0])
if result:
# eat '?' '+' or '-'
m = m[1:]
else:
else:
# default pass
result = 'pass'
if m in ['a', 'mx', 'ptr', 'exists', 'include']:
if m in ['a', 'mx', 'ptr', 'prt', 'exists', 'include']:
arg = self.expand(arg)
if m == 'include':
if arg != self.d:
res,code,txt = self.check1(self.dns_spf(arg),
arg, recursion + 1)
if res == 'pass':
break
if res in ('fail','neutral','softfail'):
continue
if res == 'none':
self.prob = \
'Could not find a valid SPF record'
res = 'unknown'
return res,code,txt
else:
self.prob = 'Required option is missing'
self.mech.append(mech)
return ('unknown', 250, 'missing SPF option')
if arg != self.d:
res,code,txt = self.check1(self.dns_spf(arg),
arg, recursion + 1)
if res == 'pass':
break
if res == 'none':
raise PermError(
'No valid SPF record for included domain')
continue
else:
raise PermError('include mechanism missing domain')
elif m == 'all':
break
@@ -472,9 +502,15 @@ class query(object):
break
elif m in ('ip4', 'ipv4', 'ip') and arg != self.d:
try:
if cidrmatch(self.i, [arg], cidrlength):
break
elif m == 'ip6':
break
except socket.error:
self.mech.append(mech)
self.prob = 'Bad mechanism syntax found'
return ('unknown',250,'SPF mechanism syntax error')
elif m in ('ip6', 'ipv6'):
# Until we support IPV6, we should never
# get an IPv6 connection. So this mech
# will never match.
@@ -486,17 +522,14 @@ class query(object):
break
else:
# unknown mechanisms cause immediate unknown
# abort results
self.mech.append(mech)
self.prob = 'Unknown mechanism found'
return ('unknown',250,'unknown SPF mechanism')
# unknown mechanisms cause immediate unknown
# abort results
raise PermError('Unknown mechanism found: ' + mech)
else:
# no matches
if redirect:
return self.check1(self.dns_spf(redirect),
redirect, recursion+1)
redirect, recursion + 1)
else:
result = default
@@ -630,7 +663,7 @@ class query(object):
def dns_txt(self, domainname):
"Get a list of TXT records for a domain name."
if domainname:
return [t for a in self.dns(domainname, 'TXT') for t in a]
return [''.join(a) for a in self.dns(domainname, 'TXT')]
return []
def dns_mx(self, domainname):
@@ -672,6 +705,9 @@ class query(object):
pre: qtype in ['A', 'AAAA', 'MX', 'PTR', 'TXT', 'SPF']
post: isinstance(__return__, types.ListType)
"""
self.lookups += 1
if self.lookups > MAX_LOOKUP:
raise PermError('Too many DNS lookups')
result = self.cache.get( (name, qtype) )
cname = None
if not result: