diff --git a/NEWS b/NEWS index a994cbf71e5513792e5c7bbf683c277696c1af97..f55d63daae185dba060aa067eea50114b6292cb7 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,11 @@ Here is a history of user visible changes to Python milter. - -0.8.6 Delay reject of unsigned RCPT for postmaster and abuse only +0.8.6 Support CBV timeout + 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 Resolve FIXME for wrap_close in miltermodule.c Add Message-ID to DSNs diff --git a/TODO b/TODO index e29d33e750cb413a8d1c55786742720195313df2..51dd3decc6b0c40b76f7a0e8bcb10eda934042d5 100644 --- a/TODO +++ b/TODO @@ -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). Bug in Auto-whitelist. Recent Auto-whitelist doesn't override expired entry. -Delayed_failure detection needs to handle multi-line header fields. Also, -delayed_failure should be recognized when addressed to postmaster@helodomain -Idea: load headers into message object, and use header array. +DONE Delayed_failure detection needs to handle multi-line header fields. +Also, delayed_failure should be recognized when addressed to +postmaster@helodomain Need to use wildcards in blacklist.log: *.madcowsrecord.net 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. 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, e.g. verizon.net). diff --git a/bms.py b/bms.py index 64ce7a15c6f3273c842c08e6928882144a5c4558..d349ba84b6d085e3ab73ba80321b832a98fc56ed 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.66 2006/07/26 16:42:26 customdesigned +# Support CBV timeout +# # Revision 1.65 2006/06/21 22:22:00 customdesigned # Handle multi-line headers in delayed dsns. # @@ -215,7 +218,7 @@ import mime import email.Errors import Milter import tempfile -import ConfigParser +from ConfigParser import ConfigParser import time import socket import struct @@ -254,6 +257,7 @@ ip4re = re.compile(r'^[0-9]*\.[0-9]*\.[0-9]*\.[0-9]*$') # If found, we blacklist that recipient. subjpats = ( r'^failure notice', + r'^subjectbounce', r'^returned mail', r'^undeliver', r'^delivery\b.*\bfailure', @@ -281,6 +285,7 @@ block_forward = {} hide_path = () log_headers = False block_chinese = False +case_sensitive_localpart = False spam_words = () porn_words = () banned_exts = mime.extlist.split(',') @@ -328,8 +333,15 @@ milter_log = logging.getLogger('milter') if gossip: 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): if self.has_option(sect,opt): return [q.strip() for q in self.get(sect,opt).split(',')] @@ -343,11 +355,11 @@ class MilterConfigParser(ConfigParser.ConfigParser): for q in s.split(','): q = q.strip() if q.startswith('file:'): - domain = q[5:] + domain = q[5:].lower() d[domain] = d.setdefault(domain,[]) + open(domain,'r').read().split() else: user,domain = q.split('@') - d.setdefault(domain,[]).append(user) + d.setdefault(domain.lower(),[]).append(user) return d def getaddrdict(self,sect,opt): @@ -390,7 +402,8 @@ def read_config(list): 'reject_noptr': 'no', 'supply_sender': 'no', 'best_guess': 'no', - 'dspam_internal': 'yes' + 'dspam_internal': 'yes', + 'case_sensitive_localpart': 'no' }) cp.read(list) @@ -398,6 +411,7 @@ def read_config(list): tempfile.tempdir = cp.get('milter','tempdir') global socketname, timeout, check_user, log_headers global internal_connect, internal_domains, trusted_relay, hello_blacklist + global case_sensitive_localpart socketname = cp.get('milter','socket') timeout = cp.getint('milter','timeout') check_user = cp.getaddrset('milter','check_user') @@ -406,6 +420,7 @@ def read_config(list): internal_domains = cp.getlist('milter','internal_domains') trusted_relay = cp.getlist('milter','trusted_relay') hello_blacklist = cp.getlist('milter','hello_blacklist') + case_sensitive_localpart = cp.getboolean('milter','case_sensitive_localpart') # defang section 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 global smart_alias - for sa in cp.getlist('wiretap','smart_alias'): - sm = cp.getlist('wiretap',sa) + for sa,v in [ + (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: milter_log.warning('malformed smart alias: %s',sa) continue 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:] # dspam section @@ -829,12 +850,18 @@ class bmsMilter(Milter.Milter): def smart_alias(self,to): 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: ct = '@'.join(t) else: 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) if len(cf0) == 2: cf0 = '@' + cf0[1] @@ -1582,10 +1609,10 @@ class bmsMilter(Milter.Milter): if ln[0].isspace() and ln[0] != '\n': lastln += ln continue - name,val = lastln.rstrip().split(None,1) - pos = val.find('<SRS') - if pos >= 0: - try: + try: + name,val = lastln.rstrip().split(None,1) + pos = val.find('<SRS') + if pos >= 0: sender = srs.reverse(val[pos+1:-1]) cbv_cache[sender] = 500,self.delayed_failure,time.time() try: @@ -1597,7 +1624,7 @@ class bmsMilter(Milter.Milter): self.tempname = None self.log('BLACKLIST:',sender,fname) return Milter.DISCARD - except: continue + except: continue lnl = ln.lower() for k in ('message-id','x-mailer','sender'): if lnl.startswith(k): diff --git a/milter.cfg b/milter.cfg index 2b6df0453eccabc65538231ddbf278e0f35a9b02..9e75f7fe200710413907cc4f8a3520487e03c24f 100644 --- a/milter.cfg +++ b/milter.cfg @@ -31,6 +31,9 @@ log_headers = 0 # Reject mail for domains mentioned unless user is mentioned here also ;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 [defang] @@ -125,11 +128,13 @@ blind = 1 # discard outgoing mail without alerting sender # can be used in conjunction with wiretap to censor outgoing mail ;discard_users = canned@bigcorp.com + # # smart aliases trigger on both sender and recipient +# alias = sender, recipient[, destination] # -;smart_alias = copycust,walter,spy1,spy2 -# multiple wiretap monitors +[smart_alias] +# multiple wiretap monitors. Smart aliases are applied after wiretap. ;spy1 = disloyal@bigcorp.com,spy@bigcorp.com ;spy2 = bigmouth@bigcorp.com,spy@bigcorp.com # 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, ; walter@bigcorp.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 [dspam] diff --git a/milter.spec b/milter.spec index 37a46fcbae447136de9a5656b2059fb37c3bcded..96af07dcac92e16dd4f0e66e779a6f9a2a4eff73 100644 --- a/milter.spec +++ b/milter.spec @@ -177,6 +177,7 @@ rm -rf $RPM_BUILD_ROOT %changelog * Tue May 23 2006 Stuart Gathman <stuart@bmsi.com> 0.8.6-2 +- Support CBV timeout - Support fail template, headers in templates - Create GOSSiP record only when connection will procede to DATA. - More SPF lax heuristics