diff --git a/Milter/cache.py b/Milter/cache.py
index 937753124622774cd828f85ccc249171c722d2f1..e8ab0b5e59e956471c9eb13ba0e4506e9799c33c 100644
--- a/Milter/cache.py
+++ b/Milter/cache.py
@@ -10,6 +10,11 @@
 # CBV results.
 #
 # $Log$
+# Revision 1.6  2007/01/19 23:31:38  customdesigned
+# Move parse_header to Milter.utils.
+# Test case for delayed DSN parsing.
+# Fix plock when source missing or cannot set owner/group.
+#
 # Revision 1.5  2007/01/11 19:59:40  customdesigned
 # Purge old entries in auto_whitelist and send_dsn logs.
 #
@@ -129,12 +134,10 @@ class AddrCache(object):
   def __setitem__(self,sender,res):
     lsender = sender.lower()
     now = time.time()
-    cached = self.has_key(sender)
-    if not cached:
-      self.cache[lsender] = (now,res)
-      if not res and self.fname:
-	s = time.strftime(AddrCache.time_format,time.localtime(now))
-	print >>open(self.fname,'a'),sender,s # log refreshed senders
+    self.cache[lsender] = (now,res)
+    if not res and self.fname:
+      s = time.strftime(AddrCache.time_format,time.localtime(now))
+      print >>open(self.fname,'a'),sender,s # log refreshed senders
 
   def __len__(self):
     return len(self.cache)
diff --git a/NEWS b/NEWS
index a48e1b0f8b4ede007e10ab8a6b389c996b2d627f..c169e3951a7b6e1409700fdf41ef1a5041ada4d0 100644
--- a/NEWS
+++ b/NEWS
@@ -2,6 +2,7 @@ Here is a history of user visible changes to Python milter.
 0.8.8	move AddrCache, parse_addr, iniplist, parse_header to Milter package
 	fix plock for missing source and can't change owner/group
 	add sample spfmilter.py milter
+	private_relay config option
 0.8.7	Move spf module to pyspf
 	Prevent PTR cache poisoning
 	More lame bounce heuristics
diff --git a/bms.py b/bms.py
index 038624fd40948625527b1c58e12e72b4d9027252..65364c6df7e942a225a100c955ec113633596cc2 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.90  2007/01/23 19:46:20  customdesigned
+# Add private relay.
+#
 # Revision 1.89  2007/01/22 02:46:01  customdesigned
 # Convert tabs to spaces.
 #
@@ -359,7 +362,7 @@ def findsrs(fp):
     lnl = ln.lower()
     if lnl.startswith('action:'):
       if lnl.split()[-1] != 'failed': break
-    for k in ('message-id:','x-mailer:','sender:'):
+    for k in ('message-id:','x-mailer:','sender:','references:'):
       if lnl.startswith(k):
         lastln = ln
         break
@@ -907,6 +910,7 @@ class bmsMilter(Milter.Milter):
         self.log('REJECT: RELAY:',to)
 	self.setreply('550','5.7.1','Unauthorized relay for %s' % domain)
         return Milter.REJECT
+
       # non DSN mail to SRS address will bounce due to invalid local part
       canon_to = '@'.join(t)
       self.recipients.append(canon_to)
@@ -941,8 +945,8 @@ class bmsMilter(Milter.Milter):
         self.reject_spam = False
     self.smart_alias(to)
     # get recipient after virtusertable aliasing
-    #rcpt = self.getsymval("{rcpt_addr}")
-    #self.log("rcpt-addr",rcpt);
+    rcpt = self.getsymval("{rcpt_addr}")
+    self.log("rcpt-addr",rcpt);
     return Milter.CONTINUE
 
   # Heuristic checks for spam headers
@@ -1379,7 +1383,9 @@ class bmsMilter(Milter.Milter):
         self.fp.seek(0)
         sender = findsrs(self.fp)
         if sender:
-          cbv_cache[sender] = 500,self.delayed_failure,time.time()
+          cbv_cache[sender] = 550,self.delayed_failure
+	  # make blacklisting persistent, since delayed DSNs are expensive
+	  blacklist[sender] = None
           try:
             # save message for debugging
             fname = tempfile.mktemp(".dsn")
@@ -1596,7 +1602,6 @@ class bmsMilter(Milter.Milter):
         self.log('TEMPFAIL:',desc)
         self.setreply('450','4.2.0',*desc.splitlines())
         return Milter.TEMPFAIL
-      if len(res) < 3: res += time.time(),
       cbv_cache[sender] = res
       self.log('REJECT:',desc)
       self.setreply('550','5.7.1',*desc.splitlines())
diff --git a/doc/links.h b/doc/links.h
index 92336fa0562997221a1cfe4816b669e7e711e61f..a72ce765e50c1d8f596f5ace9c3e0849d4228d0c 100644
--- a/doc/links.h
+++ b/doc/links.h
@@ -9,6 +9,7 @@
 <li><a href="logmsgs.html">Log&nbsp;Messages</a>
 <li><a href="http://bmsi.com/mailman/listinfo/pymilter">Mailing&nbsp;List</a>
 <li><a href="credits.html">CREDITS</a>
+<li><a href="http://sourceforge.net"><img src="http://sflogo.sourceforge.net/sflogo.php?group_id=139894&amp;type=1" width="88" height="31" border="0" alt="SourceForge.net Logo" /></a>
 <h3>Links</h3>
 <li><a href="http://www.milter.org/milter_api/api.html">C&nbsp;API</a>
 <li><a href="http://www.milter.org/">Milter.Org</a>
diff --git a/milter.spec b/milter.spec
index d27eaeaee4f068c3fb6b059ba88d74fa2a8096e7..a10c2d3e459aad70a79809483996497afdb82586 100644
--- a/milter.spec
+++ b/milter.spec
@@ -181,6 +181,7 @@ rm -rf $RPM_BUILD_ROOT
 - move parse_header to Milter.utils
 - fix plock for missing source and can't change owner/group
 - add sample spfmilter.py milter
+- private_relay config option
 * Sat Nov 04 2006 Stuart Gathman <stuart@bmsi.com> 0.8.7-1
 - More lame bounce heuristics
 - SPF moved to pyspf RPM
diff --git a/testutils.py b/testutils.py
index 24ff11f125fd84cbc969f74df25d16f417559685..2d78febf20004ff9a1e437485684f0d2c14bc0f1 100644
--- a/testutils.py
+++ b/testutils.py
@@ -8,13 +8,12 @@ class AddrCacheTestCase(unittest.TestCase):
 
   def setUp(self):
     self.fname = 'test.dat'
-    self.cache = AddrCache(fname=self.fname)
 
   def tearDown(self):
     os.remove(self.fname)
 
   def testAdd(self):
-    cache = self.cache
+    cache = AddrCache(fname=self.fname)
     cache['foo@bar.com'] = None
     cache.addperm('baz@bar.com')
     cache['temp@bar.com'] = 'testing'
@@ -26,6 +25,17 @@ class AddrCacheTestCase(unittest.TestCase):
     self.failUnless(len(s) == 2)
     self.failUnless(s[0].startswith('foo@bar.com '))
     self.assertEquals(s[1].strip(),'baz@bar.com')
+    # check that new result overrides old
+    cache['temp@bar.com'] = None
+    self.failUnless(not cache['temp@bar.com'])
+
+  def testDomain(self):
+    fp = open(self.fname,'w')
+    print >>fp,'spammer.com'
+    fp.close()
+    cache = AddrCache(fname=self.fname)
+    cache.load(self.fname,30)
+    self.failUnless('spammer.com' in cache)
 
 def suite(): 
   s = unittest.makeSuite(AddrCacheTestCase,'test')