From 9f8cef5ee2666f2226a2ee4bb0e2a6f58d66de8d Mon Sep 17 00:00:00 2001
From: Stuart Gathman <stuart@gathman.org>
Date: Mon, 8 Jan 2007 23:20:54 +0000
Subject: [PATCH] Get user feedback.

---
 Milter/cache.py  |  8 +++++---
 Milter/unsign.py | 17 +++++++++++++++++
 bms.py           | 28 +++++++++++++++++++++++++---
 3 files changed, 47 insertions(+), 6 deletions(-)
 create mode 100644 Milter/unsign.py

diff --git a/Milter/cache.py b/Milter/cache.py
index 8de1fcf..8f9c824 100644
--- a/Milter/cache.py
+++ b/Milter/cache.py
@@ -10,6 +10,9 @@
 # CBV results.
 #
 # $Log$
+# Revision 1.2  2007/01/05 23:33:55  customdesigned
+# Make blacklist an AddrCache
+#
 # Revision 1.1  2007/01/05 21:25:40  customdesigned
 # Move AddrCache to Milter package.
 #
@@ -52,7 +55,7 @@ class AddrCache(object):
   def has_key(self,sender):
     "True if sender is cached and has not expired."
     try:
-      lsender = sender.lower()
+      lsender = sender and sender.lower()
       ts,res = self.cache[lsender]
       too_old = time.time() - self.age*24*60*60	# max age in days
       if not ts or ts > too_old:
@@ -67,8 +70,7 @@ class AddrCache(object):
       try:
 	user,host = sender.split('@',1)
 	return self.has_key(host)
-      except ValueError:
-        pass
+      except: pass
     return False
 
   __contains__ = has_key
diff --git a/Milter/unsign.py b/Milter/unsign.py
new file mode 100644
index 0000000..a363a04
--- /dev/null
+++ b/Milter/unsign.py
@@ -0,0 +1,17 @@
+# Author: Stuart D. Gathman <stuart@bmsi.com>
+# Copyright 2005 Business Management Systems, Inc.
+# This code is under the GNU General Public License.  See COPYING for details.
+
+# The localpart of SMTP return addresses is often signed.  The format
+# of the signing is application specific and doesn't concern us -
+# except that we wish to extract some sort of fixed string from
+# the variable signature which represents the "source" of the message.
+
+def unsign(s):
+  """Attempt to unsign localpart and return original email.
+  No attempt is made to verify the signature.
+  >>> unsign('SRS0=8Y3CZ=3U=jsconnor.com=bills@bmsi.com')
+  'bills@jsconnor.com'
+  """
+  # not implemented yet
+  return s
diff --git a/bms.py b/bms.py
index 3c61d83..f404af7 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.82  2007/01/06 04:21:30  customdesigned
+# Add config file to spfmilter
+#
 # Revision 1.81  2007/01/05 23:33:55  customdesigned
 # Make blacklist an AddrCache
 #
@@ -54,7 +57,6 @@ import Milter
 import tempfile
 import time
 import socket
-import struct
 import re
 import shutil
 import gc
@@ -168,7 +170,7 @@ logging.basicConfig(
 milter_log = logging.getLogger('milter')
 
 if gossip:
-  gossip_node = Gossip('gossip4.db',120)
+  gossip_node = Gossip('gossip4.db',1000)
 
 def read_config(list):
   cp = MilterConfigParser({
@@ -1149,6 +1151,15 @@ class bmsMilter(Milter.Milter):
       if not blind_wiretap:
         self.addheader('Cc',rcpt)
 
+  # 
+  def gossip_header(self):
+    "Set UMIS from GOSSiP header."
+    msg = email.message_from_file(self.fp)
+    gh = msg.get('x-gossip')
+    if gh:
+      self.log('X-GOSSiP:',gh)
+      self.umis,_ = gh.split(',',1)
+
   # check spaminess for recipients in dictionary groups
   # if there are multiple users getting dspammed, then
   # a signature tag for each is added to the message.
@@ -1158,6 +1169,7 @@ class bmsMilter(Milter.Milter):
 
   def check_spam(self):
     "return True/False if self.fp, else return Milter.REJECT/TEMPFAIL/etc"
+    self.screened = False
     if not dspam_userdir: return False
     ds = Dspam.DSpamDirectory(dspam_userdir)
     ds.log = self.log
@@ -1173,9 +1185,11 @@ class bmsMilter(Milter.Milter):
 	    if user == 'spam' and self.internal_connection:
 	      sender = dspam_users.get(self.canon_from)
 	      if sender:
-	        self.log("SPAM: %s" % sender)	# log user for FP
+	        self.log("SPAM: %s" % sender)	# log user for SPAM
 		ds.add_spam(sender,txt)
 		txt = None
+		self.fp.seek(0)
+		self.gossip_header()
 		self.fp = None
 		return Milter.DISCARD
 	    elif user == 'falsepositive' and self.internal_connection:
@@ -1184,6 +1198,7 @@ class bmsMilter(Milter.Milter):
 	        self.log("FP: %s" % sender)	# log user for FP
 	        txt = ds.false_positive(sender,txt)
 		self.fp = StringIO.StringIO(txt)
+		self.gossip_header()
 		self.delrcpt('<%s>' % rcpt)
 		self.recipients = None
 		self.rejectvirus = False
@@ -1287,6 +1302,9 @@ class bmsMilter(Milter.Milter):
 	ds.check_spam(screener,txt,self.recipients,
 		force_result=dspam.DSR_ISINNOCENT)
 	return False
+      # log spam score for screened messages
+      self.add_header("X-DSpam-Score",'%f' % ds.probability)
+      self.screened = True
     return modified
 
   # train late in eom(), after failed CBV
@@ -1453,6 +1471,8 @@ class bmsMilter(Milter.Milter):
       rc = self.send_dsn(q,msg,template_name)
       self.cbv_needed = None
       if rc == Milter.REJECT:
+        if gossip and self.umis:
+	  gossip_node.feedback(self.umis,1)
 	self.train_spam()
 	return Milter.DISCARD
       if rc != Milter.CONTINUE:
@@ -1474,6 +1494,8 @@ class bmsMilter(Milter.Milter):
         fout.close()
       
     if not defanged and not spam_checked:
+      if gossip and self.umis and self.screened:
+	gossip_node.feedback(self.umis,0)
       os.remove(self.tempname)
       self.tempname = None	# prevent re-removal
       self.log("eom")
-- 
GitLab