diff --git a/TODO b/TODO
index d2cca47ad369ad953c0ed7539f18d854da5b5d4c..22b3b40ad1def0dc136470c03a31129e679744c3 100644
--- a/TODO
+++ b/TODO
@@ -1,7 +1,14 @@
+Generate DSNs according to RFC 3464
+
+Parse incoming 3464 DSNs for "Action: failed" to recognize delayed
+failures.  This works regardless of Subject.
+
+Get temperror policy from access file.
+
 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
+Reporting explanation for failure should show source if sender
 provided explanation.
 
 Reports PROBATION even when rejecting message (works, but confusing in log).
diff --git a/bms.py b/bms.py
index fb392d24f6ad6b3c4ff0d9ebce01161326506cfb..accad5245af7cee76f9360f5fc37fc9ebaab56ee 100644
--- a/bms.py
+++ b/bms.py
@@ -1,6 +1,9 @@
 #!/usr/bin/env python
 # A simple milter that has grown quite a bit.
 # $Log$
+# Revision 1.68  2006/10/04 03:46:01  customdesigned
+# Fix defaults.
+#
 # Revision 1.67  2006/10/01 01:44:06  customdesigned
 # case_sensitive_localpart option, more delayed bounce heuristics,
 # optional smart_alias section.
@@ -1278,13 +1281,19 @@ class bmsMilter(Milter.Milter):
 	  # if confirmed by finding our signed Message-ID, 
 	  # original sender (encoded in Message-ID) is blacklisted
 
+    elif lname == 'from' and val.lower().startswith('postmaster@'):
+      # Yes, if From header comes last, this might not help much.
+      # But this is a heuristic - if MTAs would send proper DSNs in
+      # the first place, none of this would be needed.
+      self.is_bounce = True
+      
     # check for invalid message id
-    if lname == 'message-id' and len(val) < 4:
+    elif lname == 'message-id' and len(val) < 4:
       self.log('REJECT: %s: %s' % (name,val))
       return Milter.REJECT
 
     # check for common bulk mailers
-    if lname == 'x-mailer':
+    elif lname == 'x-mailer':
       mailer = val.lower()
       if mailer in ('direct email','calypso','mail bomber') \
 	or mailer.find('optin') >= 0:
diff --git a/milter.spec b/milter.spec
index 96af07dcac92e16dd4f0e66e779a6f9a2a4eff73..c2b921762e5f6013b71fddf9a8b23fcc1f7a151b 100644
--- a/milter.spec
+++ b/milter.spec
@@ -92,7 +92,7 @@ cat >$RPM_BUILD_ROOT/etc/cron.daily/milter <<'EOF'
 
 find /var/log/milter/save -mtime +7 | xargs $R rm
 # work around memory leak
-/etc/init.d/milter restart
+/etc/init.d/milter condrestart
 EOF
 chmod a+x $RPM_BUILD_ROOT/etc/cron.daily/milter
 
diff --git a/spf.py b/spf.py
index 20d87ae39982de28f25916888516b117ec408de7..3fb15db402573accf1e6cbb5126eb12194cbdaec 100755
--- a/spf.py
+++ b/spf.py
@@ -47,124 +47,8 @@ For news, bugfixes, etc. visit the home page for this implementation at
 # Development taken over by Stuart Gathman <stuart@bmsi.com>.
 #
 # $Log$
-# Revision 1.105  2006/10/07 22:06:28  kitterma
-# Pass strict status to DNSLookup - will be needed for TCP failover.
-#
-# Revision 1.104  2006/10/07 21:59:37  customdesigned
-# long/empty label tests and fix.
-#
-# Revision 1.103  2006/10/07 18:16:20  customdesigned
-# Add tests for and fix RE_TOPLAB.
-#
-# Revision 1.102  2006/10/05 13:57:15  customdesigned
-# Remove isSPF and make missing space after version tag a warning.
-#
-# Revision 1.101  2006/10/05 13:39:11  customdesigned
-# SPF version tag is case insensitive.
-#
-# 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
-#
-# Revision 1.95  2006/09/30 22:53:44  customdesigned
-# Fix getp to obey SHOULDs in RFC.
-#
-# Revision 1.94  2006/09/30 22:23:25  customdesigned
-# p macro tests and fixes
-#
-# Revision 1.93  2006/09/30 20:57:06  customdesigned
-# Remove generator expression for compatibility with python2.3.
-#
-# Revision 1.92  2006/09/30 19:52:52  customdesigned
-# Removed redundant flag and unneeded global.
-#
-# Revision 1.91  2006/09/30 19:37:49  customdesigned
-# Missing L
-#
-# Revision 1.90  2006/09/30 19:29:58  customdesigned
-# pydns returns AAAA RR as binary string
-#
-# Revision 1.89  2006/09/29 20:23:11  customdesigned
-# Optimize cidrmatch
-#
-# Revision 1.88  2006/09/29 19:44:10  customdesigned
-# Fix ptr with ip6 for harsh mode.
-#
-# Revision 1.87  2006/09/29 19:26:53  customdesigned
-# Add PTR tests and fix ip6 ptr
-#
-# Revision 1.86  2006/09/29 17:55:22  customdesigned
-# Pass ip6 tests
-#
-# Revision 1.85  2006/09/29 15:58:02  customdesigned
-# Pass self test on non IP6 python.
-# PTR accepts no cidr.
-#
-# Revision 1.83  2006/09/27 18:09:40  kitterma
-# Converted spf.check to return pre-MARID result codes for drop in
-# compatibility with pySPF 1.6/1.7.  Added new procedure, spf.check2 to
-# return RFC4408 results in a two part answer (result, explanation).
-# This is the external API for pySPF 2.0.  No longer any need to branch
-# for 'classic' and RFC compliant pySPF libraries.
-#
-# Revision 1.82  2006/09/27 18:02:21  kitterma
-# Converted max MX limit to ambiguity warning for validator.
-#
-# Revision 1.81  2006/09/27 17:38:14  kitterma
-# Updated initial comments and moved pre-1.7 changes to spf_changelog.
-#
-# Revision 1.80  2006/09/27 17:33:53  kitterma
-# Fixed indentation error in check0.
-#
-# Revision 1.79  2006/09/26 18:05:44  kitterma
-# Removed unused receiver policy definitions.
-#
-# Revision 1.78  2006/09/26 16:15:50  kitterma
-# added additional IP4 and CIDR validation tests - no code changes.
-#
-# Revision 1.77  2006/09/25 19:42:32  customdesigned
-# Fix unknown macro sentinel
-#
-# Revision 1.76  2006/09/25 19:10:40  customdesigned
-# Fix exp= error and add another failing test.
-#
-# Revision 1.75  2006/09/25 02:02:30  kitterma
-# Fixed redirect-cancels-exp test suite failure.
-#
-# Revision 1.74  2006/09/24 04:04:08  kitterma
-# Implemented check for macro 'c' - Macro unimplimented.
-#
-# Revision 1.73  2006/09/24 02:08:35  kitterma
-# Fixed invalid-macro-char test failure.
-#
-# Revision 1.72  2006/09/23 05:45:52  kitterma
-# Fixed domain-name-truncation test failure
-#
-# Revision 1.71  2006/09/22 01:02:54  kitterma
-# pySPF correction for nolocalpart in rfc4408-tests.yml failed, 4.3/2.
-# Added comments to testspf.py on where to get YAML.
-#
-# Revision 1.70  2006/09/18 02:13:27  kitterma
-# Worked through a large number of pylint issues - all 4 spaces, not a mix
-# of 4 spaces, 2 spaces, and tabs. Caught a few minor errors in the process.
-# All built in tests still pass.
-#
-# Revision 1.69  2006/09/17 18:44:25  kitterma
-# Fixed validation mode only crash bug when rDNS check had no PTR record
-#
+# Revision 1.107  2006/11/04 21:58:12  customdesigned
+# Prevent cache poisoning by bogus additional RRs in PTR DNS response.
 #
 # See spf_changelog.txt for earlier changes.
 
@@ -303,8 +187,9 @@ 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' }
-
+COMMON_MISTAKES = {
+  'prt': 'ptr', 'ip': 'ip4', 'ipv4': 'ip4', 'ipv6': 'ip6', 'all.': 'all'
+}
 
 #If harsh processing, for the validator, is invoked, warn if results
 #likely deviate from the publishers intention.
@@ -1163,6 +1048,22 @@ class query(object):
         """Get a list of domain names for an IP address."""
         return self.dns('%s.%s.arpa'%(reverse_dots(i),self.v), 'PTR')
 
+    # We have to be careful which additional DNS RRs we cache.  For
+    # instance, PTR records are controlled by the connecting IP, and they
+    # could poison our local cache with bogus A and MX records.  
+
+    SAFE2CACHE = {
+      ('MX','A'): None,
+      ('MX','MX'): None,
+      ('CNAME','A'): None,
+      ('CNAME','CNAME'): None,
+      ('A','A'): None,
+      ('AAAA','AAAA'): None,
+      ('PTR','PTR'): None,
+      ('TXT','TXT'): None,
+      ('SPF','SPF'): None
+    }
+
     def dns(self, name, qtype, cnames=None):
         """DNS query.
 
@@ -1179,11 +1080,14 @@ class query(object):
         """
         result = self.cache.get( (name, qtype) )
         cname = None
+
         if not result:
+	    safe2cache = query.SAFE2CACHE
             for k, v in DNSLookup(name, qtype, self.strict):
                 if k == (name, 'CNAME'):
                     cname = v
-                self.cache.setdefault(k, []).append(v)
+		if (qtype,k[1]) in safe2cache:
+		    self.cache.setdefault(k, []).append(v)
             result = self.cache.get( (name, qtype), [])
         if not result and cname:
             if not cnames: