From 8df5cd026e8a34c6c0872c5f5e0d62938c838ad7 Mon Sep 17 00:00:00 2001 From: Stuart Gathman Date: Fri, 22 Jul 2005 16:00:23 +0000 Subject: [PATCH] Limit CNAME chains independently of DNS lookup limit --- milter.html | 11 ++++++++-- milter.spec | 13 ++++++++---- spf.py | 58 ++++++++++++++++++++++++++++++++++++++++------------- 3 files changed, 62 insertions(+), 20 deletions(-) diff --git a/milter.html b/milter.html index 8c5eece..f553061 100644 --- a/milter.html +++ b/milter.html @@ -24,7 +24,7 @@ ALT="Viewable With Any Browser" BORDER="0"> Stuart D. Gathman
This web page is written by Stuart D. Gathman
and
sponsored by Business Management Systems, Inc.
-Last updated Jun 09, 2005 +Last updated Jul 20, 2005 See the FAQ | Download now | Subscribe to mailing list | @@ -49,7 +49,14 @@ efficient and secure. I recommend upgrading. Python milter is being moved to pymilter Sourceforge -project for development. +project for development and release downloads. +

+Release 0.8.2 has changes to SPF to bring it in line with the newly +official RFC. It adds SES support (the original SES without body hash) +for pysrs-0.30.10, and honeypot support for pydspam-1.1.9. There is +a new method in the base milter module. milter.set_exception_policy(i) +lets you choose a policy of CONTINUE, REJECT, or TEMPFAIL (default) for +untrapped exceptions encountered in a milter callback.

Release 0.8.0 is the first Sourceforge release. It supports Python-2.4, and provides an option to accept mail diff --git a/milter.spec b/milter.spec index 11b1db7..a0d30ed 100644 --- a/milter.spec +++ b/milter.spec @@ -1,6 +1,6 @@ %define name milter %define version 0.8.2 -%define release 2.RH7 +%define release 4.RH7 # what version of RH are we building for? %define redhat9 0 %define redhat7 1 @@ -31,7 +31,7 @@ Name: %{name} Version: %{version} Release: %{release} Source: %{name}-%{version}.tar.gz -#Patch: %{name}-%{version}.patch +Patch: %{name}-%{version}.patch Copyright: GPL Group: Development/Libraries BuildRoot: %{_tmppath}/%{name}-buildroot @@ -52,7 +52,7 @@ modules provide for navigating and modifying MIME parts. %prep %setup -#%patch -p1 +%patch -p1 %build env CFLAGS="$RPM_OPT_FLAGS" %{python} setup.py build @@ -166,6 +166,12 @@ rm -rf $RPM_BUILD_ROOT /usr/share/sendmail-cf/hack/rhsbl.m4 %changelog +* Fri Jul 15 2005 Stuart Gathman 0.8.2-4 +- Limit each CNAME chain independently like PTR and MX +* Fri Jul 15 2005 Stuart Gathman 0.8.2-3 +- Limit CNAME lookups (regression) +* Fri Jul 15 2005 Stuart Gathman 0.8.2-2 +- Handle corrupt ZIP attachments * Fri Jul 15 2005 Stuart Gathman 0.8.2-1 - Strict processing limits per SPF RFC - Fixed several parsing bugs under RFC @@ -174,7 +180,6 @@ rm -rf $RPM_BUILD_ROOT - Extended SPF processing results beyond strict RFC limits - Support original SES for local bounce protection (requires pysrs-0.30.10) - Callback exception processing option in milter module -- Handle corrupt ZIP attachments * Thu Jun 16 2005 Stuart Gathman 0.8.1-1 - Fix zip in zip loop in mime.py - Fix HeaderParseError in bms.py header callback diff --git a/spf.py b/spf.py index c116e22..eb5edaa 100755 --- a/spf.py +++ b/spf.py @@ -47,8 +47,24 @@ For news, bugfixes, etc. visit the home page for this implementation at # Terrence is not responding to email. # # $Log$ -# Revision 1.11 2005/07/20 03:30:04 customdesigned -# Check pydspam version for honeypot, include latest pyspf changes. +# Revision 1.31 2005/07/22 02:11:50 customdesigned +# Use dictionary to check for CNAME loops. Check limit independently for +# each top level name, just like for PTR. +# +# Revision 1.30 2005/07/21 20:07:31 customdesigned +# Translate DNS error in DNSLookup. This completely isolates DNS +# dependencies to the DNSLookup method. +# +# Revision 1.29 2005/07/21 17:49:39 customdesigned +# My best guess at what RFC intended for limiting CNAME loops. +# +# Revision 1.28 2005/07/21 17:37:08 customdesigned +# Break out external DNSLookup method so that test suite can +# duplicate CNAME loop bug. Test zone data dictionary now +# mirrors structure of real DNS. +# +# Revision 1.27 2005/07/21 15:26:06 customdesigned +# First cut at updating docs. Test suite is obsolete. # # Revision 1.26 2005/07/20 03:12:40 customdesigned # When not in strict mode, don't give PermErr for bad mechanism until @@ -256,6 +272,16 @@ if not hasattr(DNS.Type,'SPF'): DNS.Type.typemap[99] = 'SPF' DNS.Lib.RRunpacker.getSPFdata = DNS.Lib.RRunpacker.getTXTdata +def DNSLookup(name,qtype): + try: + req = DNS.DnsRequest(name, qtype=qtype) + resp = req.req() + #resp.show() + # key k: ('wayforward.net', 'A'), value v + return [((a['name'], a['typename']), a['data']) for a in resp.answers] + except DNS.DNSError,x: + raise TempError,'DNS ' + str(x) + # 32-bit IPv4 address mask MASK = 0xFFFFFFFFL @@ -308,6 +334,7 @@ DEFAULT_SPF = 'v=spf1 a/24 mx/24 ptr' MAX_LOOKUP = 10 #draft-schlitt-spf-classic-02 Para 10.1 MAX_MX = 10 #draft-schlitt-spf-classic-02 Para 10.1 MAX_PTR = 10 #draft-schlitt-spf-classic-02 Para 10.1 +MAX_CNAME = 10 # analogous interpretation to MAX_PTR MAX_RECURSION = 20 ALL_MECHANISMS = ('a', 'mx', 'ptr', 'exists', 'include', 'ip4', 'ip6', 'all') COMMON_MISTAKES = { 'prt': 'ptr', 'ip': 'ip4', 'ipv4': 'ip4', 'ipv6': 'ip6' } @@ -412,6 +439,9 @@ class query(object): >>> q.check(spf='v=spf1 ip4:192.0.0.0/8 ?all moo') ('unknown', 550, 'SPF Permanent Error: Unknown mechanism found: moo') + >>> q.check(spf='v=spf1 =a ?all moo') + ('unknown', 550, 'SPF Permanent Error: Unknown qualifier, IETF draft para 4.6.1, found in: =a') + >>> q.check(spf='v=spf1 ip4:192.0.0.0/8 ~all') ('pass', 250, 'sender SPF verified') @@ -453,8 +483,6 @@ class query(object): self.perm_error.ext = rc raise self.perm_error return rc - except DNS.DNSError,x: - return ('error', 450, 'SPF DNS Error: ' + str(x)) except TempError,x: return ('error', 450, 'SPF Temporary Error: ' + str(x)) except PermError,x: @@ -847,7 +875,7 @@ class query(object): """Get a list of domain names for an IP address.""" return self.dns(reverse_dots(i) + ".in-addr.arpa", 'PTR') - def dns(self, name, qtype): + def dns(self, name, qtype, cnames=None): """DNS query. If the result is in cache, return that. Otherwise pull the @@ -864,19 +892,21 @@ class query(object): result = self.cache.get( (name, qtype) ) cname = None if not result: - req = DNS.DnsRequest(name, qtype=qtype) - resp = req.req() - #resp.show() - for a in resp.answers: - # key k: ('wayforward.net', 'A'), value v - k, v = (a['name'], a['typename']), a['data'] + for k,v in DNSLookup(name,qtype): if k == (name, 'CNAME'): - cname = v + cname = v self.cache.setdefault(k, []).append(v) result = self.cache.get( (name, qtype), []) if not result and cname: - self.check_lookups() - result = self.dns(cname, qtype) + if not cnames: + cnames = {} + elif len(cnames) >= MAX_CNAME: + raise PermError( + 'Length of CNAME chain exceeds %d' % MAX_CNAME) + cnames[name] = cname + if cname in cnames: + raise PermError,'CNAME loop' + result = self.dns(cname, qtype, cnames=cnames) return result def get_header(self,res,receiver=None):