case_sensitive_localpart option, more delayed bounce heuristics,
optional smart_alias section.
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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).
|
||||||
|
|||||||
@@ -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)
|
||||||
|
if case_sensitive_localpart:
|
||||||
key = (sm[0],sm[1])
|
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:
|
||||||
|
if case_sensitive_localpart:
|
||||||
|
t = parse_addr(to)
|
||||||
|
else:
|
||||||
t = parse_addr(to.lower())
|
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]
|
||||||
|
if case_sensitive_localpart:
|
||||||
cf = self.canon_from
|
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
|
||||||
|
try:
|
||||||
name,val = lastln.rstrip().split(None,1)
|
name,val = lastln.rstrip().split(None,1)
|
||||||
pos = val.find('<SRS')
|
pos = val.find('<SRS')
|
||||||
if pos >= 0:
|
if pos >= 0:
|
||||||
try:
|
|
||||||
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:
|
||||||
|
|||||||
+8
-3
@@ -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]
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user