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