diff --git a/Milter/dsn.py b/Milter/dsn.py
index 18656895d04005c03fd72be7a089564ca88f11af..c66e13068e79f988e3443e906ab6e9b455ddb1bf 100644
--- a/Milter/dsn.py
+++ b/Milter/dsn.py
@@ -3,7 +3,9 @@ import spf
 import socket
 from email.Message import Message
 
-nospf_msg = """This is an automatically generated Delivery Status Notification.
+nospf_msg = """Subject: Critical mail server configuration error
+
+This is an automatically generated Delivery Status Notification.
 
 THIS IS A WARNING MESSAGE ONLY.
 
@@ -65,11 +67,12 @@ If you need further assistance, please do not hesitate to
 contact me again.
 
 Kind regards,
-Stuart D. Gathman
+
 postmaster@%(receiver)s
 """
 
-softfail_msg = """
+softfail_msg = """Subject: SPF softfail (POSSIBLE FORGERY)
+
 This is an automatically generated Delivery Status Notification.
 
 THIS IS A WARNING MESSAGE ONLY.
@@ -131,7 +134,8 @@ def send_dsn(mailfrom,receiver,msg=None):
     smtp.close()
   return (450,'No MX servers available')	# temp error
 
-def create_msg(q,rcptlist,origmsg):
+def create_msg(q,rcptlist,origmsg=None,template=None):
+  "Create a DSN message from a template.  Template must be '\n' separated."
   heloname = q.h
   sender = q.s
   connectip = q.i
@@ -145,24 +149,30 @@ def create_msg(q,rcptlist,origmsg):
     if not spf_result.startswith('softfail'):
       spf_result = None
   except: spf_result = None
+
   msg = Message()
+
   msg.add_header('To',sender)
   msg.add_header('From','postmaster@%s'%receiver)
   msg.add_header('Auto-Submitted','auto-generated (configuration error)')
   msg.set_type('text/plain')
-  if spf_result:
-    msg.add_header('Subject','SPF softfail (POSSIBLE FORGERY)')
-    msg.set_payload(softfail_msg % locals())
-  else:
-    msg.add_header('Subject','Critical mail server configuration error')
-    msg.set_payload(nospf_msg % locals())
+
+  if not template:
+    if spf_result: template = softfail_msg
+    else: template = nospf_msg
+  hdrs,body = template.split('\n',1)
+  for ln in hdrs.splitlines():
+    name,val = ln.split(':',1)
+    msg.add_header(name,(val % locals()).strip())
+  msg.set_payload(body % locals())
+
   return msg
 
 if __name__ == '__main__':
   q = spf.query('192.168.9.50',
   'SRS0=pmeHL=RH=bmsi.com=stuart@bmsi.com',
   'bmsred.bmsi.com',receiver='mail.bmsi.com')
-  msg = create_msg(q,'charlie@jsconnor.com')
-  #print msg.as_string()
+  msg = create_msg(q,['charlie@jsconnor.com'],None,None)
+  print msg.as_string()
   # print send_dsn(f,msg.as_string())
   print send_dsn(q.s,'mail.bmsi.com',msg.as_string())
diff --git a/NEWS b/NEWS
index 5ec27710ac50685ef3835d1502f1c7c2f11c218f..13f961fdb75886ac168932d9f8fedbe5c7a1b6cb 100644
--- a/NEWS
+++ b/NEWS
@@ -1,5 +1,10 @@
 Here is a history of user visible changes to Python milter.
 
+0.8.0	Move Milter module to subpackage.
+	DSN support for Three strikes rule and SPF SOFTFAIL
+	Move /*mime*/ and dynip to Milter subpackage
+	Fix SPF unknown mechanism list not cleared
+0.7.3	Experimental release with python2.4 support
 0.7.2	Return unknown for invalid ip address in mechanism
 	Recognize dynamic PTR names, and don't count them as authentication.
 	Three strikes and yer out rule.
diff --git a/bms.py b/bms.py
index 946056bcfe01f2c00cb06fe5a4b302eb72d8e952..a526e2ef7f826b9dbc87745711edb7efb94a0894 100644
--- a/bms.py
+++ b/bms.py
@@ -1,6 +1,7 @@
 #!/usr/bin/env python
-# A simple milter.
+# A simple milter that has grown quite a bit.
 # $Log$
+#
 # Revision 1.134  2005/05/25 15:36:43  stuart
 # Use dynip module.
 # Support smart aliasing of wiretap destination.
@@ -252,6 +253,7 @@ spf_reject_neutral = ()
 spf_accept_softfail = ()
 spf_best_guess = False
 spf_reject_noptr = False
+multiple_bounce_recipients = True
 timeout = 600
 cbv_cache = {}
 try:
@@ -264,7 +266,7 @@ class MilterConfigParser(ConfigParser.ConfigParser):
   def getlist(self,sect,opt):
     if self.has_option(sect,opt):
       return [q.strip() for q in self.get(sect,opt).split(',')]
-    return ()
+    return []
 
   def getaddrset(self,sect,opt):
     if not self.has_option(sect,opt):
@@ -637,6 +639,7 @@ class bmsMilter(Milter.Milter):
 	  )
 	  return Milter.REJECT
         if self.mailfrom != '<>':
+	  q.result = res
 	  self.cbv_needed = q
     if res in ('deny', 'fail'):
       self.log('REJECT: SPF %s %i %s' % (res,code,txt))
@@ -658,6 +661,7 @@ class bmsMilter(Milter.Milter):
 	  )
 	  return Milter.REJECT
       if self.mailfrom != '<>':
+        q.result = res
 	self.cbv_needed = q
     if res == 'neutral' and q.o in spf_reject_neutral:
       self.log('REJECT: SPF neutral for',q.s)
@@ -695,7 +699,7 @@ class bmsMilter(Milter.Milter):
       user,domain = t
       if self.mailfrom == '<>' or self.canon_from.startswith('postmaster@') \
       	or self.canon_from.startswith('mailer-daemon@'):
-        if self.recipients:
+        if self.recipients and not multiple_bounce_recipients:
 	  self.data_allowed = False
         if srs and domain == srs_fwdomain:
 	  oldaddr = '@'.join(parse_addr(to))
@@ -843,8 +847,9 @@ class bmsMilter(Milter.Milter):
     # copy headers to a temp file for scanning the body
     headers = self.fp.getvalue()
     self.fp.close()
-    self.tempname = fname = tempfile.mktemp(".defang")
-    self.fp = open(fname,"w+b")
+    fd,fname = tempfile.mkstemp(".defang")
+    self.tempname = fname
+    self.fp = os.fdopen(fd,"w+b")
     self.fp.write(headers)	# IOError (e.g. disk full) causes TEMPFAIL
     # check if headers are really spammy
     if dspam_dict and not self.internal_connection:
@@ -1048,14 +1053,21 @@ class bmsMilter(Milter.Milter):
       self.addheader(name,val)
 
     if self.cbv_needed:
-      sender = self.cbv_needed.s
+      q = self.cbv_needed
+      sender = q.s
       cached = cbv_cache.has_key(sender)
       if cached:
 	self.log('CBV:',sender,'(cached)')
         res = cbv_cache[sender]
       else:
 	self.log('CBV:',sender)
-	m = dsn.create_msg(self.cbv_needed,self.recipients,msg)
+	try:
+	  if q.result == 'softfail':
+	    template = file('softfail.txt').read()
+	  else:
+	    template = file('strike3.txt').read()
+	except IOError: template = None
+	m = dsn.create_msg(q,self.recipients,msg,template)
         m = m.as_string()
         print >>open('last_dsn','w'),m
 	res = dsn.send_dsn(sender,self.receiver,m)
diff --git a/milter.cfg b/milter.cfg
index 43f0e53a45121536519db89991a202b0d4eec714..6204f57e5e7598c5bd8b8e19d7567411ee1ea609 100644
--- a/milter.cfg
+++ b/milter.cfg
@@ -104,6 +104,8 @@ blind = 1
 # additional copies can be added
 ;walter1 = cust@othercorp.com,walter@bigcorp.com,boss@bigcorp.com,
 ;	walter@bigcorp.com
+;bulk = soruce@telex.com,bob@jsconnor.com
+;bulk = soruce@telex.com,larry@jsconnor.com
 
 # See http://bmsi.com/python/dspam.html
 [dspam]
diff --git a/milter.spec b/milter.spec
index f71ef4a7168dca993095a823f1eebc622833ef8a..354c588eba96824a75c7552bddf11352249b9305 100644
--- a/milter.spec
+++ b/milter.spec
@@ -1,10 +1,25 @@
 %define name milter
 %define version 0.8.0
-%define release 2.EL3
-# Redhat 7.x and earlier (multiple ps lines per thread)
-#define sysvinit milter.rc7
-# RH9, other systems (single ps line per process)
+%define release 3.RH7
+# what version of RH are we building for?
+%define redhat9 0	# and Enterprise Linux
+%define redhat7 1
+%define redhat6 0
+
+# Options for Redhat version 6.x:
+# rpm -ba|--rebuild --define "rh6 1"
+%{?rh6:%define redhat7 0}
+%{?rh6:%define redhat6 1}
+
+# some systems dont have initrddir defined
+%{?_initrddir:%define _initrddir /etc/rc.d/init.d}
+
+%if %{redhat9}
 %define sysvinit milter.rc
+%else	# Redhat 7.x and earlier (multiple ps lines per thread)
+%define sysvinit milter.rc7
+%endif
+# RH9, other systems (single ps line per process)
 %ifos Linux
 %define python python2.4
 %else
@@ -25,7 +40,7 @@ Vendor: Stuart D. Gathman <stuart@bmsi.com>
 Packager: Stuart D. Gathman <stuart@bmsi.com>
 Url: http://www.bmsi.com/python/milter.html
 Requires: %{python} >= 2.4, sendmail >= 8.12.10
-%ifnos aix4.1
+%ifos Linux
 Requires: chkconfig
 %endif
 BuildRequires: %{python}-devel >= 2.2.2, sendmail-devel >= 8.12.10
@@ -149,8 +164,13 @@ rm -rf $RPM_BUILD_ROOT
 /usr/share/sendmail-cf/hack/rhsbl.m4
 
 %changelog
+* Wed May 25 2005 Stuart Gathman <stuart@bmsi.com> 0.8.0-1
+- Move Milter module to subpackage.
+- DSN support for Three strikes rule and SPF SOFTFAIL
+- Move /*mime*/ and dynip to Milter subpackage
+- Fix SPF unknown mechanism list not cleared
 * Tue Feb 08 2005 Stuart Gathman <stuart@bmsi.com> 0.7.3-1.EL3
-- Compile for EL3 and Python4
+- Support EL3 and Python2.4 (some scanning/defang support broken)
 * Mon Aug 30 2004 Stuart Gathman <stuart@bmsi.com> 0.7.2-1
 - Fix various SPF bugs
 - Recognize dynamic PTR names, and don't count them as authentication.
diff --git a/setup.cfg b/setup.cfg
index 1b9f4f1e13221805f8b60f797b0a2004c6cecea1..7503af5b7bb87080df10978e80a603dde6bbad21 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -2,3 +2,4 @@
 python=python2
 doc_files=README NEWS TODO
 packager=Stuart D. Gathman <stuart@bmsi.com>
+release=2.4
diff --git a/softfail.txt b/softfail.txt
new file mode 100644
index 0000000000000000000000000000000000000000..263cecf6424eadd2ecfad7ecc3a15bfe1c938d9d
--- /dev/null
+++ b/softfail.txt
@@ -0,0 +1,23 @@
+Subject: SPF softfail (POSSIBLE FORGERY)
+
+This is an automatically generated Delivery Status Notification.
+
+THIS IS A WARNING MESSAGE ONLY.
+
+YOU DO *NOT* NEED TO RESEND YOUR MESSAGE.
+
+Delivery to the following recipients has been delayed.
+
+       %(rcpt)s
+
+Subject: %(subject)s
+Received-SPF: %(spf_result)s
+
+Your sender policy indicated that the above email was likely forged and that
+feedback was desired.
+
+If you need further assistance, please do not hesitate to contact me.
+
+Kind regards,
+
+postmaster@%(receiver)s
diff --git a/strike3.txt b/strike3.txt
new file mode 100644
index 0000000000000000000000000000000000000000..f76416c407d89e0c9d2eb8db502e71771ccc3354
--- /dev/null
+++ b/strike3.txt
@@ -0,0 +1,66 @@
+Subject: Critical mail server configuration error
+
+This is an automatically generated Delivery Status Notification.
+
+THIS IS A WARNING MESSAGE ONLY.
+
+YOU DO *NOT* NEED TO RESEND YOUR MESSAGE.
+
+Delivery to the following recipients has been delayed.
+
+	%(rcpt)s
+
+Subject: %(subject)s 
+
+Someone at IP address %(connectip)s sent an email claiming
+to be from %(sender)s.  
+
+If that wasn't you, then your domain, %(sender_domain)s,
+was forged - i.e. used without your knowlege or authorization by
+someone attempting to steal your mail identity.  This is a very
+serious problem, and you need to provide authentication for your
+SMTP (email) servers to prevent criminals from forging your
+domain.  The simplest step is usually to publish an SPF record
+with your Sender Policy.  
+
+For more information, see: http://spfhelp.net
+
+I hate to annoy you with a DSN (Delivery Status
+Notification) from a possibly forged email, but since you
+have not published a sender policy, there is no other way
+of bringing this to your attention.
+
+If it *was* you that sent the email, then your email domain
+or configuration is in error.  If you don't know anything
+about mail servers, then pass this on to your SMTP (mail)
+server administrator.  We have accepted the email anyway, in
+case it is important, but we couldn't find anything about
+the mail submitter at %(connectip)s to distinguish it from a
+zombie (compromised/infected computer - usually a Windows
+PC).  There was no PTR record for its IP address (PTR names
+that contain the IP address don't count).  RFC2821 requires
+that your hello name be a FQN (Fully Qualified domain Name,
+i.e. at least one dot) that resolves to the IP address of
+the mail sender.  In addition, just like for PTR, we don't
+accept a helo name that contains the IP, since this doesn't
+help to identify you.  The hello name you used,
+%(heloname)s, was invalid.
+
+Furthermore, there was no SPF record for the sending domain
+%(sender_domain)s.  We even tried to find its IP in any A or
+MX records for your domain, but that failed also.  We really
+should reject mail from anonymous mail clients, but in case
+it is important, we are accepting it anyway.
+
+We are sending you this message to alert you to the fact that
+
+Either - Someone is forging your domain.
+Or - You have problems with your email configuration.
+Or - Possibly both.
+
+If you need further assistance, please do not hesitate to
+contact me again.
+
+Kind regards,
+
+postmaster@%(receiver)s