diff --git a/NEWS b/NEWS
index 05b1baf9800352a125e9a2a031fc8f67244b0cf4..f0c7c0b2dfc18123f5588049087b373c36d53be6 100644
--- a/NEWS
+++ b/NEWS
@@ -1,5 +1,6 @@
 Here is a history of user visible changes to Python milter.
 
+0.8.6	
 0.8.5	Simple trusted_forwarder implementation.
 	Fix access_file neutral policy
 	Move Received-SPF header to beginning of headers
diff --git a/TODO b/TODO
index cf3e57cc8337c77a16e9a098384ef2a51c10f674..b38a7db69a45400fe4e561ac864d8de6d0976e90 100644
--- a/TODO
+++ b/TODO
@@ -1,3 +1,6 @@
+Allow unsigned DSNs from selected domains (that don't accept signed MFROM,
+e.g. verizon.net).
+
 Added Message-ID header to DSN with SRS signed sender.  When seen on incoming
 rfc ignorant failure message, blacklist sender.
 
diff --git a/bms.py b/bms.py
index 11d50f7a208acedc43f3a0833d0aacefafbfdc86..642a4d896d4f33c65ce865598d73545de22fa268 100644
--- a/bms.py
+++ b/bms.py
@@ -1,6 +1,11 @@
 #!/usr/bin/env python
 # A simple milter that has grown quite a bit.
 # $Log$
+# Revision 1.56  2006/02/24 02:12:54  customdesigned
+# Properly report hard PermError (lax mode fails also) by always setting
+# perm_error attribute with PermError exception.  Improve reporting of
+# invalid domain PermError.
+#
 # Revision 1.55  2006/02/17 05:04:29  customdesigned
 # Use SRS sign domain list.
 # Accept but do not use for training whitelisted senders without SPF pass.
@@ -184,7 +189,6 @@ import mime
 import email.Errors
 import Milter
 import tempfile
-import traceback
 import ConfigParser
 import time
 import socket
@@ -800,6 +804,7 @@ class bmsMilter(Milter.Milter):
     self.blacklist = False
     self.reject_spam = True
     self.data_allowed = True
+    self.delayed_failure = None
     self.trust_received = self.trusted_relay
     self.trust_spf = self.trusted_relay
     self.redirect_list = []
@@ -1082,7 +1087,9 @@ class bmsMilter(Milter.Milter):
 	      self.log("REJECT: ses spoofed:",oldaddr)
 	      self.setreply('550','5.7.1','Invalid SES signature')
 	      return Milter.REJECT
-	    if srs_reject_spoofed:	# FIXME: srs_reject_immed?
+	    # reject for certain recipients are delayed until after DATA
+	    if srs_reject_spoofed \
+		and not user.lower() in ('postmaster','abuse'):
 	      return self.forged_bounce()
 	    self.data_allowed = not srs_reject_spoofed
 
@@ -1125,6 +1132,13 @@ class bmsMilter(Milter.Milter):
 	  self.setreply('550','5.7.1','That subject is not allowed')
 	  return Milter.REJECT
 
+      # even if we wanted the Taiwanese spam, we can't read Chinese
+      if block_chinese:
+	if val.startswith('=?big5') or val.startswith('=?ISO-2022-JP'):
+	  self.log('REJECT: %s: %s' % (name,val))
+	  self.setreply('550','5.7.1',"We don't understand chinese")
+	  return Milter.REJECT
+
       # check for spam that claims to be legal
       lval = val.lower().strip()
       for adv in ("adv:","adv.","adv ","[adv]","(adv)","advt:","advert:"):
@@ -1152,6 +1166,15 @@ class bmsMilter(Milter.Milter):
 	  self.setreply('550','5.7.1','I find unedited forwards annoying')
 	  return Milter.REJECT
 
+      # check for delayed bounce of CBV
+      if self.is_bounce and srs:
+	for w in ("delivery failure", "failure notice",
+	  "returned mail", "undeliverable"):
+	  if lval.startswith(w):
+	    self.delayed_failure = val.strip()
+	    # if confirmed by finding our signed Message-ID, 
+	    # original sender (encoded in Message-ID) is blacklisted
+
     # check for invalid message id
     if lname == 'message-id' and len(val) < 4:
       self.log('REJECT: %s: %s' % (name,val))
@@ -1192,12 +1215,6 @@ class bmsMilter(Milter.Milter):
     # decode near ascii text to unobfuscate
     val = parse_header(hval)
     if not self.internal_connection and not (self.blacklist or self.whitelist):
-      # even if we wanted the Taiwanese spam, we can't read Chinese
-      if block_chinese and lname == 'subject':
-	if val.startswith('=?big5') or val.startswith('=?ISO-2022-JP'):
-	  self.log('REJECT: %s: %s' % (name,val))
-	  self.setreply('550','5.7.1',"We don't understand chinese")
-	  return Milter.REJECT
       rc = self.check_header(name,val)
       if rc != Milter.CONTINUE: return rc
     elif self.whitelist_sender and lname == 'subject':
@@ -1490,6 +1507,27 @@ class bmsMilter(Milter.Milter):
       return Milter.ACCEPT	# no message collected - so no eom processing
 
     try:
+      # check for delayed bounce
+      if self.delayed_failure:
+        self.fp.seek(0)
+	for ln in self.fp:
+	  if ln.lower().startswith('message-id:'):
+	    name,val = ln.split(None,1)
+	    if val.startswith('<SRS'):
+	      try:
+		sender = srs.reverse(val[1:-1])
+		cbv_cache[sender] = 500,self.delayed_failure,time.time()
+		try:
+		  # save message for debugging
+		  fname = tempfile.mktemp(".dsn")
+		  os.rename(self.tempname,fname)
+		except:
+		  fname = self.tempname
+		self.tempname = None
+		self.log('BLACKLIST:',sender,fname)
+		return Milter.DISCARD
+	      except: continue
+
       # analyze external mail for spam
       spam_checked = self.check_spam()	# tag or quarantine for spam
       if not self.fp:
diff --git a/milter.spec b/milter.spec
index 546f3d0204181393ff45279a597ef61f219c3cf6..6167f9fbae4efe31a1d17318c33300839db4b178 100644
--- a/milter.spec
+++ b/milter.spec
@@ -174,6 +174,19 @@ rm -rf $RPM_BUILD_ROOT
 /usr/share/sendmail-cf/hack/rhsbl.m4
 
 %changelog
+* Thu Feb 23 2006 Stuart Gathman <stuart@bmsi.com> 0.8.6-1
+- Delay reject of unsigned RCPT for postmaster and abuse only
+- Fix dsn reporting of hard permerror
+- Resolve FIXME for wrap_close in miltermodule.c
+- Add Message-ID to DSNs
+- Use signed Message-ID in delayed reject to blacklist senders
+- Auto-train via blacklist and auto-whitelist
+- Don't check userlist for signed MFROM
+- Accept but skip DSPAM and training for whitelisted senders without SPF PASS
+- Report GC stats 
+- Support CIDR matching for IP lists
+- Support pysrs sign feature
+- Support localpart specific SPF policy in access file
 * Thu Dec 29 2005 Stuart Gathman <stuart@bmsi.com> 0.8.5-1
 - Simple trusted_forwarder implementation.
 - Fix access_file neutral policy