SPF updates from pyspf.

This commit is contained in:
Stuart Gathman
2006-10-04 02:15:57 +00:00
parent 33aeefa19f
commit b92154934b
2 changed files with 124 additions and 14 deletions
+3
View File
@@ -1,3 +1,6 @@
When training with spam, REJECT after data so that mistakenly blacklisted
senders at least get an error.
Reporting explanation for failure should show source of sender Reporting explanation for failure should show source of sender
provided explanation. provided explanation.
+121 -14
View File
@@ -47,6 +47,19 @@ 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.100 2006/10/04 02:14:04 customdesigned
# Remove incomplete saving of result. Was messing up bmsmilter. Would
# be useful if done consistently - and disabled when passing spf= to check().
#
# Revision 1.99 2006/10/03 21:00:26 customdesigned
# Correct fat fingered merge error.
#
# Revision 1.98 2006/10/03 17:35:45 customdesigned
# Provide python inet_ntop and inet_pton when not socket.has_ipv6
#
# Revision 1.97 2006/10/02 17:10:13 customdesigned
# Test and fix for uppercase macros.
#
# Revision 1.96 2006/10/01 01:27:54 customdesigned # Revision 1.96 2006/10/01 01:27:54 customdesigned
# Switch to pymilter lax processing convention: # Switch to pymilter lax processing convention:
# Always return strict result, extended result in q.perm_error.ext # Always return strict result, extended result in q.perm_error.ext
@@ -165,6 +178,7 @@ import re
import socket # for inet_ntoa() and inet_aton() import socket # for inet_ntoa() and inet_aton()
import struct # for pack() and unpack() import struct # for pack() and unpack()
import time # for time() import time # for time()
import urllib # for quote()
import DNS # http://pydns.sourceforge.net import DNS # http://pydns.sourceforge.net
if not hasattr(DNS.Type, 'SPF'): if not hasattr(DNS.Type, 'SPF'):
@@ -382,7 +396,7 @@ class query(object):
self.l, self.o = split_email(s, h) self.l, self.o = split_email(s, h)
self.t = str(int(time.time())) self.t = str(int(time.time()))
self.d = self.o self.d = self.o
self.p = None self.p = None # lazy evaluation
if receiver: if receiver:
self.r = receiver self.r = receiver
else: else:
@@ -397,7 +411,8 @@ class query(object):
self.lookups = 0 self.lookups = 0
# strict can be False, True, or 2 (numeric) for harsh # strict can be False, True, or 2 (numeric) for harsh
self.strict = strict self.strict = strict
self.set_ip(i) if i:
self.set_ip(i)
def set_ip(self, i): def set_ip(self, i):
"Set connect ip, and ip6 or ip4 mode." "Set connect ip, and ip6 or ip4 mode."
@@ -405,8 +420,7 @@ class query(object):
self.ip = addr2bin(i) self.ip = addr2bin(i)
ip6 = False ip6 = False
else: else:
assert socket.has_ipv6,"No IPv6 python support" self.ip = bin2long6(inet_pton(i))
self.ip = bin2long6(socket.inet_pton(socket.AF_INET6, i))
if (self.ip >> 32) == 0xFFFF: # IP4 mapped address if (self.ip >> 32) == 0xFFFF: # IP4 mapped address
self.ip = self.ip & 0xFFFFFFFFL self.ip = self.ip & 0xFFFFFFFFL
ip6 = False ip6 = False
@@ -414,8 +428,8 @@ class query(object):
ip6 = True ip6 = True
# NOTE: self.A is not lowercase, so isn't a macro. See query.expand() # NOTE: self.A is not lowercase, so isn't a macro. See query.expand()
if ip6: if ip6:
self.c = socket.inet_ntop(socket.AF_INET6, self.c = inet_ntop(
struct.pack("!QQ", self.ip>>64, self.ip&0xFFFFFFFFFFFFFFFFL)) struct.pack("!QQ", self.ip>>64, self.ip&0xFFFFFFFFFFFFFFFFL))
self.i = '.'.join(list('%032X'%self.ip)) self.i = '.'.join(list('%032X'%self.ip))
self.A = 'AAAA' self.A = 'AAAA'
self.v = 'ip6' self.v = 'ip6'
@@ -529,7 +543,6 @@ class query(object):
# will continue processing. However, the exception # will continue processing. However, the exception
# that strict processing would raise is saved here # that strict processing would raise is saved here
self.perm_error = None self.perm_error = None
self.result = None
try: try:
self.lookups = 0 self.lookups = 0
@@ -539,7 +552,6 @@ class query(object):
spf = insert_libspf_local_policy( spf = insert_libspf_local_policy(
spf, self.libspf_local) spf, self.libspf_local)
rc = self.check1(spf, self.d, 0) rc = self.check1(spf, self.d, 0)
self.result = rc[0]
if self.perm_error: if self.perm_error:
# lax processing encountered a permerror, but continued # lax processing encountered a permerror, but continued
self.perm_error.ext = rc self.perm_error.ext = rc
@@ -840,7 +852,7 @@ class query(object):
elif m == 'ip6': elif m == 'ip6':
if self.v == 'ip6': # match own connection type only if self.v == 'ip6': # match own connection type only
try: try:
arg = socket.inet_pton(socket.AF_INET6,arg) arg = inet_pton(arg)
if self.cidrmatch([arg], cidrlength): break if self.cidrmatch([arg], cidrlength): break
except socket.error: except socket.error:
raise PermError('syntax error', mech) raise PermError('syntax error', mech)
@@ -1006,8 +1018,10 @@ class query(object):
if expansion: if expansion:
if expansion == self: if expansion == self:
raise PermError('Unknown Macro Encountered', macro) raise PermError('Unknown Macro Encountered', macro)
result += expand_one(expansion, macro[3:-1], e = expand_one(expansion, macro[3:-1], JOINERS.get(letter))
JOINERS.get(letter)) if letter != macro[2]:
e = urllib.quote(e)
result += e
end = i.end() end = i.end()
result += str[end:] result += str[end:]
@@ -1357,10 +1371,103 @@ def addr2bin(str):
""" """
return struct.unpack("!L", socket.inet_aton(str))[0] return struct.unpack("!L", socket.inet_aton(str))[0]
def bin2long6(str):
h, l = struct.unpack("!QQ", str)
return h << 64 | l
if socket.has_ipv6: if socket.has_ipv6:
def bin2long6(str): def inet_ntop(s):
h, l = struct.unpack("!QQ", str) return socket.inet_ntop(socket.AF_INET6,s)
return h << 64 | l def inet_pton(s):
return socket.inet_pton(socket.AF_INET6,s)
else:
def inet_ntop(s):
"""Convert ip6 address to standard hex notation.
Examples:
>>> inet_ntop(struct.pack("!HHHHHHHH",0,0,0,0,0,0xFFFF,0x0102,0x0304))
'::FFFF:1.2.3.4'
>>> inet_ntop(struct.pack("!HHHHHHHH",0x1234,0x5678,0,0,0,0,0x0102,0x0304))
'1234:5678::102:304'
>>> inet_ntop(struct.pack("!HHHHHHHH",0,0,0,0x1234,0x5678,0,0x0102,0x0304))
'::1234:5678:0:102:304'
>>> inet_ntop(struct.pack("!HHHHHHHH",0x1234,0x5678,0,0x0102,0x0304,0,0,0))
'1234:5678:0:102:304::'
>>> inet_ntop(struct.pack("!HHHHHHHH",0,0,0,0,0,0,0,0))
'::'
"""
# convert to 8 words
a = struct.unpack("!HHHHHHHH",s)
n = (0,0,0,0,0,0,0,0) # null ip6
if a == n: return '::'
# check for ip4 mapped
if a[:5] == (0,0,0,0,0) and a[5] in (0,0xFFFF):
ip4 = '.'.join([str(i) for i in struct.unpack("!HHHHHHBBBB",s)[6:]])
if a[5]:
return "::FFFF:" + ip4
return "::" + ip4
# find index of longest sequence of 0
for l in (7,6,5,4,3,2,1):
e = n[:l]
for i in range(9-l):
if a[i:i+l] == e:
if i == 0:
return ':'+':%x'*(8-l) % a[l:]
if i == 8 - l:
return '%x:'*(8-l) % a[:-l] + ':'
return '%x:'*i % a[:i] + ':%x'*(8-l-i) % a[i+l:]
return "%x:%x:%x:%x:%x:%x:%x:%x" % a
def inet_pton(p):
"""Convert ip6 standard hex notation to ip6 address.
Examples:
>>> struct.unpack('!HHHHHHHH',inet_pton('::'))
(0, 0, 0, 0, 0, 0, 0, 0)
>>> struct.unpack('!HHHHHHHH',inet_pton('::1234'))
(0, 0, 0, 0, 0, 0, 0, 4660)
>>> struct.unpack('!HHHHHHHH',inet_pton('1234::'))
(4660, 0, 0, 0, 0, 0, 0, 0)
>>> struct.unpack('!HHHHHHHH',inet_pton('1234::5678'))
(4660, 0, 0, 0, 0, 0, 0, 22136)
>>> struct.unpack('!HHHHHHHH',inet_pton('::FFFF:1.2.3.4'))
(0, 0, 0, 0, 0, 65535, 258, 772)
>>> struct.unpack('!HHHHHHHH',inet_pton('1.2.3.4'))
(0, 0, 0, 0, 0, 65535, 258, 772)
>>> try: inet_pton('::1.2.3.4.5')
... except ValueError,x: print x
::1.2.3.4.5
"""
if p == '::':
return '\0'*16
s = p
m = RE_IP4.search(s)
try:
if m:
pos = m.start()
ip4 = [int(i) for i in s[pos:].split('.')]
if not pos:
return struct.pack('!QLBBBB',0,65535,*ip4)
s = s[:pos]+'%x%02x:%x%02x'%tuple(ip4)
a = s.split('::')
if len(a) == 2:
l,r = a
if not l:
r = r.split(':')
return struct.pack('!HHHHHHHH',
*[0]*(8-len(r)) + [int(s,16) for s in r])
if not r:
l = l.split(':')
return struct.pack('!HHHHHHHH',
*[int(s,16) for s in l] + [0]*(8-len(l)))
l = l.split(':')
r = r.split(':')
return struct.pack('!HHHHHHHH',
*[int(s,16) for s in l] + [0]*(8-len(l)-len(r))
+ [int(s,16) for s in r])
if len(a) == 1:
return struct.pack('!HHHHHHHH',
*[int(s,16) for s in a[0].split(':')])
except ValueError: pass
raise ValueError,p
def expand_one(expansion, str, joiner): def expand_one(expansion, str, joiner):
if not str: if not str: