Get user feedback.

This commit is contained in:
Stuart Gathman
2007-01-08 23:20:54 +00:00
parent 4b0e7b22da
commit 9f8cef5ee2
3 changed files with 47 additions and 6 deletions
+5 -3
View File
@@ -10,6 +10,9 @@
# CBV results. # CBV results.
# #
# $Log$ # $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 # Revision 1.1 2007/01/05 21:25:40 customdesigned
# Move AddrCache to Milter package. # Move AddrCache to Milter package.
# #
@@ -52,7 +55,7 @@ class AddrCache(object):
def has_key(self,sender): def has_key(self,sender):
"True if sender is cached and has not expired." "True if sender is cached and has not expired."
try: try:
lsender = sender.lower() lsender = sender and sender.lower()
ts,res = self.cache[lsender] ts,res = self.cache[lsender]
too_old = time.time() - self.age*24*60*60 # max age in days too_old = time.time() - self.age*24*60*60 # max age in days
if not ts or ts > too_old: if not ts or ts > too_old:
@@ -67,8 +70,7 @@ class AddrCache(object):
try: try:
user,host = sender.split('@',1) user,host = sender.split('@',1)
return self.has_key(host) return self.has_key(host)
except ValueError: except: pass
pass
return False return False
__contains__ = has_key __contains__ = has_key
+17
View File
@@ -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
+25 -3
View File
@@ -1,6 +1,9 @@
#!/usr/bin/env python #!/usr/bin/env python
# A simple milter that has grown quite a bit. # A simple milter that has grown quite a bit.
# $Log$ # $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 # Revision 1.81 2007/01/05 23:33:55 customdesigned
# Make blacklist an AddrCache # Make blacklist an AddrCache
# #
@@ -54,7 +57,6 @@ import Milter
import tempfile import tempfile
import time import time
import socket import socket
import struct
import re import re
import shutil import shutil
import gc import gc
@@ -168,7 +170,7 @@ logging.basicConfig(
milter_log = logging.getLogger('milter') milter_log = logging.getLogger('milter')
if gossip: if gossip:
gossip_node = Gossip('gossip4.db',120) gossip_node = Gossip('gossip4.db',1000)
def read_config(list): def read_config(list):
cp = MilterConfigParser({ cp = MilterConfigParser({
@@ -1149,6 +1151,15 @@ class bmsMilter(Milter.Milter):
if not blind_wiretap: if not blind_wiretap:
self.addheader('Cc',rcpt) 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 # check spaminess for recipients in dictionary groups
# if there are multiple users getting dspammed, then # if there are multiple users getting dspammed, then
# a signature tag for each is added to the message. # a signature tag for each is added to the message.
@@ -1158,6 +1169,7 @@ class bmsMilter(Milter.Milter):
def check_spam(self): def check_spam(self):
"return True/False if self.fp, else return Milter.REJECT/TEMPFAIL/etc" "return True/False if self.fp, else return Milter.REJECT/TEMPFAIL/etc"
self.screened = False
if not dspam_userdir: return False if not dspam_userdir: return False
ds = Dspam.DSpamDirectory(dspam_userdir) ds = Dspam.DSpamDirectory(dspam_userdir)
ds.log = self.log ds.log = self.log
@@ -1173,9 +1185,11 @@ class bmsMilter(Milter.Milter):
if user == 'spam' and self.internal_connection: if user == 'spam' and self.internal_connection:
sender = dspam_users.get(self.canon_from) sender = dspam_users.get(self.canon_from)
if sender: 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) ds.add_spam(sender,txt)
txt = None txt = None
self.fp.seek(0)
self.gossip_header()
self.fp = None self.fp = None
return Milter.DISCARD return Milter.DISCARD
elif user == 'falsepositive' and self.internal_connection: elif user == 'falsepositive' and self.internal_connection:
@@ -1184,6 +1198,7 @@ class bmsMilter(Milter.Milter):
self.log("FP: %s" % sender) # log user for FP self.log("FP: %s" % sender) # log user for FP
txt = ds.false_positive(sender,txt) txt = ds.false_positive(sender,txt)
self.fp = StringIO.StringIO(txt) self.fp = StringIO.StringIO(txt)
self.gossip_header()
self.delrcpt('<%s>' % rcpt) self.delrcpt('<%s>' % rcpt)
self.recipients = None self.recipients = None
self.rejectvirus = False self.rejectvirus = False
@@ -1287,6 +1302,9 @@ class bmsMilter(Milter.Milter):
ds.check_spam(screener,txt,self.recipients, ds.check_spam(screener,txt,self.recipients,
force_result=dspam.DSR_ISINNOCENT) force_result=dspam.DSR_ISINNOCENT)
return False return False
# log spam score for screened messages
self.add_header("X-DSpam-Score",'%f' % ds.probability)
self.screened = True
return modified return modified
# train late in eom(), after failed CBV # train late in eom(), after failed CBV
@@ -1453,6 +1471,8 @@ class bmsMilter(Milter.Milter):
rc = self.send_dsn(q,msg,template_name) rc = self.send_dsn(q,msg,template_name)
self.cbv_needed = None self.cbv_needed = None
if rc == Milter.REJECT: if rc == Milter.REJECT:
if gossip and self.umis:
gossip_node.feedback(self.umis,1)
self.train_spam() self.train_spam()
return Milter.DISCARD return Milter.DISCARD
if rc != Milter.CONTINUE: if rc != Milter.CONTINUE:
@@ -1474,6 +1494,8 @@ class bmsMilter(Milter.Milter):
fout.close() fout.close()
if not defanged and not spam_checked: 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) os.remove(self.tempname)
self.tempname = None # prevent re-removal self.tempname = None # prevent re-removal
self.log("eom") self.log("eom")