Use extended results processing for best_guess.

This commit is contained in:
Stuart Gathman
2005-07-15 20:25:36 +00:00
parent ef413913d0
commit e1f4744a22
2 changed files with 57 additions and 27 deletions
+12 -3
View File
@@ -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 == 'error': if res == 'unknown':
if code >= 500:
self.log('REJECT: SPF %s %i %s' % (res,code,txt)) self.log('REJECT: SPF %s %i %s' % (res,code,txt))
# latest SPF draft recommends 5.5.2 instead of 5.7.1 # latest SPF draft recommends 5.5.2 instead of 5.7.1
self.setreply(str(code),'5.5.2',txt) self.setreply(str(code),'5.5.2',txt)
return Milter.REJECT return Milter.REJECT
if res == 'error':
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
+29 -8
View File
@@ -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,11 +736,20 @@ 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:
print mxcount,self.strict,self.perm_error
try:
if self.strict or not self.perm_error:
raise PermError('Too many MX lookups') raise PermError('Too many MX lookups')
except PermError,x:
if self.strict or mxcount > MAX_MX*4:
raise x
self.perm_error = x
print "lax"
if a['typename'] == 'PTR': if a['typename'] == 'PTR':
ptrcount = ptrcount + 1 ptrcount = ptrcount + 1
if ptrcount > MAX_PTR: if ptrcount > MAX_PTR:
@@ -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