Use extended results processing for best_guess.
This commit is contained in:
@@ -1,6 +1,9 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# A simple milter that has grown quite a bit.
|
# A simple milter that has grown quite a bit.
|
||||||
# $Log$
|
# $Log$
|
||||||
|
# Revision 1.16 2005/07/14 03:23:33 customdesigned
|
||||||
|
# Make SES package optional. Initial honeypot support.
|
||||||
|
#
|
||||||
# Revision 1.15 2005/07/06 04:05:40 customdesigned
|
# Revision 1.15 2005/07/06 04:05:40 customdesigned
|
||||||
# Initial SES integration.
|
# Initial SES integration.
|
||||||
#
|
#
|
||||||
@@ -690,6 +693,7 @@ class bmsMilter(Milter.Milter):
|
|||||||
q.set_default_explanation(
|
q.set_default_explanation(
|
||||||
'SPF fail: see http://spf.pobox.com/why.html?sender=%s&ip=%s' % (q.s,q.i))
|
'SPF fail: see http://spf.pobox.com/why.html?sender=%s&ip=%s' % (q.s,q.i))
|
||||||
res,code,txt = q.check()
|
res,code,txt = q.check()
|
||||||
|
ores = res # original result
|
||||||
if res in ('none', 'softfail'):
|
if res in ('none', 'softfail'):
|
||||||
if self.mailfrom != '<>':
|
if self.mailfrom != '<>':
|
||||||
# check hello name via spf
|
# check hello name via spf
|
||||||
@@ -712,12 +716,17 @@ class bmsMilter(Milter.Milter):
|
|||||||
#self.log('SPF: no record published, guessing')
|
#self.log('SPF: no record published, guessing')
|
||||||
q.set_default_explanation(
|
q.set_default_explanation(
|
||||||
'SPF guess: see http://spf.pobox.com/why.html')
|
'SPF guess: see http://spf.pobox.com/why.html')
|
||||||
|
q.strict = False
|
||||||
# best_guess should not result in fail
|
# best_guess should not result in fail
|
||||||
if self.missing_ptr:
|
if self.missing_ptr:
|
||||||
# ignore dynamic PTR for best guess
|
# ignore dynamic PTR for best guess
|
||||||
res,code,txt = q.best_guess('v=spf1 a/24 mx/24')
|
res,code,txt = q.best_guess('v=spf1 a/24 mx/24')
|
||||||
else:
|
else:
|
||||||
res,code,txt = q.best_guess()
|
res,code,txt = q.best_guess()
|
||||||
|
ores = res # original result
|
||||||
|
if q.perm_error:
|
||||||
|
res,code,txt = q.perm_error.ext # extended result
|
||||||
|
txt = 'EXT: ' + txt
|
||||||
receiver += ': guessing'
|
receiver += ': guessing'
|
||||||
if self.missing_ptr and res in ('neutral', 'none') and hres != 'pass':
|
if self.missing_ptr and res in ('neutral', 'none') and hres != 'pass':
|
||||||
if spf_reject_noptr:
|
if spf_reject_noptr:
|
||||||
@@ -765,16 +774,16 @@ class bmsMilter(Milter.Milter):
|
|||||||
'servers for %s should accomplish this.' % q.o
|
'servers for %s should accomplish this.' % q.o
|
||||||
)
|
)
|
||||||
return Milter.REJECT
|
return Milter.REJECT
|
||||||
|
if res == 'unknown':
|
||||||
|
self.log('REJECT: SPF %s %i %s' % (res,code,txt))
|
||||||
|
# latest SPF draft recommends 5.5.2 instead of 5.7.1
|
||||||
|
self.setreply(str(code),'5.5.2',txt)
|
||||||
|
return Milter.REJECT
|
||||||
if res == 'error':
|
if res == 'error':
|
||||||
if code >= 500:
|
|
||||||
self.log('REJECT: SPF %s %i %s' % (res,code,txt))
|
|
||||||
# latest SPF draft recommends 5.5.2 instead of 5.7.1
|
|
||||||
self.setreply(str(code),'5.5.2',txt)
|
|
||||||
return Milter.REJECT
|
|
||||||
self.log('TEMPFAIL: SPF %s %i %s' % (res,code,txt))
|
self.log('TEMPFAIL: SPF %s %i %s' % (res,code,txt))
|
||||||
self.setreply(str(code),'4.3.0',txt)
|
self.setreply(str(code),'4.3.0',txt)
|
||||||
return Milter.TEMPFAIL
|
return Milter.TEMPFAIL
|
||||||
self.add_header('Received-SPF',q.get_header(res,receiver))
|
self.add_header('Received-SPF',q.get_header(ores,receiver))
|
||||||
return Milter.CONTINUE
|
return Milter.CONTINUE
|
||||||
|
|
||||||
# hide_path causes a copy of the message to be saved - until we
|
# hide_path causes a copy of the message to be saved - until we
|
||||||
|
|||||||
@@ -47,6 +47,12 @@ 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.13 2005/07/15 20:01:22 customdesigned
|
||||||
|
# Allow extended results for MX limit
|
||||||
|
#
|
||||||
|
# Revision 1.12 2005/07/15 19:12:09 customdesigned
|
||||||
|
# Official IANA SPF record (type 99) support.
|
||||||
|
#
|
||||||
# Revision 1.11 2005/07/15 18:03:02 customdesigned
|
# Revision 1.11 2005/07/15 18:03:02 customdesigned
|
||||||
# Fix unknown Received-SPF header broken by result changes
|
# Fix unknown Received-SPF header broken by result changes
|
||||||
#
|
#
|
||||||
@@ -193,6 +199,10 @@ import struct # for pack() and unpack()
|
|||||||
import time # for time()
|
import time # for time()
|
||||||
|
|
||||||
import DNS # http://pydns.sourceforge.net
|
import DNS # http://pydns.sourceforge.net
|
||||||
|
# patch in type99
|
||||||
|
DNS.Type.SPF = 99
|
||||||
|
DNS.Type.typemap[99] = 'SPF'
|
||||||
|
DNS.Lib.RRunpacker.getSPFdata = DNS.Lib.RRunpacker.getTXTdata
|
||||||
|
|
||||||
# 32-bit IPv4 address mask
|
# 32-bit IPv4 address mask
|
||||||
MASK = 0xFFFFFFFFL
|
MASK = 0xFFFFFFFFL
|
||||||
@@ -654,12 +664,12 @@ 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.
|
||||||
"""
|
"""
|
||||||
|
a = [t for t in self.dns_99(domain) if t.startswith('v=spf1')]
|
||||||
|
if len(a) == 1:
|
||||||
|
return a[0]
|
||||||
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 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:
|
if DELEGATE:
|
||||||
a = [t
|
a = [t
|
||||||
for t in self.dns_txt(domain+'._spf.'+DELEGATE)
|
for t in self.dns_txt(domain+'._spf.'+DELEGATE)
|
||||||
@@ -675,9 +685,9 @@ class query(object):
|
|||||||
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):
|
def dns_99(self, domainname):
|
||||||
"Get a list of TYPE99 records for a domain name."
|
"Get a list of type SPF=99 records for a domain name."
|
||||||
if domainname:
|
if domainname:
|
||||||
return [''.join(a) for a in self.dns(domainname, 'TYPE99')]
|
return [''.join(a) for a in self.dns(domainname, 'SPF')]
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def dns_mx(self, domainname):
|
def dns_mx(self, domainname):
|
||||||
@@ -726,20 +736,29 @@ class query(object):
|
|||||||
ptrcount = 0
|
ptrcount = 0
|
||||||
req = DNS.DnsRequest(name, qtype=qtype)
|
req = DNS.DnsRequest(name, qtype=qtype)
|
||||||
resp = req.req()
|
resp = req.req()
|
||||||
|
#resp.show()
|
||||||
for a in resp.answers:
|
for a in resp.answers:
|
||||||
if a['typename'] == 'MX':
|
if a['typename'] == 'MX':
|
||||||
mxcount = mxcount + 1
|
mxcount = mxcount + 1
|
||||||
if mxcount > MAX_MX:
|
if mxcount > MAX_MX:
|
||||||
raise PermError('Too many MX lookups')
|
print mxcount,self.strict,self.perm_error
|
||||||
if a['typename'] == 'PTR':
|
try:
|
||||||
ptrcount = ptrcount + 1
|
if self.strict or not self.perm_error:
|
||||||
if ptrcount > MAX_PTR:
|
raise PermError('Too many MX lookups')
|
||||||
raise PermError('Too many PTR lookups')
|
except PermError,x:
|
||||||
# key k: ('wayforward.net', 'A'), value v
|
if self.strict or mxcount > MAX_MX*4:
|
||||||
k, v = (a['name'], a['typename']), a['data']
|
raise x
|
||||||
if k == (name, 'CNAME'):
|
self.perm_error = x
|
||||||
cname = v
|
print "lax"
|
||||||
self.cache.setdefault(k, []).append(v)
|
if a['typename'] == 'PTR':
|
||||||
|
ptrcount = ptrcount + 1
|
||||||
|
if ptrcount > MAX_PTR:
|
||||||
|
raise PermError('Too many PTR lookups')
|
||||||
|
# key k: ('wayforward.net', 'A'), value v
|
||||||
|
k, v = (a['name'], a['typename']), a['data']
|
||||||
|
if k == (name, 'CNAME'):
|
||||||
|
cname = v
|
||||||
|
self.cache.setdefault(k, []).append(v)
|
||||||
result = self.cache.get( (name, qtype), [])
|
result = self.cache.get( (name, qtype), [])
|
||||||
if not result and cname:
|
if not result and cname:
|
||||||
result = self.dns(cname, qtype)
|
result = self.dns(cname, qtype)
|
||||||
@@ -825,8 +844,8 @@ 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')
|
>>> parse_mechanism('A:foo:bar.com/16', 'foo.com')
|
||||||
('a', 'bar.com', 16)
|
('a', 'foo:bar.com', 16)
|
||||||
|
|
||||||
>>> parse_mechanism('-exists:%{i}.%{s1}.100/86400.rate.%{d}','foo.com')
|
>>> parse_mechanism('-exists:%{i}.%{s1}.100/86400.rate.%{d}','foo.com')
|
||||||
('-exists', '%{i}.%{s1}.100/86400.rate.%{d}', 32)
|
('-exists', '%{i}.%{s1}.100/86400.rate.%{d}', 32)
|
||||||
@@ -1025,7 +1044,9 @@ if __name__ == '__main__':
|
|||||||
receiver=socket.gethostname())
|
receiver=socket.gethostname())
|
||||||
elif len(sys.argv) == 5:
|
elif len(sys.argv) == 5:
|
||||||
i, s, h = sys.argv[2:]
|
i, s, h = sys.argv[2:]
|
||||||
q = query(i=i, s=s, h=h, receiver=socket.gethostname())
|
q = query(i=i, s=s, h=h, receiver=socket.gethostname(),
|
||||||
|
strict=False)
|
||||||
print q.check(sys.argv[1])
|
print q.check(sys.argv[1])
|
||||||
|
if q.perm_error: print q.perm_error.ext
|
||||||
else:
|
else:
|
||||||
print USAGE
|
print USAGE
|
||||||
|
|||||||
Reference in New Issue
Block a user