From b92154934bd009657ffe267a20376616a9026683 Mon Sep 17 00:00:00 2001 From: Stuart Gathman <stuart@gathman.org> Date: Wed, 4 Oct 2006 02:15:57 +0000 Subject: [PATCH] SPF updates from pyspf. --- TODO | 3 ++ spf.py | 135 +++++++++++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 124 insertions(+), 14 deletions(-) diff --git a/TODO b/TODO index 51dd3de..d2cca47 100644 --- a/TODO +++ b/TODO @@ -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 provided explanation. diff --git a/spf.py b/spf.py index 9cacfcc..26cd662 100755 --- a/spf.py +++ b/spf.py @@ -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>. # # $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 # Switch to pymilter lax processing convention: # 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 struct # for pack() and unpack() import time # for time() +import urllib # for quote() import DNS # http://pydns.sourceforge.net if not hasattr(DNS.Type, 'SPF'): @@ -382,7 +396,7 @@ class query(object): self.l, self.o = split_email(s, h) self.t = str(int(time.time())) self.d = self.o - self.p = None + self.p = None # lazy evaluation if receiver: self.r = receiver else: @@ -397,7 +411,8 @@ class query(object): self.lookups = 0 # strict can be False, True, or 2 (numeric) for harsh self.strict = strict - self.set_ip(i) + if i: + self.set_ip(i) def set_ip(self, i): "Set connect ip, and ip6 or ip4 mode." @@ -405,8 +420,7 @@ class query(object): self.ip = addr2bin(i) ip6 = False else: - assert socket.has_ipv6,"No IPv6 python support" - self.ip = bin2long6(socket.inet_pton(socket.AF_INET6, i)) + self.ip = bin2long6(inet_pton(i)) if (self.ip >> 32) == 0xFFFF: # IP4 mapped address self.ip = self.ip & 0xFFFFFFFFL ip6 = False @@ -414,8 +428,8 @@ class query(object): ip6 = True # NOTE: self.A is not lowercase, so isn't a macro. See query.expand() if ip6: - self.c = socket.inet_ntop(socket.AF_INET6, - struct.pack("!QQ", self.ip>>64, self.ip&0xFFFFFFFFFFFFFFFFL)) + self.c = inet_ntop( + struct.pack("!QQ", self.ip>>64, self.ip&0xFFFFFFFFFFFFFFFFL)) self.i = '.'.join(list('%032X'%self.ip)) self.A = 'AAAA' self.v = 'ip6' @@ -529,7 +543,6 @@ class query(object): # will continue processing. However, the exception # that strict processing would raise is saved here self.perm_error = None - self.result = None try: self.lookups = 0 @@ -539,7 +552,6 @@ class query(object): spf = insert_libspf_local_policy( spf, self.libspf_local) rc = self.check1(spf, self.d, 0) - self.result = rc[0] if self.perm_error: # lax processing encountered a permerror, but continued self.perm_error.ext = rc @@ -840,7 +852,7 @@ class query(object): elif m == 'ip6': if self.v == 'ip6': # match own connection type only try: - arg = socket.inet_pton(socket.AF_INET6,arg) + arg = inet_pton(arg) if self.cidrmatch([arg], cidrlength): break except socket.error: raise PermError('syntax error', mech) @@ -1006,8 +1018,10 @@ class query(object): if expansion: if expansion == self: raise PermError('Unknown Macro Encountered', macro) - result += expand_one(expansion, macro[3:-1], - JOINERS.get(letter)) + e = expand_one(expansion, macro[3:-1], JOINERS.get(letter)) + if letter != macro[2]: + e = urllib.quote(e) + result += e end = i.end() result += str[end:] @@ -1357,10 +1371,103 @@ def addr2bin(str): """ 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: - def bin2long6(str): - h, l = struct.unpack("!QQ", str) - return h << 64 | l + def inet_ntop(s): + return socket.inet_ntop(socket.AF_INET6,s) + 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): if not str: -- GitLab