Send DSN before adding message to quarantine.

This commit is contained in:
Stuart Gathman
2005-08-17 19:35:28 +00:00
parent 241717b0e2
commit 2a4ab4e87c
3 changed files with 81 additions and 54 deletions
+70 -49
View File
@@ -1,6 +1,9 @@
#!/usr/bin/env python
# A simple milter that has grown quite a bit.
# $Log$
# Revision 1.22 2005/08/11 22:17:58 customdesigned
# Consider SMTP AUTH connections internal.
#
# Revision 1.21 2005/08/04 21:21:31 customdesigned
# Treat fail like softfail for selected (braindead) domains.
# Treat mail according to extended processing results, but
@@ -726,8 +729,8 @@ class bmsMilter(Milter.Milter):
q.set_default_explanation(
'SPF fail: see http://openspf.com/why.html?sender=%s&ip=%s' % (q.s,q.i))
res,code,txt = q.check()
q.result = res
if res == 'unknown' and q.perm_error:
q.result = res
self.cbv_needed = q # report SPF syntax error to sender
res,code,txt = q.perm_error.ext # extended (lax processing) result
txt = 'EXT: ' + txt
@@ -774,11 +777,9 @@ class bmsMilter(Milter.Milter):
)
return Milter.REJECT
if self.mailfrom != '<>':
q.result = res
self.cbv_needed = q
if res in ('deny', 'fail'):
if hres == 'pass' and q.o in spf_accept_fail:
q.result = res
self.cbv_needed = q
else:
self.log('REJECT: SPF %s %i %s' % (res,code,txt))
@@ -800,7 +801,6 @@ 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)
@@ -823,6 +823,7 @@ class bmsMilter(Milter.Milter):
self.setreply(str(code),'4.3.0',txt)
return Milter.TEMPFAIL
self.add_header('Received-SPF',q.get_header(res,receiver))
self.spf = q
return Milter.CONTINUE
# hide_path causes a copy of the message to be saved - until we
@@ -1075,6 +1076,7 @@ class bmsMilter(Milter.Milter):
# this will give a fast start to stats
def check_spam(self):
"return True/False if self.fp, else return Milter.REJECT/TEMPFAIL/etc"
if not dspam_userdir: return False
ds = Dspam.DSpamDirectory(dspam_userdir)
ds.log = self.log
@@ -1094,7 +1096,7 @@ class bmsMilter(Milter.Milter):
ds.add_spam(sender,txt)
txt = None
self.fp = None
return False
return Milter.DISCARD
elif user == 'falsepositive' and self.internal_connection:
sender = dspam_users.get(self.canon_from)
if sender:
@@ -1111,16 +1113,23 @@ class bmsMilter(Milter.Milter):
return False
if user == 'honeypot' and Dspam.VERSION >= '1.1.9':
keep = False # keep honeypot mail
self.fp = None
if len(self.recipients) > 1:
self.log("HONEYPOT:",rcpt,'SCREENED')
if self.spf:
# check that sender accepts quarantine DSN
msg = mime.message_from_file(StringIO.StringIO(txt))
rc = self.send_dsn(self.spf,msg,'quarantine.txt')
del msg
if rc != Milter.CONTINUE:
return rc
ds.check_spam(user,txt,self.recipients,quarantine=True,
force_result=dspam.DSR_ISSPAM)
self.log("HONEYPOT:",rcpt,'SCREENED')
else:
ds.check_spam(user,txt,self.recipients,quarantine=keep,
force_result=dspam.DSR_ISSPAM)
self.log("HONEYPOT:",rcpt)
self.fp = None
return False
return Milter.DISCARD
txt = ds.check_spam(user,txt,self.recipients)
if not txt:
# DISCARD if quarrantined for any recipient. It
@@ -1128,7 +1137,7 @@ class bmsMilter(Milter.Milter):
# as a false positive.
self.log("DSPAM:",user,rcpt)
self.fp = None
return False
return Milter.DISCARD
self.fp = StringIO.StringIO(txt)
modified = True
except Exception,x:
@@ -1143,18 +1152,25 @@ class bmsMilter(Milter.Milter):
self.log("Large message:",len(txt))
return False
screener = dspam_screener[self.id % len(dspam_screener)]
# FIXME: if screener is 'honeypot', classify with no quarantine.
# If spam, send DSN and reject if not accepted. Otherwise, use
# force_result to quarantine.
if not ds.check_spam(screener,txt,self.recipients,
classify=True,quarantine=not self.reject_spam):
classify=True,quarantine=False):
self.fp = None
if self.reject_spam:
self.log("DSPAM:",screener,
'REJECT: X-DSpam-Score: %f' % ds.probability)
self.setreply('550','5.7.1','Your Message looks spammy')
return True
return Milter.REJECT
self.log("DSPAM:",screener,"SCREENED")
if self.spf:
# check that sender accepts quarantine DSN
msg = mime.message_from_file(StringIO.StringIO(txt))
rc = self.send_dsn(self.spf,msg,'quarantine.txt')
del msg
if rc != Milter.CONTINUE:
return rc
ds.check_spam(screener,txt,self.recipients,quarantine=True,
force_result=dspam.DSR_ISSPAM)
return Milter.DISCARD
return modified
def eom(self):
@@ -1165,8 +1181,7 @@ class bmsMilter(Milter.Milter):
# analyze external mail for spam
spam_checked = self.check_spam() # tag or quarantine for spam
if not self.fp:
if spam_checked: return Milter.REJECT
return Milter.DISCARD # message quarantined for all recipients
return spam_checked
# analyze all mail for dangerous attachments and scripts
self.fp.seek(0)
@@ -1233,41 +1248,15 @@ class bmsMilter(Milter.Milter):
if self.cbv_needed:
q = self.cbv_needed
sender = q.s
cached = cbv_cache.has_key(sender)
if cached:
self.log('CBV:',sender,'(cached)')
res = cbv_cache[sender]
if q.result in ('softfail','fail','deny'):
template_name = 'softfail.txt'
elif q.result == 'unknown':
template_name = 'permerror.txt'
else:
self.log('CBV:',sender)
try:
if q.result in ('softfail','fail','deny'):
template = file('softfail.txt').read()
elif q.result == 'unknown':
template = file('permerror.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)
if res:
desc = "CBV: %d %s" % res[:2]
if 400 <= res[0] < 500:
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())
return Milter.REJECT
cbv_cache[sender] = res
if not cached:
s = time.strftime(time_format,time.localtime())
print >>open('send_dsn.log','a'),sender,s # log who we sent DSNs to
template_name = 'strike3.txt'
rc = self.send_dsn(q,msg,template_name)
self.cbv_needed = None
if rc != Milter.CONTINUE: return rc
if not defanged and not spam_checked:
os.remove(self.tempname)
@@ -1301,6 +1290,38 @@ class bmsMilter(Milter.Milter):
out.close()
return Milter.TEMPFAIL
def send_dsn(self,q,msg,template_name):
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)
try:
template = file(template_name).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)
if res:
desc = "CBV: %d %s" % res[:2]
if 400 <= res[0] < 500:
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())
return Milter.REJECT
cbv_cache[sender] = res
if not cached:
s = time.strftime(time_format,time.localtime())
print >>open('send_dsn.log','a'),sender,s # log who we sent DSNs to
return Milter.CONTINUE
def close(self):
sys.stdout.flush() # make log messages visible
if self.tempname: