Package: release.debian.org
Severity: normal
User: release.debian....@packages.debian.org
Usertags: unblock

Please unblock package pyspf

RC bug fix.

Note: The diff is somewhat cluttered since I chose to upload the fix
in the form of the new upstream release as the RC fix is the only
change from what's in Jessie now.  It does clutter the diff a bit due
to dropping the existing patch that was from upstream, changes due
to the version bump, and upstream test improvements that verify the
correctness of both bug fixes in this upstream release.  In order to
simplfy review, I'm attaching both the full diff from Jessie and a
reduced diff that's limited to the actual code changes (both lines).

unblock pyspf/2.0.11-1
diff -Nru pyspf-2.0.10/CHANGELOG pyspf-2.0.11/CHANGELOG
--- pyspf-2.0.10/CHANGELOG	2014-09-02 15:17:08.000000000 -0400
+++ pyspf-2.0.11/CHANGELOG	2014-12-05 11:12:31.000000000 -0500
@@ -1,4 +1,11 @@
-Version 2.0.10 - September 2. 2014
+Version 2.0.11 - December 5, 2014
+  * Fix another bug in SPF record parsing that caused records with terms
+    separated by multple spaces as invalid, but they are fine per the ABNF
+  * Downcase names in additional answers returned by DNS before adding
+    to cache, since case inconsistency can cause PTR match failures (initial
+    patch thanks to Joni Fieggen) and other problems.
+
+Version 2.0.10 - September 2, 2014
   * Fix bug in SPF record parsing that caused all 'whitespace' characters to
     be considered valid term separators and not just spaces
   * Fixed multiple bugs in temperror processing that would lead to tracebacks
diff -Nru pyspf-2.0.10/debian/changelog pyspf-2.0.11/debian/changelog
--- pyspf-2.0.10/debian/changelog	2014-11-30 11:22:49.000000000 -0500
+++ pyspf-2.0.11/debian/changelog	2014-12-18 18:45:03.000000000 -0500
@@ -1,3 +1,13 @@
+pyspf (2.0.11-1) unstable; urgency=medium
+
+  * New upstream release
+    - Fix problem with incorrect SPF results due to DNS cache case sensitivity
+      problems (Closes: #773491)
+    - Remove debian/patches since the only patch was a cherry pick from
+      upstream that is in the new release
+
+ -- Scott Kitterman <sc...@kitterman.com>  Thu, 18 Dec 2014 17:55:40 -0500
+
 pyspf (2.0.10-2) unstable; urgency=medium
 
   * Backport upstream fix for multiple spaces between SPF record terms
diff -Nru pyspf-2.0.10/debian/patches/fix-multispace.diff pyspf-2.0.11/debian/patches/fix-multispace.diff
--- pyspf-2.0.10/debian/patches/fix-multispace.diff	2014-11-30 11:28:40.000000000 -0500
+++ pyspf-2.0.11/debian/patches/fix-multispace.diff	1969-12-31 19:00:00.000000000 -0500
@@ -1,27 +0,0 @@
-Patch from upstream (will be included in pyspf 2.0.11).
-Index: pyspf-2.0.10/spf.py
-===================================================================
---- pyspf-2.0.10.orig/spf.py	2014-09-02 15:17:08.000000000 -0400
-+++ pyspf-2.0.10/spf.py	2014-11-30 11:25:24.175981174 -0500
-@@ -776,7 +776,11 @@
- 
-         # Split string by space, drop the 'v=spf1'.  Split by all whitespace
-         # casuses things like carriage returns being treated as valid space
--        # separators, so split() is not sufficient.
-+        # separators, so split() is not sufficient.  Just to make it even more
-+        # fun, the relevant piece of the ABNF for term separations is
-+        # *( 1*SP ( directive / modifier ) ), so it's one or more spaces, not
-+        # just one.  The re removes multiple spaces and then the split splits
-+        # on the single remaining space.
-         spf = spf.split(' ')
-         # Catch case where SPF record has no spaces.
-         # Can never happen with conforming dns_spf(), however
-@@ -787,7 +791,7 @@
-             if self.strict > 1:
-                 raise AmbiguityWarning('Invalid SPF record in', self.d)
-             return ('none', 250, EXPLANATIONS['none'])
--        spf = spf[1:]
-+        spf = [mech for mech in spf[1:] if mech]
- 
-         # copy of explanations to be modified by exp=
-         exps = self.exps
diff -Nru pyspf-2.0.10/debian/patches/series pyspf-2.0.11/debian/patches/series
--- pyspf-2.0.10/debian/patches/series	2014-11-30 11:24:03.000000000 -0500
+++ pyspf-2.0.11/debian/patches/series	1969-12-31 19:00:00.000000000 -0500
@@ -1 +0,0 @@
-fix-multispace.diff
diff -Nru pyspf-2.0.10/PKG-INFO pyspf-2.0.11/PKG-INFO
--- pyspf-2.0.10/PKG-INFO	2014-09-02 15:18:04.000000000 -0400
+++ pyspf-2.0.11/PKG-INFO	2014-12-05 12:52:27.000000000 -0500
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: pyspf
-Version: 2.0.10
+Version: 2.0.11
 Summary: SPF (Sender Policy Framework) implemented in Python.
 Home-page: http://pymilter.sourceforge.net/
 Author: Stuart D. Gathman
diff -Nru pyspf-2.0.10/pyspf.spec pyspf-2.0.11/pyspf.spec
--- pyspf-2.0.10/pyspf.spec	2014-09-02 15:17:08.000000000 -0400
+++ pyspf-2.0.11/pyspf.spec	2014-12-05 11:13:27.000000000 -0500
@@ -7,7 +7,7 @@
 %{!?python_sitelib: %define python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")}
 
 Name:           %{pythonbase}-pyspf
-Version:        2.0.10
+Version:        2.0.11
 Release:        1
 Summary:        Python module and programs for SPF (Sender Policy Framework).
 
@@ -56,6 +56,13 @@
 /usr/lib/python2.6/site-packages/pyspf-%{version}-py2.6.egg-info
 
 %changelog
+* Fri Dec  5 2014 Stuart Gathman <stu...@gathman.org> 2.0.11-1
+- Fix another bug in SPF record parsing that caused records with terms
+  separated by multple spaces as invalid, but they are fine per the ABNF
+- Downcase names in additional answers returned by DNS before adding
+  to cache, since case inconsistency can cause PTR match failures (initial
+  patch thanks to Joni Fieggen) and other problems.
+
 * Tue Sep  2 2014 Stuart Gathman <stu...@gathman.org> 2.0.10-1
 - Fix AAAA not flagged as bytes when strict=2 
 - Split mechanisms by space only, not by whitespace
diff -Nru pyspf-2.0.10/python-pyspf.spec pyspf-2.0.11/python-pyspf.spec
--- pyspf-2.0.10/python-pyspf.spec	2014-09-02 15:17:08.000000000 -0400
+++ pyspf-2.0.11/python-pyspf.spec	2014-12-05 11:19:31.000000000 -0500
@@ -1,7 +1,7 @@
 %{!?python_sitelib: %define python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")}
 
 Name:           python-pyspf
-Version:        2.0.10
+Version:        2.0.11
 Release:        1%{?dist}
 Summary:        Python module and programs for SPF (Sender Policy Framework).
 
@@ -55,6 +55,13 @@
 /usr/lib/python2.6/site-packages/pyspf-%{version}-py2.6.egg-info
 
 %changelog
+* Fri Dec  5 2014 Stuart Gathman <stu...@gathman.org> 2.0.11-1
+- Fix another bug in SPF record parsing that caused records with terms
+  separated by multple spaces as invalid, but they are fine per the ABNF
+- Downcase names in additional answers returned by DNS before adding
+  to cache, since case inconsistency can cause PTR match failures (initial
+  patch thanks to Joni Fieggen) and other problems.
+
 * Tue Sep  2 2014 Stuart Gathman <stu...@gathman.org> 2.0.10-1
 - Fix AAAA not flagged as bytes when strict=2 
 - Split mechanisms by space only, not by whitespace
diff -Nru pyspf-2.0.10/setup.py pyspf-2.0.11/setup.py
--- pyspf-2.0.10/setup.py	2014-09-02 15:17:08.000000000 -0400
+++ pyspf-2.0.11/setup.py	2014-09-21 20:24:02.000000000 -0400
@@ -6,7 +6,7 @@
 DESC = """SPF (Sender Policy Framework) implemented in Python."""
 
 setup(name='pyspf',
-      version='2.0.10',
+      version='2.0.11',
       description=DESC,
       author='Terence Way',
       author_email='te...@wayforward.net',
diff -Nru pyspf-2.0.10/spf.py pyspf-2.0.11/spf.py
--- pyspf-2.0.10/spf.py	2014-09-02 15:17:08.000000000 -0400
+++ pyspf-2.0.11/spf.py	2014-12-05 11:20:07.000000000 -0500
@@ -3,8 +3,8 @@
 
 Copyright (c) 2003 Terence Way <te...@wayforward.net>
 Portions Copyright(c) 2004,2005,2006,2007,2008,2011,2012 Stuart Gathman <stu...@bmsi.com>
-Portions Copyright(c) 2005,2006,2007,2008,2011,2012,2013 Scott Kitterman <sc...@kitterman.com>
-Portions Copyright(c) 2013 Stuart Gathman <stu...@gathman.org>
+Portions Copyright(c) 2005,2006,2007,2008,2011,2012,2013,2014 Scott Kitterman <sc...@kitterman.com>
+Portions Copyright(c) 2013,2014 Stuart Gathman <stu...@gathman.org>
 
 This module is free software, and you may redistribute it and/or modify
 it under the same terms as Python itself, so long as this copyright message
@@ -30,33 +30,41 @@
     http://www.wayforward.net/spf/
 """
 
-# CVS Commits since last release (2.0.9):
+# CVS Commits since last release (2.0.10):
 # $Log: spf.py,v $
-# Revision 1.108.2.128  2014/09/02 17:31:53  customdesigned
-# Release 2.0.10
+# Revision 1.108.2.136  2014/12/05 16:20:07  customdesigned
+# Release 2.0.11
 #
-# Revision 1.108.2.127  2014/09/01 21:17:13  kitterma
-# Fix TempError handling of errors from the DNS module.
+# Revision 1.108.2.135  2014/12/03 01:11:09  customdesigned
+# Fold case of domain for all cache entries.
 #
-# Revision 1.108.2.126  2014/08/02 18:35:50  customdesigned
-# '~' is also an unreserved char in rfc7208.
+# Revision 1.108.2.134  2014/12/03 01:01:24  customdesigned
+# PTR case change fix with test case
 #
-# Revision 1.108.2.125  2014/08/02 04:36:48  kitterma
-#   * Fix bug in SPF record parsing that caused all 'whitespace' characters to
-#     be considered valid term separators and not just spaces
+# Revision 1.108.2.133  2014/10/06 11:54:11  kitterma
+# *** empty log message ***
 #
-# Revision 1.108.2.124  2014/08/02 04:32:36  kitterma
-# Archive previous commit messages for spf.py in pyspf_changelog.txt and bumpi
-# version to 2.0.10 for start of follow on work.
+# Revision 1.108.2.132  2014/10/06 11:51:03  kitterma
+#   * Downcase IPv6 PTR results since case inconsistency can cause PTR match
+#     failures (patch thanks to Joni Fieggen)
+#
+# Revision 1.108.2.131  2014/09/22 17:20:33  customdesigned
+# Update comments
+#
+# Revision 1.108.2.130  2014/09/22 17:13:53  customdesigned
+# Cleaner fix for multiple spaces.
+#
+# Revision 1.108.2.129  2014/09/21 21:11:47  kitterma
+#  * Reset to start 2.0.11 development
+#  * Fixed bug where multiple spaces between terms causes pyspf to think they
+#    were unknown mechanisms
 #
-# Revision 1.108.2.123  2014/07/30 18:41:18  customdesigned
-# Fix flagging AAAA records in dns_a.  Add --strict option to CLI
 #
 # See pyspf_changelog.txt for earlier CVS commits.
 
 __author__ = "Terence Way, Stuart Gathman, Scott Kitterman"
 __email__ = "py...@openspf.org"
-__version__ = "2.0.10: Sep 2, 2014"
+__version__ = "2.0.11: Dec 5, 2014"
 MODULE = 'spf'
 
 USAGE = """To check an incoming mail request:
@@ -776,7 +784,7 @@
 
         # Split string by space, drop the 'v=spf1'.  Split by all whitespace
         # casuses things like carriage returns being treated as valid space
-        # separators, so split() is not sufficient.
+        # separators, so split() is not sufficient.  
         spf = spf.split(' ')
         # Catch case where SPF record has no spaces.
         # Can never happen with conforming dns_spf(), however
@@ -787,7 +795,10 @@
             if self.strict > 1:
                 raise AmbiguityWarning('Invalid SPF record in', self.d)
             return ('none', 250, EXPLANATIONS['none'])
-        spf = spf[1:]
+        # Just to make it even more fun, the relevant piece of the ABNF for
+        # term separations is *( 1*SP ( directive / modifier ) ), so it's one
+        # or more spaces, not just one.  So strip empty mechanisms.
+        spf = [mech for mech in spf[1:] if mech]
 
         # copy of explanations to be modified by exp=
         exps = self.exps
@@ -1257,6 +1268,7 @@
         if name.endswith('.'): name = name[:-1]
         if not reduce(lambda x,y:x and 0 < len(y) < 64, name.split('.'),True):
             return []   # invalid DNS name (too long or empty)
+        name = name.lower()
         result = self.cache.get( (name, qtype), [])
         if result: return result
         cnamek = (name,'CNAME')
@@ -1274,6 +1286,9 @@
                 timeout = self.timeout
             timethen = time.time()
             for k, v in DNSLookup(name, qtype, self.strict, timeout):
+                # Force case insensitivity in cache, DNS servers often
+                # return random case in domain part of answers.
+                k = (k[0].lower(), k[1]) 
                 if k == cnamek:
                     cname = v
                 if k[1] == 'CNAME' or (qtype,k[1]) in safe2cache:
diff -Nru pyspf-2.0.10/test/rfc4408-tests.yml pyspf-2.0.11/test/rfc4408-tests.yml
--- pyspf-2.0.10/test/rfc4408-tests.yml	2014-08-02 01:44:54.000000000 -0400
+++ pyspf-2.0.11/test/rfc4408-tests.yml	2014-09-21 20:24:02.000000000 -0400
@@ -1,7 +1,7 @@
 # This is the openspf.org test suite (release 2009.10) based on RFC 4408.
 # http://www.openspf.org/Test_Suite
 #
-# $Id: rfc4408-tests.yml,v 1.1.2.29 2014/08/02 05:44:54 customdesigned Exp $
+# $Id: rfc4408-tests.yml,v 1.1.2.30 2014/09/21 20:44:03 kitterma Exp $
 # vim:sw=2 sts=2 et
 #
 # See rfc4408-tests.CHANGES for a changelog.
@@ -127,6 +127,14 @@
     mailfrom: "foo...@nothosed.example.com"
     result: fail
     explanation: DEFAULT
+  two-spaces:
+    description: >-
+      ABNF for term separation is one or more spaces, not just one.
+    spec: 4.6.1
+    helo: hosed
+    host: 1.2.3.4
+    mailfrom: "actua...@fine.example.com"
+    result: fail
 zonedata:
   example.com:
     - TIMEOUT
@@ -147,6 +155,8 @@
   nothosed.example.com:
     - SPF:  "v=spf1 a:example.net -all"
     - SPF:  "\x96"
+  fine.example.com:
+    - TXT: "v=spf1 a  -all"
 ---
 description: Record lookup
 tests:
diff -Nru pyspf-2.0.10/test/rfc7208-tests.yml pyspf-2.0.11/test/rfc7208-tests.yml
--- pyspf-2.0.10/test/rfc7208-tests.yml	2014-08-02 14:35:01.000000000 -0400
+++ pyspf-2.0.11/test/rfc7208-tests.yml	2014-12-02 20:11:10.000000000 -0500
@@ -1,7 +1,7 @@
 # This is the openspf.org test suite (release 2014.04) based on RFC 7208.
 # http://www.openspf.org/Test_Suite
 #
-# $Id: rfc7208-tests.yml,v 1.1.2.8 2014/08/02 18:35:01 customdesigned Exp $
+# $Id: rfc7208-tests.yml,v 1.1.2.12 2014/12/03 01:11:10 customdesigned Exp $
 # vim:sw=2 sts=2 et
 #
 # See rfc7208-tests.CHANGES for a changelog.
@@ -128,6 +128,14 @@
     host: 192.0.2.3
     mailfrom: "foo...@ctrl.example.com"
     result: permerror
+  two-spaces:
+    description: >-
+      ABNF for term separation is one or more spaces, not just one.
+    spec: 4.6.1
+    helo: hosed
+    host: 1.2.3.4
+    mailfrom: "actua...@fine.example.com"
+    result: fail
 zonedata:
   example.com:
     - TIMEOUT
@@ -151,6 +159,8 @@
   ctrl.example.com:
     - SPF:  "v=spf1 a:ctrl.example.com\x0dptr -all"
     - A: 192.0.2.3
+  fine.example.com:
+    - TXT: "v=spf1 a  -all"
 ---
 description: Record lookup
 tests:
@@ -627,9 +637,24 @@
     host: 1.2.3.4
     mailfrom: f...@e5.example.com
     result: permerror
+  ptr-case-change:
+    description: >-
+      arpa domain is case insensitive.
+    comment: >-
+      Some DNS servers have random case in the domain part of returned
+      answers, especially for PTR records.  For example, a query for
+      1.2.6.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.E.F.0.0.4.F.1.1.1.0.1.0.A.2.ip6.arpa
+      may return
+      1.2.6.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.E.F.0.0.4.F.1.1.1.0.1.0.a.2.ip6.arpa
+    spec: 5.5/2
+    helo: mail.example.com
+    host: 2001:db8::1
+    mailfrom: b...@e6.example.com
+    result: pass
 zonedata:
   mail.example.com:
     - A: 1.2.3.4
+    - AAAA: 2001:db8::1
   e1.example.com:
     - SPF: v=spf1 ptr/0 -all
   e2.example.com:
@@ -640,6 +665,8 @@
     - PTR: mail.example.com
   1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.E.B.A.B.E.F.A.C.ip6.arpa:
     - PTR: e3.example.com
+  1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.D.0.1.0.0.2.ip6.arpa:
+    - PTR: mail.example.com
   e3.example.com:
     - SPF: v=spf1 ptr -all
     - A: 1.2.3.4
@@ -648,6 +675,8 @@
     - SPF: v=spf1 ptr -all
   e5.example.com:
     - SPF: "v=spf1 ptr:"
+  e6.example.com:
+    - SPF: "v=spf1 ptr:Example.com -all"
 ---
 description: A mechanism syntax
 tests:
@@ -1607,7 +1636,7 @@
   e4.example.com:
     - SPF: v=spf1 ip6:::1.1.1.1//33
   e5.example.com:
-    - SPF: v=spf1 ip6:CAFE:BABE:8000::/33
+    - SPF: v=spf1 ip6:Cafe:Babe:8000::/33
   e6.example.com:
     - SPF: v=spf1 ip6::CAFE::BABE
 ---
diff -Nru pyspf-2.0.10/test/testspf.py pyspf-2.0.11/test/testspf.py
--- pyspf-2.0.10/test/testspf.py	2014-08-02 01:01:14.000000000 -0400
+++ pyspf-2.0.11/test/testspf.py	2014-12-02 20:04:28.000000000 -0500
@@ -42,7 +42,7 @@
         if timeout:
           raise spf.TempError('DNS timeout')
         return
-      t,v = i
+      t,v,n = i
       if t == qtype:
         timeout = False
       if v == 'TIMEOUT':
@@ -54,7 +54,7 @@
         v = bytes(socket.inet_pton(socket.AF_INET6,v))
       elif t in ('TXT','SPF'):
         v = tuple([s.encode('utf-8') for s in v])
-      yield ((name,t),v)
+      yield ((n,t),v)
   except KeyError:
     if name.startswith('error.'):
       raise spf.TempError('DNS timeout')
@@ -78,7 +78,7 @@
     if type(self.comment) is str:
       self.comment = self.comment.splitlines()
 
-def getrdata(r):
+def getrdata(r,name):
   "Unpack rdata given as list of maps to list of tuples."
   txt = []        # generated TXT records
   gen = True
@@ -89,12 +89,12 @@
         if t == 'TXT':
           gen = False # no generated TXT records
         elif t == 'SPF' and gen:
-          txt.append(('TXT',v))
+          txt.append(('TXT',v,name))
         if v != 'NONE':
           if t in ('TXT','SPF') and type(v) == str:
-            yield (t,(v,))
+            yield (t,(v,),name)
           else:
-            yield i
+            yield (t,v,name)
     except:
       yield m
   if gen:
@@ -103,7 +103,7 @@
 
 def loadZone(data):
   return dict([
-    (d.lower(), list(getrdata(r))) for d,r in list(data['zonedata'].items())
+    (d.lower(), list(getrdata(r,d))) for d,r in list(data['zonedata'].items())
   ])
 
 class SPFScenario(object):
@@ -152,7 +152,7 @@
     unittest.TestCase.__init__(self)
     self._spftest = t
     self._testMethodName = 'runTest'
-    self._testMethodDoc = t.spec
+    self._testMethodDoc = str(t.spec)
 
   def id(self):
     t = self._spftest
@@ -1257,6 +1268,7 @@
         if name.endswith('.'): name = name[:-1]
         if not reduce(lambda x,y:x and 0 < len(y) < 64, name.split('.'),True):
             return []   # invalid DNS name (too long or empty)
+        name = name.lower()
         result = self.cache.get( (name, qtype), [])
         if result: return result
         cnamek = (name,'CNAME')
@@ -1274,6 +1286,9 @@
                 timeout = self.timeout
             timethen = time.time()
             for k, v in DNSLookup(name, qtype, self.strict, timeout):
+                # Force case insensitivity in cache, DNS servers often
+                # return random case in domain part of answers.
+                k = (k[0].lower(), k[1]) 
                 if k == cnamek:
                     cname = v
                 if k[1] == 'CNAME' or (qtype,k[1]) in safe2cache:

Reply via email to