More SPF fixes and tests from pyspf.
This commit is contained in:
@@ -47,6 +47,21 @@ For news, bugfixes, etc. visit the home page for this implementation at
|
|||||||
# Development taken over by Stuart Gathman <stuart@bmsi.com>.
|
# Development taken over by Stuart Gathman <stuart@bmsi.com>.
|
||||||
#
|
#
|
||||||
# $Log$
|
# $Log$
|
||||||
|
# Revision 1.105 2006/10/07 22:06:28 kitterma
|
||||||
|
# Pass strict status to DNSLookup - will be needed for TCP failover.
|
||||||
|
#
|
||||||
|
# Revision 1.104 2006/10/07 21:59:37 customdesigned
|
||||||
|
# long/empty label tests and fix.
|
||||||
|
#
|
||||||
|
# Revision 1.103 2006/10/07 18:16:20 customdesigned
|
||||||
|
# Add tests for and fix RE_TOPLAB.
|
||||||
|
#
|
||||||
|
# Revision 1.102 2006/10/05 13:57:15 customdesigned
|
||||||
|
# Remove isSPF and make missing space after version tag a warning.
|
||||||
|
#
|
||||||
|
# Revision 1.101 2006/10/05 13:39:11 customdesigned
|
||||||
|
# SPF version tag is case insensitive.
|
||||||
|
#
|
||||||
# Revision 1.100 2006/10/04 02:14:04 customdesigned
|
# Revision 1.100 2006/10/04 02:14:04 customdesigned
|
||||||
# Remove incomplete saving of result. Was messing up bmsmilter. Would
|
# Remove incomplete saving of result. Was messing up bmsmilter. Would
|
||||||
# be useful if done consistently - and disabled when passing spf= to check().
|
# be useful if done consistently - and disabled when passing spf= to check().
|
||||||
@@ -187,11 +202,11 @@ if not hasattr(DNS.Type, 'SPF'):
|
|||||||
DNS.Type.typemap[99] = 'SPF'
|
DNS.Type.typemap[99] = 'SPF'
|
||||||
DNS.Lib.RRunpacker.getSPFdata = DNS.Lib.RRunpacker.getTXTdata
|
DNS.Lib.RRunpacker.getSPFdata = DNS.Lib.RRunpacker.getTXTdata
|
||||||
|
|
||||||
def DNSLookup(name, qtype):
|
def DNSLookup(name, qtype, strict=True):
|
||||||
try:
|
try:
|
||||||
req = DNS.DnsRequest(name, qtype=qtype)
|
req = DNS.DnsRequest(name, qtype=qtype)
|
||||||
resp = req.req()
|
resp = req.req()
|
||||||
#resp.show()
|
#resp.show()
|
||||||
# key k: ('wayforward.net', 'A'), value v
|
# key k: ('wayforward.net', 'A'), value v
|
||||||
# FIXME: pydns returns AAAA RR as 16 byte binary string, but
|
# FIXME: pydns returns AAAA RR as 16 byte binary string, but
|
||||||
# A RR as dotted quad. For consistency, this driver should
|
# A RR as dotted quad. For consistency, this driver should
|
||||||
@@ -202,15 +217,14 @@ def DNSLookup(name, qtype):
|
|||||||
except DNS.DNSError, x:
|
except DNS.DNSError, x:
|
||||||
raise TempError, 'DNS ' + str(x)
|
raise TempError, 'DNS ' + str(x)
|
||||||
|
|
||||||
def isSPF(txt):
|
RE_SPF = re.compile(r'^v=spf1$|^v=spf1 ',re.IGNORECASE)
|
||||||
"Return True if txt has SPF record signature."
|
|
||||||
return txt.startswith('v=spf1 ') or txt == 'v=spf1'
|
|
||||||
|
|
||||||
# Regular expression to look for modifiers
|
# Regular expression to look for modifiers
|
||||||
RE_MODIFIER = re.compile(r'^([a-z][a-z0-9_\-\.]*)=', re.IGNORECASE)
|
RE_MODIFIER = re.compile(r'^([a-z][a-z0-9_\-\.]*)=', re.IGNORECASE)
|
||||||
|
|
||||||
# Regular expression to find macro expansions
|
# Regular expression to find macro expansions
|
||||||
RE_CHAR = re.compile(r'%(%|_|-|(\{[^\}]*\}))')
|
PAT_CHAR = r'%(%|_|-|(\{[^\}]*\}))'
|
||||||
|
RE_CHAR = re.compile(PAT_CHAR)
|
||||||
|
|
||||||
# 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]*)')
|
||||||
@@ -222,7 +236,8 @@ PAT_IP4 = r'\.'.join([r'(?:\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])']*4)
|
|||||||
RE_IP4 = re.compile(PAT_IP4+'$')
|
RE_IP4 = re.compile(PAT_IP4+'$')
|
||||||
|
|
||||||
RE_TOPLAB = re.compile(
|
RE_TOPLAB = re.compile(
|
||||||
r'\.[0-9a-z]*[a-z][0-9a-z]*|[0-9a-z]+-[0-9a-z-]*[0-9a-z]$', re.IGNORECASE)
|
r'\.(?:[0-9a-z]*[a-z][0-9a-z]*|[0-9a-z]+-[0-9a-z-]*[0-9a-z])\.?$|%s'
|
||||||
|
% PAT_CHAR, re.IGNORECASE)
|
||||||
|
|
||||||
RE_IP6 = re.compile( '(?:%(hex4)s:){6}%(ls32)s$'
|
RE_IP6 = re.compile( '(?:%(hex4)s:){6}%(ls32)s$'
|
||||||
'|::(?:%(hex4)s:){5}%(ls32)s$'
|
'|::(?:%(hex4)s:){5}%(ls32)s$'
|
||||||
@@ -720,10 +735,10 @@ class query(object):
|
|||||||
|
|
||||||
# validate domain-spec
|
# validate domain-spec
|
||||||
if m in ('a', 'mx', 'ptr', 'exists', 'include'):
|
if m in ('a', 'mx', 'ptr', 'exists', 'include'):
|
||||||
arg = self.expand(arg)
|
|
||||||
# any trailing dot was removed by expand()
|
# any trailing dot was removed by expand()
|
||||||
if RE_TOPLAB.split(arg)[-1]:
|
if RE_TOPLAB.split(arg)[-1]:
|
||||||
raise PermError('Invalid domain found (use FQDN)', arg)
|
raise PermError('Invalid domain found (use FQDN)', arg)
|
||||||
|
arg = self.expand(arg)
|
||||||
if m == 'include':
|
if m == 'include':
|
||||||
if arg == self.d:
|
if arg == self.d:
|
||||||
if mech != 'include':
|
if mech != 'include':
|
||||||
@@ -760,11 +775,12 @@ class query(object):
|
|||||||
spf = spf.split()
|
spf = spf.split()
|
||||||
# Catch case where SPF record has no spaces.
|
# Catch case where SPF record has no spaces.
|
||||||
# Can never happen with conforming dns_spf(), however
|
# Can never happen with conforming dns_spf(), however
|
||||||
# in the future we might want to give permerror
|
# in the future we might want to give warnings
|
||||||
# for common mistakes like IN TXT "v=spf1" "mx" "-all"
|
# for common mistakes like IN TXT "v=spf1" "mx" "-all"
|
||||||
# in relaxed mode.
|
# in relaxed mode.
|
||||||
if spf[0] != 'v=spf1':
|
if spf[0].lower() != 'v=spf1':
|
||||||
raise PermError('Invalid SPF record in', self.d)
|
assert strict > 1
|
||||||
|
raise AmbiguityWarning('Invalid SPF record in', self.d)
|
||||||
spf = spf[1:]
|
spf = spf[1:]
|
||||||
|
|
||||||
# copy of explanations to be modified by exp=
|
# copy of explanations to be modified by exp=
|
||||||
@@ -1037,15 +1053,20 @@ class query(object):
|
|||||||
name. Returns None if not found, or if more than one record
|
name. Returns None if not found, or if more than one record
|
||||||
is found.
|
is found.
|
||||||
"""
|
"""
|
||||||
|
# Per RFC 4.3/1, check for malformed domain. This produces
|
||||||
|
# no results as a special case.
|
||||||
|
for label in domain.split('.'):
|
||||||
|
if not label or len(label) > 63:
|
||||||
|
return None
|
||||||
# for performance, check for most common case of TXT first
|
# for performance, check for most common case of TXT first
|
||||||
a = [t for t in self.dns_txt(domain) if isSPF(t)]
|
a = [t for t in self.dns_txt(domain) if RE_SPF.match(t)]
|
||||||
if len(a) > 1:
|
if len(a) > 1:
|
||||||
raise PermError('Two or more type TXT spf records found.')
|
raise PermError('Two or more type TXT spf records found.')
|
||||||
if len(a) == 1 and self.strict < 2:
|
if len(a) == 1 and self.strict < 2:
|
||||||
return a[0]
|
return a[0]
|
||||||
# check official SPF type first when it becomes more popular
|
# check official SPF type first when it becomes more popular
|
||||||
try:
|
try:
|
||||||
b = [t for t in self.dns_99(domain) if isSPF(t)]
|
b = [t for t in self.dns_99(domain) if RE_SPF.match(t)]
|
||||||
except TempError,x:
|
except TempError,x:
|
||||||
# some braindead DNS servers hang on type 99 query
|
# some braindead DNS servers hang on type 99 query
|
||||||
if self.strict > 1: raise TempError(x)
|
if self.strict > 1: raise TempError(x)
|
||||||
@@ -1064,7 +1085,7 @@ class query(object):
|
|||||||
if DELEGATE: # use local record if neither found
|
if DELEGATE: # use local record if neither found
|
||||||
a = [t
|
a = [t
|
||||||
for t in self.dns_txt(domain+'._spf.'+DELEGATE)
|
for t in self.dns_txt(domain+'._spf.'+DELEGATE)
|
||||||
if isSPF(t)
|
if RE_SPF.match(t)
|
||||||
]
|
]
|
||||||
if len(a) == 1: return a[0]
|
if len(a) == 1: return a[0]
|
||||||
return None
|
return None
|
||||||
@@ -1159,7 +1180,7 @@ class query(object):
|
|||||||
result = self.cache.get( (name, qtype) )
|
result = self.cache.get( (name, qtype) )
|
||||||
cname = None
|
cname = None
|
||||||
if not result:
|
if not result:
|
||||||
for k, v in DNSLookup(name, qtype):
|
for k, v in DNSLookup(name, qtype, self.strict):
|
||||||
if k == (name, 'CNAME'):
|
if k == (name, 'CNAME'):
|
||||||
cname = v
|
cname = v
|
||||||
self.cache.setdefault(k, []).append(v)
|
self.cache.setdefault(k, []).append(v)
|
||||||
|
|||||||
Reference in New Issue
Block a user