diff --git a/NEWS b/NEWS
index 5482ebda0a8d76eb3740f20904534693d12c994e..a48e1b0f8b4ede007e10ab8a6b389c996b2d627f 100644
--- a/NEWS
+++ b/NEWS
@@ -1,4 +1,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
 0.8.7	Move spf module to pyspf
 	Prevent PTR cache poisoning
 	More lame bounce heuristics
diff --git a/README b/README
index 5df6c93b2b86ecbac9809630daf0d0cb2a8f53e2..5c48f0a8faf171d788a552088004d4e4ecbd9d00 100644
--- a/README
+++ b/README
@@ -51,8 +51,9 @@ Xpythonfilter,        S=local:/home/username/pythonsock
 Note that milters should almost certainly not run as root.
 
 That's it.  Incoming mail will cause the milter to print some things, and
-some email will be rejected (see the "header" method).  Edit and play.  See
-bms.py for an example milter used in production.
+some email will be rejected (see the "header" method).  Edit and play.  
+See spfmilter.py for a functional SPF milter, or see bms.py for an complex
+milter used in production.
 
 
 Not-so-quick Installation
diff --git a/TODO b/TODO
index 29e09b6853c04f8bcebb9d098da3eb5b3eb7ed09..ee00b482c13014a4d799a00da198e238223882ed 100644
--- a/TODO
+++ b/TODO
@@ -1,12 +1,8 @@
+Need to use wildcards in blacklist.log: *.madcowsrecord.net
+Need to exclude emails like !*-admin@example.com in whitelist_sender.
 Need to exclude robot users from autowhitelist.  Don't want to have to
 list all users, so implement something like !*-admin@bmsi.com,@bmsi.com.
 
-Milter won't start when a whitelist/blacklist file is missing.
-
-Milter won't start when it can't change permissions on *.lock to match
-*.log.  Should maybe ignore that error - the effect will be to set
-the permissions to default.
-
 GOSSiP feedback from user training is ignored because UMIS has already been
 removed from queue.  Maybe keep UMIS in queue, and add method to 
 alter last feedback for ID.
@@ -15,27 +11,17 @@ Generate DSNs according to RFC 3464
 
 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 if sender
 provided explanation.
 
 Bug in Auto-whitelist.  Recent Auto-whitelist doesn't override expired entry.
 
-Need to use wildcards in blacklist.log: *.madcowsrecord.net
-Need to exclude emails like !*-admin@example.com in whitelist_sender.
-
 SPF permerror diagnostics should include corrected mechanism.
 
 Delay SPF check until RCPT TO.  Cache result to avoid repeating
 for multiple RCPT.  This avoids overhead for invalid RCPT, and
 allows for per RCPT local policy.
 
-Add auto-blacklisted senders to blacklist.log with timestamp.
-
-Received-SPF header field should show identity that was checked.
-
 Check SPF for outgoing mail (including local policy for internal addresses).
 This could also solve the second part of the mail from relay problem below.
 
@@ -47,6 +33,7 @@ For selected domains, check rcpts via CBV before accepting mail.  Cache
 results.  This will kick out dictonary attacks against a mail domain
 behind a gateway sooner.
 
+Add auto-blacklisted senders to blacklist.log with timestamp.
 Add emails blacklisted via CBV so that they are remembered across milter
 restarts.
 
@@ -59,9 +46,6 @@ to train on error to minimize labor.
 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.
-
 Allow verified hostnames for trusted_relay.  E.g. HELO name that
 passes SPF.
 
@@ -86,11 +70,9 @@ wildcard (e.g. empty localpart).
 Quarantined mail is missing headers modified/added by milter after
 checking dspam.
 
-Require signed MFROM for all incoming bounces when signing all outgoing mail -
-except from trusted relays.
-
 Send DSN for permerror before processing extended result.  An additional
-DSN may be sent based on extended result.
+DSN may be sent based on extended result.  Send permerror DSN to
+postmaster@sending_domain.
 
 Rescind whitelist for banned extensions, in case sender is infected.
 
@@ -104,9 +86,6 @@ SPF-Neutral:aol.com	ERROR:"550 AOL mail must get SPF PASS"
 Defer TEMPERROR in SPF evaluation - give precedence to security
 (only defer for PASS mechanisms).
 
-Option to add Received-SPF header, but never reject on SPF.
-  I think the above will handle this.
-
 Create null config that does nothing - except maybe add Received-SPF
 headers.  Many admins would like to turn features on one at a time.
 
@@ -153,6 +132,26 @@ Need a test module to feed sample messages to a milter though a live
 sendmail and SMTP.  The mockup currently used is probably not very accurate,
 and doesn't test the threading code.
 
+DONE Require signed MFROM for all incoming bounces when signing all outgoing
+mail - except from trusted relays.
+
+DONE Added Message-ID header to DSN with SRS signed sender.  When seen on
+incoming rfc ignorant failure message, blacklist sender.
+
+DONE Option to add Received-SPF header, but never reject on SPF.
+  I think the above will handle this.
+
+DONE Received-SPF header field should show identity that was checked.
+
+DONE When training with spam, REJECT after data so that mistakenly blacklisted
+senders at least get an error.
+
+DONE Milter won't start when it can't change permissions on *.lock to match
+*.log.  Should maybe ignore that error - the effect will be to set
+the permissions to default.
+
+DONE Milter won't start when a whitelist/blacklist file is missing.
+
 DONE Delayed failure detection should parse From header to find email address.
 
 DONE When bms.py can't find templates, it passes None to dsn.create_msg(),
diff --git a/bms.py b/bms.py
index 738999e88bf313bd8e52a0cf79521f92b9176f00..038624fd40948625527b1c58e12e72b4d9027252 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.89  2007/01/22 02:46:01  customdesigned
+# Convert tabs to spaces.
+#
 # Revision 1.88  2007/01/19 23:31:38  customdesigned
 # Move parse_header to Milter.utils.
 # Test case for delayed DSN parsing.
@@ -157,6 +160,7 @@ scan_html = True
 scan_rfc822 = True
 internal_connect = ()
 trusted_relay = ()
+private_relay = ()
 trusted_forwarder = ()
 internal_domains = ()
 banned_users = ()
@@ -222,7 +226,7 @@ def read_config(list):
   tempfile.tempdir = cp.get('milter','tempdir')
   global socketname, timeout, check_user, log_headers
   global internal_connect, internal_domains, trusted_relay, hello_blacklist
-  global case_sensitive_localpart
+  global case_sensitive_localpart, private_relay
   socketname = cp.get('milter','socket')
   timeout = cp.getint('milter','timeout')
   check_user = cp.getaddrset('milter','check_user')
@@ -230,6 +234,7 @@ def read_config(list):
   internal_connect = cp.getlist('milter','internal_connect')
   internal_domains = cp.getlist('milter','internal_domains')
   trusted_relay = cp.getlist('milter','trusted_relay')
+  private_relay = cp.getlist('milter','private_relay')
   hello_blacklist = cp.getlist('milter','hello_blacklist')
   case_sensitive_localpart = cp.getboolean('milter','case_sensitive_localpart')
 
@@ -898,6 +903,10 @@ class bmsMilter(Milter.Milter):
               return self.forged_bounce()
             self.data_allowed = not srs_reject_spoofed
 
+      if not self.internal_connection and domain in private_relay:
+        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)
diff --git a/milter.cfg b/milter.cfg
index 0553fb9ca704c8c408bd0fa9bb4bfc2c27f77d08..a986e43f221f3b4186d4d925b891264e345901df 100644
--- a/milter.cfg
+++ b/milter.cfg
@@ -23,6 +23,9 @@ log_headers = 0
 # SPF checks are bypassed for internal connections and trusted relays.
 ;trusted_relay = 1.2.3.4, 66.12.34.56
 
+# Relaying to these domains is allowed from internal connections only.
+;private_relay = mycorp.com
+
 # Reject external senders with hello names no legit external sender would use.
 # SPF will do this also, but listing your own domain and mailserver here
 # will save some DNS lookups when rejecting certain viruses.