From 8f8de8fa97f5919b5fb0273e4f63bc1a3978e8c0 Mon Sep 17 00:00:00 2001 From: Stuart Gathman <stuart@gathman.org> Date: Fri, 14 Oct 2005 16:17:31 +0000 Subject: [PATCH] Auto whitelist refinements. --- Milter/__init__.py | 2 +- NEWS | 1 + TODO | 16 +++++----- bms.py | 32 +++++++++++-------- logmsgs.html | 76 ++++++++++++++++++++++++++++++++++++++++++++++ milter.html | 21 +++++++++++-- 6 files changed, 124 insertions(+), 24 deletions(-) create mode 100644 logmsgs.html diff --git a/Milter/__init__.py b/Milter/__init__.py index b56a90c..0031a54 100755 --- a/Milter/__init__.py +++ b/Milter/__init__.py @@ -44,7 +44,7 @@ class Milter: for i in msg: print i, print - def connect(self,hostname,unused,hostaddr): + def connect(self,hostname,family,hostaddr): "Called for each connection to sendmail." self.log("connect from %s at %s" % (hostname,hostaddr)) return CONTINUE diff --git a/NEWS b/NEWS index c7369eb..ccfbc1a 100644 --- a/NEWS +++ b/NEWS @@ -1,5 +1,6 @@ Here is a history of user visible changes to Python milter. +0.8.4 Auto-whitelist recipients of outgoing email. 0.8.3 Keep screened honeypot mail, but optionally discard honeypot only mail. spf_accept_fail option for braindead SPF senders (treats fail like softfail) diff --git a/TODO b/TODO index a63cf7d..ab412e8 100644 --- a/TODO +++ b/TODO @@ -1,10 +1,10 @@ -Find rfc2822 policy for MFROM quoting. +Train honeypot with auto-whitelisted mail as ham. This should result in +a totally self training auto screener. Rescind whitelist for banned +extensions, in case sender is infected. -Use /etc/mail/access for domain specific SPF policies. +Find rfc2822 policy for MFROM quoting. -SPF-Fail: REJECT -SPF-Softfail: OK -SPF-Neutral: OK +Support explicit errors for SPF policy in access file: SPF-Neutral:aol.com ERROR:"550 AOL mail must get SPF PASS" Defer TEMPERROR in SPF evaluation - give precedence to security @@ -16,9 +16,6 @@ Option to add Received-SPF header, but never reject on SPF. Create null config that does nothing - except maybe add Received-SPF headers. Many admins would like to turn features on one at a time. -Auto whitelist based on outgoing email - perhaps with magic subject -or recipient prefix. - Can't output messages with malformed rfc822 attachments. Move milter,Milter,mime,spf modules to pymilter @@ -33,7 +30,8 @@ check spam keywords with character classes, e.g. Implement RRS - a backdoor for non-SRS forwarders. User lists non-SRS forwarder accounts, and a util provides a special local alias for the -user to give to the forwarder. Alias only works for mail from that +user to give to the forwarder. (Or user just adds arbitrary alias +unique to that forwarder to a database.) Alias only works for mail from that forwarder. Milter gets forwarder domain from alias and uses it to SPF check forwarder. diff --git a/bms.py b/bms.py index f401a86..69809a4 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.31 2005/10/14 01:14:08 customdesigned +# Auto whitelist feature. +# # Revision 1.30 2005/10/12 16:36:30 customdesigned # Release 0.8.3 # @@ -666,20 +669,21 @@ class AddrCache(object): l = time.strptime(ts,AddrCache.time_format) t = time.mktime(l) if t > too_old: - cache[rcpt] = None + cache[rcpt.lower()] = None except: - cache[ln.strip()] = None + cache[ln.strip().lower()] = None except IOError: pass def has_key(self,sender): - return self.cache.has_key(sender) + return self.cache.has_key(sender.lower()) def __getitem__(self,sender): - return self.cache[sender] + return self.cache[sender.lower()] def __setitem__(self,sender,res): - cached = sender in self.cache - self.cache[sender] = res + lsender = sender.lower() + cached = lsender in self.cache + self.cache[lsender] = res if not cached and not res: s = time.strftime(AddrCache.time_format,time.localtime()) print >>open(self.fname,'a'),sender,s # log who we sent DSNs to @@ -1017,9 +1021,9 @@ class bmsMilter(Milter.Milter): return Milter.TEMPFAIL self.add_header('Received-SPF',q.get_header(res,receiver)) self.spf = q - if self.dspam and not self.internal_connection and res == 'pass': - if auto_whitelist.has_key(self.canon_from): - self.dspam = False + if self.dspam and res == 'pass' and auto_whitelist.has_key(self.canon_from): + self.dspam = False + self.log("WHITELIST",self.canon_from) return Milter.CONTINUE # hide_path causes a copy of the message to be saved - until we @@ -1358,9 +1362,13 @@ class bmsMilter(Milter.Milter): force_result=dspam.DSR_ISSPAM) self.log("HONEYPOT:",rcpt) return Milter.DISCARD - #if not self.dspam: - # FIXME: tag, but force as ham - txt = ds.check_spam(user,txt,self.recipients) + if not self.dspam: + # Sender whitelisted: tag, but force as ham. + # User can change if actually spam. + txt = ds.check_spam(user,txt,self.recipients, + force_result=dspam.DSR_ISINNOCENT) + else: + txt = ds.check_spam(user,txt,self.recipients) if not txt: # DISCARD if quarrantined for any recipient. It # will be resent to all recipients if they submit diff --git a/logmsgs.html b/logmsgs.html new file mode 100644 index 0000000..5797c7b --- /dev/null +++ b/logmsgs.html @@ -0,0 +1,76 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.1 Final//EN"> +<html> +<head> +<title>Python Milter Log Documentation</title> +<style> +DT { font-weight: bolder; padding-top: 1em } +</style> +</head><body> + +<h1> Milter Log Documentation </h1> + +The milter log has a variety of "tags" in it that indicate what it did. + +<dl> +<dt> DSPAM: honeypot SCREENED +<dd> message was quarantined to the honeypot quarantine + +<dt> REJECT: hello SPF: fail 550 access denied +<dt> REJECT: hello SPF: softfail 550 domain in transition +<dt> REJECT: hello SPF: neutral 550 access neither permitted nor denied +<dd> message was rejected because there was an SPF policy for the +HELO name, and it did not pass. + +<dt> CBV: sender-17-44662668-643@bluepenmagic.com +<dd> we performed a call back verification + +<dt> dspam +<dd> dspam identifier was added to the message + +<dt> REJECT: spam from self: jsconnor.com +<dd> message was reject because HELO was us (jsconnor.com) + +<dt> INNOC: richh +<dd> message was used to update richh's dspam dictionary + +<dt> HONEYPOT: michaelb@jsconnor.com +<dd> message was sent to a honeypot address (michaelb@jsconnor.com), the +message was added to the honeypot dspam dictionary as spam + +<dt> REJECT: numeric hello name: 63.217.19.146 +<dd> message was rejected because helo name was invalid (numeric) + +<dt> eom +<dd> message was successfully received + +<dt> TEMPFAIL: CBV: 450 No MX servers available +<dd> we tried to do a call back verification but could not look up +MX record, we told the sender to try again later + +<dt> CBV: info@emailpizzahut.com (cached) +<dd> call back verification was needed, we had already done it recently + +<dt> abort after 0 body chars +<dd> sender hung up on us + +<dt> REJECT: SPF fail 550 SPF fail: see + http://openspf.com/why.html?sender=m.hendersonxk@163.net&ip=213.47.161.100 +<dd> message was reject because its sender's spf policy said to + +<dt> REJECT: Subject: Cialis - No prescription needed! +<dd> message was rejected because its subject contained a bad expression + +<dt> DSPAM: tonyc tonyc@jsconnor.com +<dd> message was sent to tonyc@jsconnor.com and it was identified as spam +and placed in the tonyc dspam quarantine + +<dt> REJECT: CBV: 550 calvinalstonis@ix.netcom.com...User unknown +<dt> REJECT: CBV: 553 sorry, that domain isn't in my list +<dt> REJECT: CBV: 554 delivery error: dd This user doesn't have an account +<dd> message was rejected because call back verification gave us a fatal +error +</dl> + +Please add more tags to this list if you know of any. Thanks. +</body> +</html> diff --git a/milter.html b/milter.html index 5e9f732..4e52bd0 100644 --- a/milter.html +++ b/milter.html @@ -24,7 +24,7 @@ ALT="Viewable With Any Browser" BORDER="0"></A> Stuart D. Gathman</a><br> This web page is written by Stuart D. Gathman<br>and<br>sponsored by <a href="http://www.bmsi.com">Business Management Systems, Inc.</a> <br> -Last updated Aug 28, 2005</h4> +Last updated Oct 12, 2005</h4> See the <a href="faq.html">FAQ</a> | <a href="http://sourceforge.net/project/showfiles.php?group_id=139894">Download now</a> | <a href="/mailman/listinfo/pymilter">Subscribe to mailing list</a> | @@ -47,10 +47,25 @@ efficient and secure. I recommend upgrading. <h2> Recent Changes </h2> -Python milter is being moved to +Python milter has been moved to <a href="http://sourceforge.net/projects/pymilter/">pymilter Sourceforge project</a> for development and release downloads. <p> +Release 0.8.3 uses the standard logging module, and supports configuring +more detailed SPF policy via the sendmail access map. SMTP AUTH connections +are considered INTERNAL. Preventing forgery between internal domains is +just a matter of specifying the user-domain map - I'll define something +for the next version. We now send DSNs when mail is quarantined (rejecting +if DSN fails) and for SPF syntax errors (PermError). There is an +experimental option to add a Sender header when it is missing and the From +domain doesn't match the MAIL FROM domain. Next release, we may start +renaming and replacing an existing Sender header when neither it nor the +From domain matches MAIL FROM. Since bogus MAIL FROMs are rejected +(to varying degrees depending on the configured SPF policy), and +both Sender and From and displayed by default in many email clients, +this provides some phishing protection without rejecting mail based +on headers. +<p> Release 0.8.2 has changes to <a href="http://openspf.net">SPF</a> to bring it in line with the newly official RFC. It adds <a href="http://ses.codeshare.ca/">SES</a> @@ -245,6 +260,8 @@ content filtering. SPF checking requires <a href="http://pydns.sourceforge.net/"> pydns</a>. Configuration documentation is currently included as comments in the <a href="milter.cfg">sample config file</a> for the bms.py milter. +See also the <a href="HOWTO">HOWTO</a> and <a href="logmsgs.html"> +Milter Log Message Tags</a>. <p> Python milter is under GPL. The authors can probably be convinced to change this to LGPL if needed. -- GitLab