Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2fcbc27f2a |
@@ -16,7 +16,6 @@ include bms.py
|
||||
include spf.py
|
||||
include cid2spf.py
|
||||
include spfquery.py
|
||||
include ban2zone.py
|
||||
include test.py
|
||||
include sample.py
|
||||
include milter-template.py
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
import time
|
||||
import shelve
|
||||
import thread
|
||||
import logging
|
||||
import urllib
|
||||
|
||||
log = logging.getLogger('milter.greylist')
|
||||
|
||||
def quoteAddress(s):
|
||||
'''Quote an address so that it's safe to store in the file-system.
|
||||
Address can either be a domain name, or local part.
|
||||
Returns the quoted address.'''
|
||||
|
||||
s = urllib.quote(s, '@_-+~!.%')
|
||||
if s.startswith('.'): s = '%2e' + s[1:]
|
||||
return s
|
||||
|
||||
class Record(object):
|
||||
__slots__ = ( 'firstseen', 'lastseen', 'umis', 'cnt' )
|
||||
|
||||
def __init__(self):
|
||||
now = time.time()
|
||||
self.firstseen = now
|
||||
self.lastseen = now
|
||||
self.cnt = 0
|
||||
self.umis = None
|
||||
|
||||
class Greylist(object):
|
||||
|
||||
def __init__(self,dbname,grey_time=10,grey_expire=4,grey_retain=36):
|
||||
self.ignoreLastByte = False
|
||||
self.greylist_time = grey_time * 60 # minutes
|
||||
self.greylist_expire = grey_expire * 3600 # hours
|
||||
self.greylist_retain = grey_retain * 24 * 3600 # days
|
||||
self.dbp = shelve.open(dbname,'c',protocol=2)
|
||||
self.lock = thread.allocate_lock()
|
||||
|
||||
def check(self,ip,sender,recipient):
|
||||
"Return number of allowed messages for greylist triple."
|
||||
sender = quoteAddress(sender)
|
||||
recipient = quoteAddress(recipient)
|
||||
key = ip + ':' + sender + ':' + recipient
|
||||
self.lock.acquire()
|
||||
try:
|
||||
dbp = self.dbp
|
||||
try:
|
||||
r = dbp[key]
|
||||
now = time.time()
|
||||
if now > r.lastseen + self.greylist_retain:
|
||||
# expired
|
||||
log.debug('Expired greylist: %s',key)
|
||||
r = Record()
|
||||
elif now < r.firstseen + self.greylist_time:
|
||||
# still greylisted
|
||||
log.debug('Early greylist: %s',key)
|
||||
#r = Record()
|
||||
r.lastseen = now
|
||||
elif r.cnt or now < r.firstseen + self.greylist_expire:
|
||||
# in greylist window or active
|
||||
r.lastseen = now
|
||||
r.cnt += 1
|
||||
log.debug('Active greylist(%d): %s',r.cnt,key)
|
||||
else:
|
||||
# passed greylist window
|
||||
log.debug('Late greylist: %s',key)
|
||||
r = Record()
|
||||
dbp[key] = r
|
||||
except:
|
||||
r = Record()
|
||||
dbp[key] = r
|
||||
dbp.sync()
|
||||
finally:
|
||||
self.lock.release()
|
||||
return r.cnt
|
||||
@@ -1,14 +1,3 @@
|
||||
The recent feature to let a REJECT policy for SPF None be overridden
|
||||
by whitelisting is working for CSI and CMS. However, there could be
|
||||
a sender that we want to REJECT even when whitelisted - because they
|
||||
normally get a guessed PASS. Need another policy name - or else just
|
||||
add them to local SPF so they won't ever get 'None'.
|
||||
|
||||
When policy is OK, do not use cbv_cache for blacklist.
|
||||
|
||||
Add postmaster option or general rcpt list to dsn. Can send dsn to
|
||||
user and postmaster on the same connection.
|
||||
|
||||
Check ESMTP NOTIFY before sending real DSNs. Just use CBV if DSNs are
|
||||
not wanted.
|
||||
|
||||
|
||||
-15
@@ -1,15 +0,0 @@
|
||||
#!/usr/bin/python2.4
|
||||
|
||||
import socket
|
||||
import sys
|
||||
from glob import glob
|
||||
|
||||
banned_ips = set(socket.inet_aton(ip)
|
||||
for fn in sys.argv[1:]
|
||||
for ip in open(fn))
|
||||
banned_ips = list(banned_ips)
|
||||
banned_ips.sort()
|
||||
for ip in banned_ips:
|
||||
a = socket.inet_ntoa(ip).split('.')
|
||||
a.reverse()
|
||||
print "%s\tIN A 127.0.0.2"%('.'.join(a))
|
||||
@@ -1,28 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
# A simple milter that has grown quite a bit.
|
||||
# $Log$
|
||||
# Revision 1.133 2008/10/09 18:44:54 customdesigned
|
||||
# Skip greylisting for good reputation.
|
||||
#
|
||||
# Revision 1.132 2008/10/09 00:55:13 customdesigned
|
||||
# Don't reset greylist timer on early retries.
|
||||
#
|
||||
# Revision 1.131 2008/10/08 04:57:28 customdesigned
|
||||
# Greylisting
|
||||
#
|
||||
# Revision 1.130 2008/10/02 03:19:00 customdesigned
|
||||
# Delay strike3 REJECT and don't reject if whitelisted.
|
||||
# Recognize vacation messages as autoreplies.
|
||||
#
|
||||
# Revision 1.129 2008/09/09 23:24:56 customdesigned
|
||||
# Never ban a trusted relay.
|
||||
#
|
||||
# Revision 1.128 2008/09/09 23:08:16 customdesigned
|
||||
# Wasn't reading banned_ips
|
||||
#
|
||||
# Revision 1.127 2008/08/25 18:32:22 customdesigned
|
||||
# Handle missing gossip_node so self tests pass.
|
||||
#
|
||||
# Revision 1.126 2008/08/18 17:47:57 customdesigned
|
||||
# Log rcpt for SRS rejections.
|
||||
#
|
||||
@@ -226,7 +204,6 @@ from Milter.dynip import is_dynip as dynip
|
||||
from Milter.utils import \
|
||||
iniplist,parse_addr,parse_header,ip4re,addr2bin,parseaddr
|
||||
from Milter.config import MilterConfigParser
|
||||
from Milter.greylist import Greylist
|
||||
|
||||
from fnmatch import fnmatchcase
|
||||
from email.Utils import getaddresses
|
||||
@@ -340,7 +317,6 @@ supply_sender = False
|
||||
access_file = None
|
||||
timeout = 600
|
||||
banned_ips = set()
|
||||
greylist = None
|
||||
|
||||
logging.basicConfig(
|
||||
stream=sys.stdout,
|
||||
@@ -514,21 +490,6 @@ def read_config(list):
|
||||
else:
|
||||
gossip_ttl = 1
|
||||
|
||||
# greylist section
|
||||
if cp.has_option('greylist','dbfile'):
|
||||
global greylist
|
||||
grey_db = cp.getdefault('greylist','dbfile')
|
||||
grey_days = 36
|
||||
if cp.has_option('greylist','retain'):
|
||||
grey_days = cp.getint('greylist','retain')
|
||||
grey_expire = 4
|
||||
if cp.has_option('greylist','expire'):
|
||||
grey_expire = cp.getint('greylist','expire')
|
||||
grey_time = 10
|
||||
if cp.has_option('greylist','time'):
|
||||
grey_time = cp.getint('greylist','expire')
|
||||
greylist = Greylist(grey_db,grey_time,grey_expire,grey_days)
|
||||
|
||||
def findsrs(fp):
|
||||
lastln = None
|
||||
for ln in fp:
|
||||
@@ -768,13 +729,12 @@ class bmsMilter(Milter.Milter):
|
||||
|
||||
def offense(self,inc=1):
|
||||
self.offenses += inc
|
||||
if self.offenses > 3 and not self.trusted_relay:
|
||||
if self.offenses > 3:
|
||||
try:
|
||||
ip = addr2bin(self.connectip)
|
||||
if ip not in banned_ips:
|
||||
banned_ips.add(ip)
|
||||
print >>open('banned_ips','a'),self.connectip
|
||||
self.log("BANNED IP:",self.connectip)
|
||||
except: pass
|
||||
return Milter.REJECT
|
||||
|
||||
@@ -796,7 +756,6 @@ class bmsMilter(Milter.Milter):
|
||||
self.dspam = True
|
||||
self.whitelist = False
|
||||
self.blacklist = False
|
||||
self.greylist = False
|
||||
self.reject_spam = True
|
||||
self.data_allowed = True
|
||||
self.delayed_failure = None
|
||||
@@ -898,14 +857,12 @@ class bmsMilter(Milter.Milter):
|
||||
return Milter.REJECT
|
||||
self.umis = None
|
||||
self.spf = None
|
||||
self.policy = None
|
||||
if not (self.internal_connection or self.trusted_relay) \
|
||||
and self.connectip and spf:
|
||||
rc = self.check_spf()
|
||||
if rc != Milter.CONTINUE:
|
||||
if rc != Milter.TEMPFAIL: self.offense()
|
||||
return rc
|
||||
self.greylist = True
|
||||
else:
|
||||
rc = Milter.CONTINUE
|
||||
# FIXME: parse Received-SPF from trusted_relay for SPF result
|
||||
@@ -913,7 +870,6 @@ class bmsMilter(Milter.Milter):
|
||||
hres = self.spf and self.spf_helo
|
||||
# Check whitelist and blacklist
|
||||
if auto_whitelist.has_key(self.canon_from):
|
||||
self.greylist = False
|
||||
if res == 'pass' or self.trusted_relay:
|
||||
self.whitelist = True
|
||||
self.log("WHITELIST",self.canon_from)
|
||||
@@ -924,9 +880,8 @@ class bmsMilter(Milter.Milter):
|
||||
self.cbv_needed = None
|
||||
elif cbv_cache.has_key(self.canon_from) and cbv_cache[self.canon_from] \
|
||||
or domain in blacklist:
|
||||
# FIXME: don't use cbv_cache for blacklist if policy is 'OK'
|
||||
if not self.internal_connection:
|
||||
self.offense(inc=2)
|
||||
self.offense()
|
||||
if not dspam_userdir:
|
||||
if domain in blacklist:
|
||||
self.log('REJECT: BLACKLIST',self.canon_from)
|
||||
@@ -937,21 +892,9 @@ class bmsMilter(Milter.Milter):
|
||||
self.log('REJECT:',desc)
|
||||
self.setreply('550','5.7.1',*desc.splitlines())
|
||||
return Milter.REJECT
|
||||
self.greylist = False # don't delay - use spam for training
|
||||
self.blacklist = True
|
||||
self.log("BLACKLIST",self.canon_from)
|
||||
else:
|
||||
# REJECT delayed until after checking whitelist
|
||||
if self.policy == 'REJECT':
|
||||
self.log('REJECT: no PTR, HELO or SPF')
|
||||
self.setreply('550','5.7.1',
|
||||
"You must have a valid HELO or publish SPF: http://www.openspf.org ",
|
||||
"Contact your mail administrator IMMEDIATELY! Your mail server is ",
|
||||
"severely misconfigured. It has no PTR record (dynamic PTR records ",
|
||||
"that contain your IP don't count), an invalid or dynamic HELO, ",
|
||||
"and no SPF record."
|
||||
)
|
||||
return self.offense() # ban ip if too many bad MFROMs
|
||||
global gossip
|
||||
if gossip and domain and rc == Milter.CONTINUE \
|
||||
and not (self.internal_connection or self.trusted_relay) \
|
||||
@@ -987,8 +930,6 @@ class bmsMilter(Milter.Milter):
|
||||
self.setreply('550','5.7.1',
|
||||
'Your domain has been sending nothing but spam')
|
||||
return Milter.REJECT
|
||||
if self.reputation > 40 and self.confidence > 1:
|
||||
self.greylist = False
|
||||
except:
|
||||
gossip = None
|
||||
raise
|
||||
@@ -1071,11 +1012,19 @@ class bmsMilter(Milter.Milter):
|
||||
if self.missing_ptr and ores == 'none' and res != 'pass' \
|
||||
and hres != 'pass':
|
||||
# this bad boy has no credentials whatsoever
|
||||
res = 'none'
|
||||
policy = p.getNonePolicy()
|
||||
if policy in ('CBV','DSN'):
|
||||
if policy in ('CBV','DNS'):
|
||||
self.offenses = 3 # ban ip if any bad recipient
|
||||
self.need_cbv(policy,q,'strike3')
|
||||
if self.need_cbv(policy,q,'strike3'):
|
||||
self.log('REJECT: no PTR, HELO or SPF')
|
||||
self.setreply('550','5.7.1',
|
||||
"You must have a valid HELO or publish SPF: http://www.openspf.org ",
|
||||
"Contact your mail administrator IMMEDIATELY! Your mail server is ",
|
||||
"severely misconfigured. It has no PTR record (dynamic PTR records ",
|
||||
"that contain your IP don't count), an invalid or dynamic HELO, ",
|
||||
"and no SPF record."
|
||||
)
|
||||
return self.offense() # ban ip if too many bad MFROMs
|
||||
if res in ('deny', 'fail'):
|
||||
if self.need_cbv(p.getFailPolicy(),q,'fail'):
|
||||
self.log('REJECT: SPF %s %i %s' % (res,code,txt))
|
||||
@@ -1166,7 +1115,6 @@ class bmsMilter(Milter.Milter):
|
||||
self.log("srs rcpt:",newaddr)
|
||||
self.dspam = False # verified as reply to mail we sent
|
||||
self.blacklist = False
|
||||
self.greylist = False
|
||||
self.delayed_failure = False
|
||||
except:
|
||||
if not (self.internal_connection or self.trusted_relay):
|
||||
@@ -1235,18 +1183,8 @@ class bmsMilter(Milter.Milter):
|
||||
except:
|
||||
self.log("rcpt to",to,str)
|
||||
raise
|
||||
if self.greylist and greylist and self.canon_from:
|
||||
# no policy for trusted or internal
|
||||
rc = greylist.check(self.connectip,self.canon_from,canon_to)
|
||||
if rc == 0:
|
||||
self.log("GREYLIST:",self.connectip,self.canon_from,canon_to)
|
||||
self.setreply('451','4.7.1',
|
||||
'Greylisted: http://projects.puremagic.com/greylisting/',
|
||||
'Please retry in %.1f minutes'%(greylist.greylist_time/60.0))
|
||||
return Milter.TEMPFAIL
|
||||
self.log("GREYLISTED: %d"%rc)
|
||||
|
||||
self.log("rcpt to",to,str)
|
||||
|
||||
self.smart_alias(to)
|
||||
# get recipient after virtusertable aliasing
|
||||
#rcpt = self.getsymval("{rcpt_addr}")
|
||||
@@ -1367,10 +1305,9 @@ class bmsMilter(Milter.Milter):
|
||||
if gossip and self.umis:
|
||||
gossip_node.feedback(self.umis,1)
|
||||
return rc
|
||||
elif self.whitelist_sender:
|
||||
# check for AutoReplys
|
||||
if (lname == 'subject' and reautoreply.match(val)) \
|
||||
or (lname == 'user-agent' and val.lower().startswith('vacation')):
|
||||
elif self.whitelist_sender and lname == 'subject':
|
||||
# check for AutoReplys
|
||||
if reautoreply.match(val):
|
||||
self.whitelist_sender = False
|
||||
self.log('AUTOREPLY: not whitelisted')
|
||||
|
||||
@@ -1705,7 +1642,6 @@ class bmsMilter(Milter.Milter):
|
||||
return rc
|
||||
|
||||
def need_cbv(self,policy,q,tname):
|
||||
self.policy = policy
|
||||
if policy == 'CBV':
|
||||
if self.mailfrom != '<>' and not self.cbv_needed:
|
||||
self.cbv_needed = (q,None)
|
||||
@@ -1924,7 +1860,7 @@ class bmsMilter(Milter.Milter):
|
||||
self.log('CBV:',sender,'Using:',fname)
|
||||
except IOError: pass
|
||||
if not m:
|
||||
self.log('CBV:',sender,'PLAIN (%s)'%q.result)
|
||||
self.log('CBV:',sender,'PLAIN')
|
||||
else:
|
||||
if srs:
|
||||
# Add SRS coded sender to various headers. When (incorrectly)
|
||||
@@ -1979,7 +1915,6 @@ def main():
|
||||
return
|
||||
try:
|
||||
from glob import glob
|
||||
global banned_ips
|
||||
banned_ips = set(addr2bin(ip)
|
||||
for fn in glob('banned_ips*')
|
||||
for ip in open(fn))
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@
|
||||
<li><a href="credits.html">CREDITS</a>
|
||||
<li><a href="http://sourceforge.net"><img src="http://sflogo.sourceforge.net/sflogo.php?group_id=139894&type=1" width="88" height="31" border="0" alt="SourceForge.net Logo" /></a>
|
||||
<h3>Links</h3>
|
||||
<li><a href="https://www.milter.org/developers/api/index">C API</a>
|
||||
<li><a href="http://www.milter.org/milter_api/api.html">C API</a>
|
||||
<li><a href="http://www.milter.org/">Milter.Org</a>
|
||||
<li><a href="http://www.python.org/">Python.Org</a>
|
||||
<li><a href="http://www.sendmail.org/">Sendmail.Org</a>
|
||||
|
||||
+60
-17
@@ -13,19 +13,14 @@ ALT="Viewable With Any Browser" BORDER="0"></A>
|
||||
</map>
|
||||
</P>
|
||||
|
||||
<table rules="none">
|
||||
<tr><td>
|
||||
<img src="Maxwells.gif" alt="Maxwell's Daemon: pymilter mascot" align="top">
|
||||
Mascot by <a href="http://alphard.ethz.ch/hafner/lebl.htm">Christian Hafner</a>
|
||||
</td>
|
||||
<td>
|
||||
<img src="Maxwells.gif" alt="Maxwell's Daemon: pymilter mascot" align=left>
|
||||
<h1 align=center>Sendmail Milters in Python</h1>
|
||||
<h4 align=center>by <a href="mailto:%75%72%6D%61%6E%65%40%6E%65%75%72%61l%61%63%63%65%73%73%2E%63%6F%6D">Jim Niemira</a>
|
||||
and <a href="mailto:%73%74%75%61%72%74%40%62%6D%73%69%2E%63%6F%6D">
|
||||
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 26, 2008</h4>
|
||||
Last updated Mar 30, 2007</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="http://bmsi.com/mailman/listinfo/pymilter">Subscribe to mailing list</a> |
|
||||
@@ -36,7 +31,7 @@ See the <a href="faq.html">FAQ</a> | <a href="http://sourceforge.net/project/sho
|
||||
<a href="//www.python.org">
|
||||
<img src="python55.gif" align=left alt="A Python"></a>
|
||||
<a href="//www.sendmail.org/">Sendmail</a> introduced a
|
||||
<a href="https://www.milter.org/developers/api/index"> new API</a> beginning with version 8.10 -
|
||||
<a href="http://www.milter.org/milter_api/api.html"> new API</a> beginning with version 8.10 -
|
||||
libmilter. The milter module for <a href="//www.python.org">Python</a>
|
||||
provides a python interface to libmilter that exploits all its features.
|
||||
<p>
|
||||
@@ -44,11 +39,7 @@ Sendmail 8.12 officially releases libmilter.
|
||||
Version 8.12 seems to be more robust, and includes new privilege
|
||||
separation features to enhance security. Even better, sendmail 8.13
|
||||
supports socket maps, which makes <a href="pysrs.html">pysrs</a> much more
|
||||
efficient and secure. Sendmail 8.14 finally supports modifying
|
||||
MAIL FROM via the milter API. Unfortunately, I haven't gotten around
|
||||
to supporting that yet in python milter.
|
||||
</td></tr>
|
||||
</table>
|
||||
efficient and secure. I recommend upgrading.
|
||||
|
||||
<h3><a name=overview>Overview</a></h3>
|
||||
|
||||
@@ -57,7 +48,7 @@ href="#milter">milters</a>, and the beginnings of a general purpose mail
|
||||
filtering system written in Python.
|
||||
<p>
|
||||
At the lowest level, the 'milter' module provides a thin wrapper around the
|
||||
<a href="https://www.milter.org/developers/api/index">
|
||||
<a href="http://www.milter.org/milter_api/api.html">
|
||||
sendmail libmilter API</a>. This API lets you register callbacks for
|
||||
a number of events in the
|
||||
<a href="http://www.cs.concordia.ca/~group/fig/public/email/relay/milter+ruleset-checks.html">process of sendmail receiving a message via SMTP</a>.
|
||||
@@ -130,7 +121,7 @@ copy mail.
|
||||
<li> For more ideas, check the <a href="//www.milter.org">Milter Web Page</a>.
|
||||
</menu>
|
||||
|
||||
<a href="https://www.milter.org/developers/api/index">
|
||||
<a href="http://www.milter.org/milter_api/api.html">
|
||||
Documentation</a> for the C API is provided with sendmail. Miltermodule
|
||||
provides a thin python wrapper for the C API. Milter.py provides a simple
|
||||
OO wrapper on top of that.
|
||||
@@ -211,8 +202,60 @@ href="http://www.duh.org/cvsweb.cgi/~checkout~/pmilter/doc/milter-protocol.txt?r
|
||||
<h3> Confirmed Installations </h3>
|
||||
|
||||
Please <a href="mailto:%73%74%75%61%72%74%40%62%6D%73%69%2E%63%6F%6D">email</a>
|
||||
me if you do <i>not</i> successfully install milter. The confirmed
|
||||
installations are too numerous to list at this point.
|
||||
me if you successfully install milter on a system not mentioned below.
|
||||
<p>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Operating System</th> <th>Compiler</th> <th>Python</th> <th>Sendmail</th>
|
||||
<th>milter</th>
|
||||
<tr>
|
||||
<td>Mandrake 8.0</td><td>gcc-3.0.1</td><td>2.1.1</td><td>8.12.0</td>
|
||||
<td>0.3.3</td><tr>
|
||||
<td>Mandrake 8.0</td><td>gcc-2.96</td><td>2.0</td><td>8.11.2</td>
|
||||
<td>0.3.6</td><tr>
|
||||
<td>RedHat 6.2</td><td>egcs-1.1.2</td><td>2.2.2</td><td>8.11.6</td>
|
||||
<td>0.5.4</td><tr>
|
||||
<td>RedHat 7.1</td><td>gcc-2.96</td><td>?</td><td>8.12.1</td>
|
||||
<td>0.3.5</td><tr>
|
||||
<td>RedHat 7.3</td><td>gcc-2.96</td><td>2.2.2</td><td>8.11.6</td>
|
||||
<td>0.5.5</td><tr>
|
||||
<td>RedHat 7.3</td><td>gcc-2.96</td><td>2.3.3</td><td>8.13.1</td>
|
||||
<td>0.7.2</td><tr>
|
||||
<td>RedHat 7.3</td><td>gcc-2.96</td><td>2.4.1</td><td>8.13.5</td>
|
||||
<td>0.8.4</td><tr>
|
||||
<td>RedHat 8.0</td><td>gcc-3.2</td><td>2.2.1</td><td>8.12.6</td>
|
||||
<td>0.5.2</td><tr>
|
||||
<td>RedHat 9.0</td><td>gcc-3.2.2</td><td>2.4.1</td><td>8.13.1</td>
|
||||
<td>0.8.2</td><tr>
|
||||
<td>RedHat EL3</td><td>gcc-3.2.3</td><td>2.4.1</td><td>8.13.5</td>
|
||||
<td>0.8.4</td><tr>
|
||||
<td>Debian Linux</td><td>gcc-2.95.2</td><td>2.1.1</td><td>8.12.0</td>
|
||||
<td>0.3.7</td><tr>
|
||||
<td>Debian Linux</td><td>gcc-3.2.2</td><td>2.2.2</td><td>8.12.7</td>
|
||||
<td>0.5.4</td><tr>
|
||||
<td>AIX-4.1.5</td><td>gcc-2.95.2</td><td>2.1.1</td><td>8.11.5</td>
|
||||
<td>0.3.3</td><tr>
|
||||
<td>AIX-4.1.5</td><td>gcc-2.95.2</td><td>2.1.1</td><td>8.12.1</td>
|
||||
<td>0.3.4</td><tr>
|
||||
<td>AIX-4.1.5</td><td>gcc-2.95.2</td><td>2.1.3</td><td>8.12.3</td>
|
||||
<td>0.4.2</td><tr>
|
||||
<td>AIX-4.1.5</td><td>gcc-2.95.2</td><td>2.4.1</td><td>8.13.1</td>
|
||||
<td>0.8.4</td><tr>
|
||||
<td>Slackware 7.1</td><td>?</td><td>?</td><td>8.12.1</td>
|
||||
<td>0.3.8</td><tr>
|
||||
<td>Slackware 9.0</td><td>gcc-3.2.2</td><td>2.2.3</td><td>8.12.9</td>
|
||||
<td>0.5.4</td><tr>
|
||||
<td>OpenBSD</td><td>?</td><td>2.3.3?</td><td>8.13.1?</td>
|
||||
<td>0.7.2</td><tr>
|
||||
<td>SuSE 7.3</td><td>gcc-2.95.3</td><td>2.1.1</td><td>8.12.2</td>
|
||||
<td>0.3.9</td><tr>
|
||||
<td>FreeBSD</td><td>gcc-2.95.3</td><td>2.2.1</td><td>8.12.3</td>
|
||||
<td>0.4.0</td><tr>
|
||||
<td>FreeBSD</td><td>gcc-2.95.3</td><td>2.2.2</td><td>?</td>
|
||||
<td>0.5.5</td><tr>
|
||||
<td>FreeBSD 4.4</td><td>gcc-2.95.3</td><td>?</td><td>8.12.10</td>
|
||||
<td>0.6.6</td>
|
||||
</table>
|
||||
|
||||
<h2> Enough Already! </h2>
|
||||
|
||||
|
||||
+8
-63
@@ -82,7 +82,7 @@ to determine the official SPF policy result.
|
||||
The offical SPF result is then logged in the Received-SPF header field,
|
||||
but certain results are subjected to further processing to create
|
||||
an effective result for policy purposes.
|
||||
<p>
|
||||
|
||||
If the official result is 'none', we try to turn it into an effective result of
|
||||
'pass' or 'fail'. First, we check for a local substitute SPF record
|
||||
under the domain defined in the <code>[spf]delegate</code> configuration.
|
||||
@@ -91,12 +91,12 @@ too clueless to add their own. If there is no local substitute, we use a "best
|
||||
guess" SPF record of "v=spf1 a/24 mx/24 ptr" for MAIL FROM or "v=spf1 a/24
|
||||
mx/24" for HELO. In addition, a HELO that is a subdomain of MAIL FROM and
|
||||
resolves to the connect IP results in an effective result of 'pass'.
|
||||
<p>
|
||||
|
||||
If there is no local SPF record, and the effective result is still not
|
||||
'pass', we check for either a valid HELO name or a valid PTR record for
|
||||
the connect IP. A valid HELO or PTR cannot look like a dynamic name
|
||||
as determined by the heuristic in <code>Milter.dynip</code>.
|
||||
<p>
|
||||
|
||||
If HELO has an SPF record, and the result is anything but pass, we reject
|
||||
the connection:
|
||||
<pre>
|
||||
@@ -107,16 +107,16 @@ the connection:
|
||||
</pre>
|
||||
Note that HELO does not have any forwarding issues like MAIL FROM, and so
|
||||
any result other than 'pass' or 'none' should be treated like 'fail'.
|
||||
<p>
|
||||
|
||||
Only if nothing about the SMTP envelope can be validated does the effective
|
||||
result remain 'none. I call this the "3 strikes" rule.
|
||||
<p>
|
||||
|
||||
If the official result is 'permerror' (a syntax error in the sender's
|
||||
policy), we use the 'lax' option in pyspf to try various heuristics to guess
|
||||
what they really meant. For instance, the invalid mechanism "ip:1.2.3.4" is
|
||||
treated as "ip4:1.2.3.4". The result of lax processing is then used
|
||||
as the effective result for policy purposes.
|
||||
<p>
|
||||
|
||||
With an effective SPF result in hand, we consult the sendmail access
|
||||
database to find our receiver policy for the sender.
|
||||
|
||||
@@ -159,7 +159,7 @@ SPF-Fail:abeb@adelphia.net DSN
|
||||
This says to accept mail from that adelphia.net user despite the
|
||||
SPF fail, but only after annoying them with a DSN about their ISP's broken
|
||||
policy.
|
||||
<p>
|
||||
|
||||
If there is no match on the full sender, the domain is checked:
|
||||
<pre>
|
||||
SPF-Neutral:aol.com REJECT
|
||||
@@ -168,7 +168,7 @@ This says to reject mail from AOL with an SPF result of neutral.
|
||||
This means AOL users can't use their AOL address with another mail service
|
||||
to send us mail. This is good because the other mail service is
|
||||
likely a badly configured greeting card site or a virus.
|
||||
<p>
|
||||
|
||||
Finally, a default policy for the result is checked. While there are program
|
||||
defaults, you should have defaults in the access database for SPF results:
|
||||
<pre>
|
||||
@@ -192,58 +192,3 @@ independently for each SPF result and sender combination. So aol.com:neutral
|
||||
might have a really bad reputation, while aol.com:pass would be ok.
|
||||
Furthermore, when a sender finally publishes an SPF policy and starts
|
||||
getting SPF pass, their reputation is effectively reset.
|
||||
|
||||
<h2> Whitelists and Blacklists </h2>
|
||||
|
||||
The administrator can whitelist or blacklist senders and sending domains by
|
||||
appending them to <code>${datadir}/auto_whitelist.log</code> or
|
||||
<code>${datadir}/blacklist.log</code> respectively. In addition,
|
||||
recipients of internal senders (except for automatic replies like vacation
|
||||
messages and return receipts) are automatically whitelisted for 60 days, and
|
||||
senders that fail CBV or DSN checks are automatically blacklisted for 30 days.
|
||||
Whitelisted and blacklisted senders are used to automatically train the
|
||||
bayesian content filter before being delivered or rejected, respectively.
|
||||
<p>
|
||||
Real Soon Now users will be able to maintain their own whitelist and
|
||||
blacklist that applies only when they are the recipient.
|
||||
|
||||
<h2> Content Filter </h2>
|
||||
|
||||
Most messages have been rejected or delivered by now, but spammers
|
||||
are always finding new places to send their junk from. For instance,
|
||||
we get around 10000 emails a day, of which around 500 are first time
|
||||
spam senders. A bayesian filter is trained by the whitelists and
|
||||
blacklists, and scores the message. What is likely spam is either
|
||||
rejected or quarantined. If the sender is an effective SPF pass,
|
||||
then they get a DSN notifying them that their message has been
|
||||
quarantined. (A DSN failure gets the sender auto blacklisted.)
|
||||
Else, if the reject_spam option is set, the message is rejected.
|
||||
Otherwise, a CBV is done (failure gets the sender auto blacklisted)
|
||||
and the message is silently quarantined.
|
||||
<p>
|
||||
Normally, you don't want email messages to silently disappear into
|
||||
a black hole, so you should set the reject_spam option. However,
|
||||
if you don't want your correspondent's email to get rejected, you can
|
||||
check your quarantine frequently instead.
|
||||
|
||||
<h3> Honeypot </h3>
|
||||
|
||||
You can also blacklist recipients by listing them as aliases of the
|
||||
'honeypot' dspam user. These are collectively called
|
||||
the honeypot. Any email to these recipients is used to train the
|
||||
spam filter as spam and chalk up a reputation demerit for the sender, then
|
||||
discarded. It might be a good idea to blacklist the sender if it has SPF pass
|
||||
as well, but I'm afraid of accidents.
|
||||
|
||||
<h3> Reputation </h3>
|
||||
|
||||
Reputation is tracked by sending domain and effective SPF result.
|
||||
The GOSSiP server tracks the spam/ham status of the last 1024 messages
|
||||
for each domain:result combination. When the server is queried during
|
||||
the SMTP envelope phase (MAIL FROM), it also queries any configured
|
||||
peers, and the scores are combined. Domains with a history of spam for
|
||||
a given SPF result are rejected at MAIL FROM. The GOSSiP system has
|
||||
a command line utility to reset (delete) a reputation for cases where a
|
||||
sender that was infected with malware is repaired. In addition,
|
||||
the confidence score of a reputation decays with time, so a bad sender
|
||||
will eventually be able to try again without manual intervention.
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
To: %(sender)s
|
||||
From: postmaster@%(receiver)s
|
||||
Subject: SPF fail (EMAIL FORGERY)
|
||||
Auto-Submitted: auto-generated (configuration error)
|
||||
|
||||
This is an automatically generated Delivery Status Notification.
|
||||
|
||||
*** WARNING! YOU ARE SENDING FROM AN UNAUTHORIZED LOCATION ***
|
||||
|
||||
The email administrator for '%(sender_domain)' (YOUR administrator)
|
||||
has FORBIDDEN you to send email from this location. IMMEDIATELY contact your
|
||||
email administrator and follow his instructions to properly send mail.
|
||||
|
||||
THIS IS A WARNING MESSAGE ONLY.
|
||||
|
||||
YOU DO *NOT* NEED TO RESEND YOUR MESSAGE.
|
||||
|
||||
Delivery to the following recipients has been delayed.
|
||||
|
||||
%(rcpt)s
|
||||
|
||||
Subject: %(subject)s
|
||||
Received-SPF: %(spf_result)s
|
||||
|
||||
Your sender policy indicated that the above email was forged.
|
||||
Because we believe your policy is in error, we have accepted the
|
||||
email anyway. Please ask your email administrator to review
|
||||
your SPF policy. You may also have neglected to follow your
|
||||
postmaster's instructions for configuring outgoing email.
|
||||
|
||||
If you need further assistance, please do not hesitate to contact me.
|
||||
|
||||
Kind regards,
|
||||
Stuart D Gathman
|
||||
postmaster@%(receiver)s
|
||||
@@ -227,9 +227,3 @@ blind = 1
|
||||
# domains. Peer reputation is also tracked as to how often they
|
||||
# agree with us, and weighted accordingly.
|
||||
;peers=host1:port,host2
|
||||
|
||||
[greylist]
|
||||
dbfile=greylist.db
|
||||
grey_time=5 # mins (Google retries in 5 mins)
|
||||
grey_expire=6 # hours (some legit sites don't retry for 6 hours)
|
||||
grey_retain=36 # days (keep "first monday" type mailings on file)
|
||||
|
||||
+17
-14
@@ -3,8 +3,8 @@
|
||||
# rpmbuild -ba --target=i386,noarch pymilter.spec
|
||||
|
||||
%define __python python2.4
|
||||
%define version 0.8.11
|
||||
%define release 1%{?dist}.py24
|
||||
%define version 0.8.10
|
||||
%define release 2%{?dist}.py24
|
||||
# what version of RH are we building for?
|
||||
%define redhat7 0
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
%ifos aix4.1
|
||||
%define libdir /var/log/milter
|
||||
%else
|
||||
%define libdir %{_libdir}/pymilter
|
||||
%define libdir /usr/lib/pymilter
|
||||
%endif
|
||||
|
||||
%ifarch noarch
|
||||
@@ -39,7 +39,9 @@ Source: pymilter-%{version}.tar.gz
|
||||
License: GPL
|
||||
Group: Development/Libraries
|
||||
BuildRoot: %{_tmppath}/%{name}-buildroot
|
||||
Prefix: %{_prefix}
|
||||
Vendor: Stuart D. Gathman <stuart@bmsi.com>
|
||||
Packager: Stuart D. Gathman <stuart@bmsi.com>
|
||||
Url: http://www.bmsi.com/python/milter.html
|
||||
Requires: %{__python} >= 2.4, pyspf >= 2.0.4, pymilter
|
||||
%ifos Linux
|
||||
@@ -47,23 +49,21 @@ Requires: chkconfig
|
||||
%endif
|
||||
|
||||
%description -n milter
|
||||
A complex but effective spam filtering, SPF checking, greylisting,
|
||||
and reputation tracking mail application. It uses pydspam if installed for
|
||||
bayesian filtering.
|
||||
A complex but effective spam filtering, SPF checking, and reputation tracking
|
||||
mail application. It uses pydspam if installed for bayesian filtering.
|
||||
|
||||
%package spf
|
||||
Group: Applications/System
|
||||
Summary: BMS spam and reputation milter
|
||||
Requires: pyspf >= 2.0.4, pymilter
|
||||
Obsoletes: pymilter-spf < 0.8.10
|
||||
Obsoletes: pymilter-spf
|
||||
|
||||
%description spf
|
||||
A simple mail filter to add Received-SPF headers and reject forged mail.
|
||||
Rejection policy is configured via sendmail access file and can be
|
||||
tailored by domain.
|
||||
Rejection policy is configured via sendmail access file.
|
||||
|
||||
%prep
|
||||
%setup -q -n pymilter-%{version}
|
||||
%setup -n pymilter-%{version}
|
||||
#patch -p0 -b .bms
|
||||
|
||||
%install
|
||||
@@ -73,7 +73,7 @@ mkdir -p $RPM_BUILD_ROOT/etc/mail
|
||||
mkdir $RPM_BUILD_ROOT/var/log/milter/save
|
||||
mkdir -p $RPM_BUILD_ROOT%{libdir}
|
||||
cp *.txt $RPM_BUILD_ROOT/var/log/milter
|
||||
cp -p bms.py spfmilter.py ban2zone.py $RPM_BUILD_ROOT%{libdir}
|
||||
cp bms.py spfmilter.py $RPM_BUILD_ROOT%{libdir}
|
||||
cp milter.cfg $RPM_BUILD_ROOT/etc/mail/pymilter.cfg
|
||||
cp spfmilter.cfg $RPM_BUILD_ROOT/etc/mail
|
||||
|
||||
@@ -172,7 +172,6 @@ fi
|
||||
%dir /var/log/milter
|
||||
%dir /var/log/milter/save
|
||||
%config %{libdir}/bms.py
|
||||
%config %{libdir}/ban2zone.py
|
||||
%config(noreplace) /var/log/milter/strike3.txt
|
||||
%config(noreplace) /var/log/milter/softfail.txt
|
||||
%config(noreplace) /var/log/milter/fail.txt
|
||||
@@ -202,7 +201,9 @@ Source: %{name}-%{version}.tar.gz
|
||||
License: GPL
|
||||
Group: Development/Libraries
|
||||
BuildRoot: %{_tmppath}/%{name}-buildroot
|
||||
Prefix: %{_prefix}
|
||||
Vendor: Stuart D. Gathman <stuart@bmsi.com>
|
||||
Packager: Stuart D. Gathman <stuart@bmsi.com>
|
||||
Url: http://www.bmsi.com/python/milter.html
|
||||
Requires: %{__python} >= 2.4, sendmail >= 8.13
|
||||
BuildRequires: %{__python}-devel >= 2.4, sendmail-devel >= 8.13
|
||||
@@ -214,7 +215,7 @@ modules provide for navigating and modifying MIME parts, sending
|
||||
DSNs, and doing CBV.
|
||||
|
||||
%prep
|
||||
%setup -q
|
||||
%setup
|
||||
#patch -p0 -b .bms
|
||||
|
||||
%build
|
||||
@@ -234,6 +235,8 @@ mkdir -p $RPM_BUILD_ROOT%{libdir}
|
||||
cat >$RPM_BUILD_ROOT%{libdir}/start.sh <<'EOF'
|
||||
#!/bin/sh
|
||||
cd /var/log/milter
|
||||
# uncomment to enable sgmlop if installed
|
||||
#export PYTHONPATH=/usr/local/lib/python2.1/site-packages
|
||||
exec /usr/local/bin/python bms.py >>milter.log 2>&1
|
||||
EOF
|
||||
%else # not aix4.1
|
||||
@@ -279,7 +282,7 @@ rm -rf $RPM_BUILD_ROOT
|
||||
- Allow explicitly whitelisted email from banned_users.
|
||||
- configure gossip TTL
|
||||
* Mon Sep 24 2007 Stuart Gathman <stuart@bmsi.com> 0.8.9-1
|
||||
- Use ifarch hack to build milter and milter-spf packages as noarch
|
||||
- Use %ifarch hack to build milter and milter-spf packages as noarch
|
||||
- Remove spf dependency from dsn.py, add dns.py
|
||||
* Fri Jan 05 2007 Stuart Gathman <stuart@bmsi.com> 0.8.8-1
|
||||
- move AddrCache, parse_addr, iniplist to Milter package
|
||||
|
||||
@@ -16,7 +16,7 @@ if sys.version < '2.2.3':
|
||||
DistributionMetadata.download_url = None
|
||||
|
||||
# NOTE: importing Milter to obtain version fails when milter.so not built
|
||||
setup(name = "pymilter", version = '0.8.11',
|
||||
setup(name = "pymilter", version = '0.8.10',
|
||||
description="Python interface to sendmail milter API",
|
||||
long_description="""\
|
||||
This is a python extension module to enable python scripts to
|
||||
|
||||
Reference in New Issue
Block a user