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: