case_sensitive_localpart option, more delayed bounce heuristics,

optional smart_alias section.
This commit is contained in:
Stuart Gathman
2006-10-01 01:44:06 +00:00
parent 2fe8fa8813
commit 33aeefa19f
5 changed files with 68 additions and 24 deletions
+7 -2
View File
@@ -1,6 +1,11 @@
Here is a history of user visible changes to Python milter. Here is a history of user visible changes to Python milter.
0.8.6 Support CBV timeout
0.8.6 Delay reject of unsigned RCPT for postmaster and abuse only Support fail template, headers in templates
Create GOSSiP record only when connection will procede to DATA.
More SPF lax heuristics
Don't require SPF pass for white/black listing mail from trusted relay.
Support localpart wildcard for white and black lists.
Delay reject of unsigned RCPT for postmaster and abuse only
Fix dsn reporting of hard permerror Fix dsn reporting of hard permerror
Resolve FIXME for wrap_close in miltermodule.c Resolve FIXME for wrap_close in miltermodule.c
Add Message-ID to DSNs Add Message-ID to DSNs
+10 -4
View File
@@ -1,10 +1,13 @@
Reporting explanation for failure should show source of sender
provided explanation.
Reports PROBATION even when rejecting message (works, but confusing in log). Reports PROBATION even when rejecting message (works, but confusing in log).
Bug in Auto-whitelist. Recent Auto-whitelist doesn't override expired entry. Bug in Auto-whitelist. Recent Auto-whitelist doesn't override expired entry.
Delayed_failure detection needs to handle multi-line header fields. Also, DONE Delayed_failure detection needs to handle multi-line header fields.
delayed_failure should be recognized when addressed to postmaster@helodomain Also, delayed_failure should be recognized when addressed to
Idea: load headers into message object, and use header array. postmaster@helodomain
Need to use wildcards in blacklist.log: *.madcowsrecord.net Need to use wildcards in blacklist.log: *.madcowsrecord.net
Need to exclude emails like !*-admin@example.com in whitelist_sender. Need to exclude emails like !*-admin@example.com in whitelist_sender.
@@ -42,7 +45,10 @@ data structure as autowhitelist.log. Add emails blacklisted via CBV
so that they are remembered across milter restarts. so that they are remembered across milter restarts.
Make all dictionaries work like honeypot. Do not train as ham unless Make all dictionaries work like honeypot. Do not train as ham unless
whitelisted. Train on blacklisted messages, or spam feedback. whitelisted. Train on blacklisted messages, or spam feedback. This
can be called Train On Error. Should be possible to startup
with training on everything to get dictionary built fast, then switch
to train on error to minimize labor.
Allow unsigned DSNs from selected domains (that don't accept signed MFROM, Allow unsigned DSNs from selected domains (that don't accept signed MFROM,
e.g. verizon.net). e.g. verizon.net).
+42 -15
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.66 2006/07/26 16:42:26 customdesigned
# Support CBV timeout
#
# Revision 1.65 2006/06/21 22:22:00 customdesigned # Revision 1.65 2006/06/21 22:22:00 customdesigned
# Handle multi-line headers in delayed dsns. # Handle multi-line headers in delayed dsns.
# #
@@ -215,7 +218,7 @@ import mime
import email.Errors import email.Errors
import Milter import Milter
import tempfile import tempfile
import ConfigParser from ConfigParser import ConfigParser
import time import time
import socket import socket
import struct import struct
@@ -254,6 +257,7 @@ ip4re = re.compile(r'^[0-9]*\.[0-9]*\.[0-9]*\.[0-9]*$')
# If found, we blacklist that recipient. # If found, we blacklist that recipient.
subjpats = ( subjpats = (
r'^failure notice', r'^failure notice',
r'^subjectbounce',
r'^returned mail', r'^returned mail',
r'^undeliver', r'^undeliver',
r'^delivery\b.*\bfailure', r'^delivery\b.*\bfailure',
@@ -281,6 +285,7 @@ block_forward = {}
hide_path = () hide_path = ()
log_headers = False log_headers = False
block_chinese = False block_chinese = False
case_sensitive_localpart = False
spam_words = () spam_words = ()
porn_words = () porn_words = ()
banned_exts = mime.extlist.split(',') banned_exts = mime.extlist.split(',')
@@ -328,7 +333,14 @@ milter_log = logging.getLogger('milter')
if gossip: if gossip:
gossip_node = Gossip('gossip4.db',120) gossip_node = Gossip('gossip4.db',120)
class MilterConfigParser(ConfigParser.ConfigParser): class MilterConfigParser(ConfigParser):
def __init__(self,defaults):
ConfigParser.__init__(self)
self.defaults = defaults
def get(self,sect,opt):
return ConfigParser.get(self,sect,opt,vars=self.defaults)
def getlist(self,sect,opt): def getlist(self,sect,opt):
if self.has_option(sect,opt): if self.has_option(sect,opt):
@@ -343,11 +355,11 @@ class MilterConfigParser(ConfigParser.ConfigParser):
for q in s.split(','): for q in s.split(','):
q = q.strip() q = q.strip()
if q.startswith('file:'): if q.startswith('file:'):
domain = q[5:] domain = q[5:].lower()
d[domain] = d.setdefault(domain,[]) + open(domain,'r').read().split() d[domain] = d.setdefault(domain,[]) + open(domain,'r').read().split()
else: else:
user,domain = q.split('@') user,domain = q.split('@')
d.setdefault(domain,[]).append(user) d.setdefault(domain.lower(),[]).append(user)
return d return d
def getaddrdict(self,sect,opt): def getaddrdict(self,sect,opt):
@@ -390,7 +402,8 @@ def read_config(list):
'reject_noptr': 'no', 'reject_noptr': 'no',
'supply_sender': 'no', 'supply_sender': 'no',
'best_guess': 'no', 'best_guess': 'no',
'dspam_internal': 'yes' 'dspam_internal': 'yes',
'case_sensitive_localpart': 'no'
}) })
cp.read(list) cp.read(list)
@@ -398,6 +411,7 @@ def read_config(list):
tempfile.tempdir = cp.get('milter','tempdir') tempfile.tempdir = cp.get('milter','tempdir')
global socketname, timeout, check_user, log_headers global socketname, timeout, check_user, log_headers
global internal_connect, internal_domains, trusted_relay, hello_blacklist global internal_connect, internal_domains, trusted_relay, hello_blacklist
global case_sensitive_localpart
socketname = cp.get('milter','socket') socketname = cp.get('milter','socket')
timeout = cp.getint('milter','timeout') timeout = cp.getint('milter','timeout')
check_user = cp.getaddrset('milter','check_user') check_user = cp.getaddrset('milter','check_user')
@@ -406,6 +420,7 @@ def read_config(list):
internal_domains = cp.getlist('milter','internal_domains') internal_domains = cp.getlist('milter','internal_domains')
trusted_relay = cp.getlist('milter','trusted_relay') trusted_relay = cp.getlist('milter','trusted_relay')
hello_blacklist = cp.getlist('milter','hello_blacklist') hello_blacklist = cp.getlist('milter','hello_blacklist')
case_sensitive_localpart = cp.getboolean('milter','case_sensitive_localpart')
# defang section # defang section
global scan_rfc822, scan_html, block_chinese, scan_zip, block_forward global scan_rfc822, scan_html, block_chinese, scan_zip, block_forward
@@ -439,13 +454,19 @@ def read_config(list):
if wiretap_dest: wiretap_dest = '<%s>' % wiretap_dest if wiretap_dest: wiretap_dest = '<%s>' % wiretap_dest
global smart_alias global smart_alias
for sa in cp.getlist('wiretap','smart_alias'): for sa,v in [
sm = cp.getlist('wiretap',sa) (k,cp.get('wiretap',k)) for k in cp.getlist('wiretap','smart_alias')
] + (cp.has_section('smart_alias') and cp.items('smart_alias',True) or []):
print sa,v
sm = [q.strip() for q in v.split(',')]
if len(sm) < 2: if len(sm) < 2:
milter_log.warning('malformed smart alias: %s',sa) milter_log.warning('malformed smart alias: %s',sa)
continue continue
if len(sm) == 2: sm.append(sa) if len(sm) == 2: sm.append(sa)
key = (sm[0],sm[1]) if case_sensitive_localpart:
key = (sm[0],sm[1])
else:
key = (sm[0].lower(),sm[1].lower())
smart_alias[key] = sm[2:] smart_alias[key] = sm[2:]
# dspam section # dspam section
@@ -829,12 +850,18 @@ class bmsMilter(Milter.Milter):
def smart_alias(self,to): def smart_alias(self,to):
if smart_alias: if smart_alias:
t = parse_addr(to.lower()) if case_sensitive_localpart:
t = parse_addr(to)
else:
t = parse_addr(to.lower())
if len(t) == 2: if len(t) == 2:
ct = '@'.join(t) ct = '@'.join(t)
else: else:
ct = t[0] ct = t[0]
cf = self.canon_from if case_sensitive_localpart:
cf = self.canon_from
else:
cf = self.canon_from.lower()
cf0 = cf.split('@',1) cf0 = cf.split('@',1)
if len(cf0) == 2: if len(cf0) == 2:
cf0 = '@' + cf0[1] cf0 = '@' + cf0[1]
@@ -1582,10 +1609,10 @@ class bmsMilter(Milter.Milter):
if ln[0].isspace() and ln[0] != '\n': if ln[0].isspace() and ln[0] != '\n':
lastln += ln lastln += ln
continue continue
name,val = lastln.rstrip().split(None,1) try:
pos = val.find('<SRS') name,val = lastln.rstrip().split(None,1)
if pos >= 0: pos = val.find('<SRS')
try: if pos >= 0:
sender = srs.reverse(val[pos+1:-1]) sender = srs.reverse(val[pos+1:-1])
cbv_cache[sender] = 500,self.delayed_failure,time.time() cbv_cache[sender] = 500,self.delayed_failure,time.time()
try: try:
@@ -1597,7 +1624,7 @@ class bmsMilter(Milter.Milter):
self.tempname = None self.tempname = None
self.log('BLACKLIST:',sender,fname) self.log('BLACKLIST:',sender,fname)
return Milter.DISCARD return Milter.DISCARD
except: continue except: continue
lnl = ln.lower() lnl = ln.lower()
for k in ('message-id','x-mailer','sender'): for k in ('message-id','x-mailer','sender'):
if lnl.startswith(k): if lnl.startswith(k):
+8 -3
View File
@@ -31,6 +31,9 @@ log_headers = 0
# Reject mail for domains mentioned unless user is mentioned here also # Reject mail for domains mentioned unless user is mentioned here also
;check_user = joe@mycorp.com, mary@mycorp.com, file:bigcorp.com ;check_user = joe@mycorp.com, mary@mycorp.com, file:bigcorp.com
# Treat localparts in milter.cfg as case-insensitive
case_sensitive_localpart = true
# features intended to filter or block incoming mail # features intended to filter or block incoming mail
[defang] [defang]
@@ -125,11 +128,13 @@ blind = 1
# discard outgoing mail without alerting sender # discard outgoing mail without alerting sender
# can be used in conjunction with wiretap to censor outgoing mail # can be used in conjunction with wiretap to censor outgoing mail
;discard_users = canned@bigcorp.com ;discard_users = canned@bigcorp.com
# #
# smart aliases trigger on both sender and recipient # smart aliases trigger on both sender and recipient
# alias = sender, recipient[, destination]
# #
;smart_alias = copycust,walter,spy1,spy2 [smart_alias]
# multiple wiretap monitors # multiple wiretap monitors. Smart aliases are applied after wiretap.
;spy1 = disloyal@bigcorp.com,spy@bigcorp.com ;spy1 = disloyal@bigcorp.com,spy@bigcorp.com
;spy2 = bigmouth@bigcorp.com,spy@bigcorp.com ;spy2 = bigmouth@bigcorp.com,spy@bigcorp.com
# mail from client@clientcorp.com to sue@bigcorp.com is redirected to # mail from client@clientcorp.com to sue@bigcorp.com is redirected to
@@ -142,7 +147,7 @@ blind = 1
;walter1 = cust@othercorp.com,walter@bigcorp.com,boss@bigcorp.com, ;walter1 = cust@othercorp.com,walter@bigcorp.com,boss@bigcorp.com,
; walter@bigcorp.com ; walter@bigcorp.com
;bulk = soruce@telex.com,bob@jsconnor.com ;bulk = soruce@telex.com,bob@jsconnor.com
;bulk = soruce@telex.com,larry@jsconnor.com ;bulk1 = soruce@telex.com,larry@jsconnor.com,bulk
# See http://bmsi.com/python/dspam.html # See http://bmsi.com/python/dspam.html
[dspam] [dspam]
+1
View File
@@ -177,6 +177,7 @@ rm -rf $RPM_BUILD_ROOT
%changelog %changelog
* Tue May 23 2006 Stuart Gathman <stuart@bmsi.com> 0.8.6-2 * Tue May 23 2006 Stuart Gathman <stuart@bmsi.com> 0.8.6-2
- Support CBV timeout
- Support fail template, headers in templates - Support fail template, headers in templates
- Create GOSSiP record only when connection will procede to DATA. - Create GOSSiP record only when connection will procede to DATA.
- More SPF lax heuristics - More SPF lax heuristics