Release 0.7.2

This commit is contained in:
Stuart Gathman
2005-05-31 18:10:47 +00:00
parent 16dea6e187
commit 20fb6efab0
11 changed files with 499 additions and 76 deletions
+1
View File
@@ -20,3 +20,4 @@ include start.sh
include milter.rc include milter.rc
include milter.rc7 include milter.rc7
include milter.cfg include milter.cfg
include rhsbl.m4
+9
View File
@@ -1,5 +1,14 @@
Here is a history of user visible changes to Python milter. Here is a history of user visible changes to Python milter.
0.7.2 Return unknown for invalid ip address in mechanism
Recognize dynamic PTR names, and don't count them as authentication.
Three strikes and yer out rule.
Block softfail by default when no PTR or HELO
Return unknown for null mechanism
Return unknown for invalid ip address in mechanism
Try best guess on HELO also
Expand setreply for common errors
make rhsbl.m4 hack available for sendmail.mc
0.7.1 Handle modifying mislabeled multipart messages without an exception 0.7.1 Handle modifying mislabeled multipart messages without an exception
Support setbacklog, setmlreply Support setbacklog, setmlreply
Allow multi-recipient CBV Allow multi-recipient CBV
+23 -2
View File
@@ -1,10 +1,30 @@
Checking in mime.py;
/bms/cvs/milter/mime.py,v <-- mime.py
new revision: 1.56; previous revision: 1.55
done
Checking in spf.py;
/bms/cvs/milter/spf.py,v <-- spf.py
new revision: 1.18; previous revision: 1.17
done
Checking in testmime.py;
/bms/cvs/milter/testmime.py,v <-- testmime.py
new revision: 1.19; previous revision: 1.18
Auto whitelist based on outgoing email - perhaps with magic subject
or recipient prefix.
Can't output messages with malformed rfc822 attachments.
Use python exceptions in SPF to cleanly handle unknown and error results.
Example malformed SPF:
onvunvuvvx.usafisnews.org text "v=spf1 mx ptr ip4:207.44.199.970 -all"
Move milter,Milter,mime,spf modules to pymilter Move milter,Milter,mime,spf modules to pymilter
milter package will have bms.py application milter package will have bms.py application
spf.py has no recursion bound on CNAME lookup
Support SMTP AUTH and disable SPF checks when connection is authorized. Support SMTP AUTH and disable SPF checks when connection is authorized.
Web admin interface Web admin interface
RHSBL
Check valid domains allowed by internal senders to detect PCs infected Check valid domains allowed by internal senders to detect PCs infected
with spam trojans. with spam trojans.
Do CBV (callback verification) for mail with no published SPF record. Do CBV (callback verification) for mail with no published SPF record.
@@ -52,3 +72,4 @@ Wrap smfi_setbacklog(int) - but it is only available in sendmail >= 8.12.3,
Need a test module to feed sample messages to a milter though a live Need a test module to feed sample messages to a milter though a live
sendmail and SMTP. The mockup currently used is probably not very accurate, sendmail and SMTP. The mockup currently used is probably not very accurate,
and doesn't test the threading code. and doesn't test the threading code.
+98 -12
View File
@@ -1,6 +1,30 @@
#!/usr/bin/env python #!/usr/bin/env python
# A simple milter. # A simple milter.
# $Log$ # $Log$
# Revision 1.126 2004/11/24 14:39:38 stuart
# Also accept softfail if valid PTR or HELO.
#
# Revision 1.125 2004/11/19 16:40:14 stuart
# Block softfail except for listed domains.
#
# Revision 1.124 2004/11/19 06:18:04 stuart
# block softfail for configured domains only
#
# Revision 1.123 2004/11/18 20:36:49 stuart
# Recognize more dynamic hosts. Ignore dynamic PTR for best_guess.
#
# Revision 1.122 2004/11/18 17:16:10 stuart
# Recognize more dynamic ips.
#
# Revision 1.121 2004/11/09 22:37:48 stuart
# Don't accept helo names which are dynamic IP addresses.
#
# Revision 1.120 2004/11/09 20:33:50 stuart
# Recognize more dynamic PTR variations.
#
# Revision 1.118 2004/08/30 21:19:50 stuart
# Try best guess for HELO, expand setreply for common errors
#
# Revision 1.117 2004/08/23 02:27:53 stuart # Revision 1.117 2004/08/23 02:27:53 stuart
# Allow multi rcpt CBV. Add some multiline replies. # Allow multi rcpt CBV. Add some multiline replies.
# #
@@ -292,6 +316,7 @@ srs = None
srs_reject_spoofed = False srs_reject_spoofed = False
srs_fwdomain = None srs_fwdomain = None
spf_reject_neutral = () spf_reject_neutral = ()
spf_accept_softfail = ()
spf_best_guess = False spf_best_guess = False
spf_reject_noptr = False spf_reject_noptr = False
timeout = 600 timeout = 600
@@ -401,6 +426,7 @@ def read_config(list):
global dspam_dict, dspam_users, dspam_userdir, dspam_exempt global dspam_dict, dspam_users, dspam_userdir, dspam_exempt
global dspam_screener,dspam_whitelist,dspam_reject,dspam_sizelimit global dspam_screener,dspam_whitelist,dspam_reject,dspam_sizelimit
global spf_reject_neutral,spf_best_guess,SRS,spf_reject_noptr global spf_reject_neutral,spf_best_guess,SRS,spf_reject_noptr
global spf_accept_softfail
dspam_dict = cp.getdefault('dspam','dspam_dict') dspam_dict = cp.getdefault('dspam','dspam_dict')
dspam_exempt = cp.getaddrset('dspam','dspam_exempt') dspam_exempt = cp.getaddrset('dspam','dspam_exempt')
dspam_whitelist = cp.getaddrset('dspam','dspam_whitelist') dspam_whitelist = cp.getaddrset('dspam','dspam_whitelist')
@@ -414,6 +440,7 @@ def read_config(list):
if spf: if spf:
spf.DELEGATE = cp.getdefault('spf','delegate') spf.DELEGATE = cp.getdefault('spf','delegate')
spf_reject_neutral = cp.getlist('spf','reject_neutral') spf_reject_neutral = cp.getlist('spf','reject_neutral')
spf_accept_softfail = cp.getlist('spf','accept_softfail')
spf_best_guess = cp.getboolean('spf','best_guess') spf_best_guess = cp.getboolean('spf','best_guess')
spf_reject_noptr = cp.getboolean('spf','reject_noptr') spf_reject_noptr = cp.getboolean('spf','reject_noptr')
srs_config = cp.getdefault('srs','config') srs_config = cp.getdefault('srs','config')
@@ -461,6 +488,43 @@ def parse_header(val):
except LookupError: pass except LookupError: pass
return val return val
ip3 = re.compile('([0-9]{1,3})[.-]([0-9]{1,3})[.-]([0-9]{1,3})')
rehmac = re.compile('h[0-9a-f]{12}[.]|pcp[0-9]{6,10}pcs[.]|no-reverse')
def dynip(host,addr):
"""Return True if hostname is for a dynamic ip.
Examples:
>>> is_dynip('post3.fabulousdealz.com','69.60.99.112')
False
>>> is_dynip('adsl-69-208-201-177.dsl.emhril.ameritech.net','69.208.201.177')
True
"""
if host.startswith('[') and host.endswith(']'):
return True
if addr:
if host.find(addr) >= 0: return True
a = addr.split('.')
m = ip3.search(host)
if m:
g = list(m.groups())
if g == a[1:] or g == a[:3]: return True
g.reverse()
if g == a[1:] or g == a[:3]: return True
if rehmac.search(host): return True
if host.find("-%s." % '-'.join(a[2:])) >= 0: return True
if host.find("w%s." % '-'.join(a[:2])) >= 0: return True
if host.find(''.join(a[:3])) >= 0: return True
if host.find(''.join(a[1:])) >= 0: return True
x = "%02x%02x%02x%02x" % tuple(map(int,a))
if host.lower().find(x) >= 0: return True
z = [n.zfill(3) for n in a]
if host.find('-'.join(z)) >= 0: return True
if host.find("-%s." % '-'.join(z[2:])) >= 0: return True
if host.find("%s." % ''.join(z[2:])) >= 0: return True
if host.find(''.join(z)) >= 0: return True
return False
class bmsMilter(Milter.Milter): class bmsMilter(Milter.Milter):
"""Milter to replace attachments poisonous to Windows with a WARNING message, """Milter to replace attachments poisonous to Windows with a WARNING message,
check SPF, and other anti-forgery features, and implement wiretapping check SPF, and other anti-forgery features, and implement wiretapping
@@ -500,7 +564,6 @@ class bmsMilter(Milter.Milter):
self.log('%s: %s' % (name,val)) self.log('%s: %s' % (name,val))
def connect(self,hostname,unused,hostaddr): def connect(self,hostname,unused,hostaddr):
self.missing_ptr = hostname.startswith('[') and hostname.endswith(']')
self.internal_connection = False self.internal_connection = False
self.trusted_relay = False self.trusted_relay = False
self.receiver = self.getsymval('j') self.receiver = self.getsymval('j')
@@ -517,6 +580,7 @@ class bmsMilter(Milter.Milter):
self.connectip = ipaddr self.connectip = ipaddr
else: else:
self.connectip = None self.connectip = None
self.missing_ptr = dynip(hostname,self.connectip)
for pat in internal_connect: for pat in internal_connect:
if fnmatchcase(hostname,pat): if fnmatchcase(hostname,pat):
self.internal_connection = True self.internal_connection = True
@@ -602,37 +666,51 @@ class bmsMilter(Milter.Milter):
q.set_default_explanation('SPF fail: see http://spf.pobox.com/why.html') q.set_default_explanation('SPF fail: see http://spf.pobox.com/why.html')
res,code,txt = q.check() res,code,txt = q.check()
receiver = self.receiver receiver = self.receiver
if res == 'none': if res in ('none', 'softfail'):
if self.mailfrom != '<>': if self.mailfrom != '<>':
# check hello name via spf # check hello name via spf
hres,hcode,htxt = spf.check(self.connectip,'',self.hello_name) h = spf.query(self.connectip,'',self.hello_name)
hres,hcode,htxt = h.check()
if hres in ('deny','fail','neutral','softfail'): if hres in ('deny','fail','neutral','softfail'):
self.log('REJECT: hello SPF: %s 550 %s' % (hres,htxt)) self.log('REJECT: hello SPF: %s 550 %s' % (hres,htxt))
self.setreply('550','5.7.1',htxt, self.setreply('550','5.7.1',htxt,
"The hostname given in your MTA's HELO response is not listed", "The hostname given in your MTA's HELO response is not listed",
"as a legitimate MTA in the SPF records for your domain.", "as a legitimate MTA in the SPF records for your domain. If you",
"If you get this bounce, the message was not in fact a forgery,", "get this bounce, the message was not in fact a forgery, and you",
"and you should notify your email administrator of the problem." "should IMMEDIATELY notify your email administrator of the problem."
) )
return Milter.REJECT return Milter.REJECT
if spf_best_guess: if hres == 'none' and spf_best_guess \
and not dynip(self.hello_name,self.connectip):
hres,hcode,htxt = h.best_guess()
else: hres = res
if spf_best_guess and res == 'none':
#self.log('SPF: no record published, guessing') #self.log('SPF: no record published, guessing')
q.set_default_explanation( q.set_default_explanation(
'SPF guess: see http://spf.pobox.com/why.html') 'SPF guess: see http://spf.pobox.com/why.html')
# best_guess should not result in fail # best_guess should not result in fail
if self.missing_ptr:
# ignore dynamic PTR for best guess
res,code,txt = q.best_guess('v=spf1 a/24 mx/24')
else:
res,code,txt = q.best_guess() res,code,txt = q.best_guess()
receiver += ': guessing' receiver += ': guessing'
if self.missing_ptr and res in ('neutral', 'none') and spf_reject_noptr: if self.missing_ptr and res in ('neutral', 'none') \
self.log('REJECT: no PTR or SPF') and spf_reject_noptr and hres != 'pass':
self.log('REJECT: no PTR, HELO or SPF')
self.setreply('550','5.7.1', self.setreply('550','5.7.1',
'You must have a reverse lookup or publish SPF: http://spf.pobox.com' 'You must have a reverse lookup or publish SPF: http://spf.pobox.com',
'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 HELO, and no SPF record."
) )
return Milter.REJECT return Milter.REJECT
if res in ('deny', 'fail'): if res in ('deny', 'fail'):
self.log('REJECT: SPF %s %i %s' % (res,code,txt)) self.log('REJECT: SPF %s %i %s' % (res,code,txt))
self.setreply(str(code),'5.7.1',txt) self.setreply(str(code),'5.7.1',txt)
return Milter.REJECT return Milter.REJECT
if res == 'softfail': if res == 'softfail' and not q.o in spf_accept_softfail:
if self.missing_ptr and spf_reject_noptr and hres != 'pass':
self.log('TEMPFAIL: SPF %s 450 %s' % (res,txt)) self.log('TEMPFAIL: SPF %s 450 %s' % (res,txt))
self.setreply('450','4.3.0', self.setreply('450','4.3.0',
'SPF softfail: will keep trying until your SPF record is fixed.', 'SPF softfail: will keep trying until your SPF record is fixed.',
@@ -640,7 +718,7 @@ class bmsMilter(Milter.Milter):
'legitimate. Your administrator has published SPF records in a', 'legitimate. Your administrator has published SPF records in a',
'testing mode. The SPF record reported your email as a forgery,', 'testing mode. The SPF record reported your email as a forgery,',
'which is a mistake if you are reading this. Please notify your', 'which is a mistake if you are reading this. Please notify your',
'administrator of the problem.' 'administrator of the problem immediately.'
) )
return Milter.TEMPFAIL return Milter.TEMPFAIL
if res == 'neutral' and q.o in spf_reject_neutral: if res == 'neutral' and q.o in spf_reject_neutral:
@@ -655,6 +733,11 @@ class bmsMilter(Milter.Milter):
) )
return Milter.REJECT return Milter.REJECT
if res == 'error': if res == 'error':
if code >= 500:
self.log('REJECT: SPF %s %i %s' % (res,code,txt))
self.setreply(str(code),'5.7.1',txt)
return Milter.REJECT
self.log('TEMPFAIL: SPF %s %i %s' % (res,code,txt))
self.setreply(str(code),'4.3.0',txt) self.setreply(str(code),'4.3.0',txt)
return Milter.TEMPFAIL return Milter.TEMPFAIL
self.add_header('Received-SPF',q.get_header(res,receiver)) self.add_header('Received-SPF',q.get_header(res,receiver))
@@ -985,6 +1068,9 @@ class bmsMilter(Milter.Milter):
self.tempname = None self.tempname = None
if exc_type == email.Errors.BoundaryError: if exc_type == email.Errors.BoundaryError:
self.log("MALFORMED: %s" % fname) # log filename self.log("MALFORMED: %s" % fname) # log filename
if self.internal_connection:
# accept anyway for now
return Milter.ACCEPT
self.setreply('554','5.7.7', self.setreply('554','5.7.7',
'Boundary error in your message, are you a spammer?') 'Boundary error in your message, are you a spammer?')
return Milter.REJECT return Milter.REJECT
+5 -2
View File
@@ -20,10 +20,11 @@ log_headers = 0
;check_user = joe@mycorp.com, mary@mycorp.com, file:bigcorp.com ;check_user = joe@mycorp.com, mary@mycorp.com, file:bigcorp.com
# reject mail with these case insensitive strings in the subject # reject mail with these case insensitive strings in the subject
porn_words = penis, breast, pussy, horse cock, porn, xenical, diet pill, d1ck, porn_words = penis, breast, pussy, horse cock, porn, xenical, diet pill, d1ck,
vi*gra, vi-a-gra, viag, tits, p0rn, hunza, horny, sexy, c0ck, vi*gra, vi-a-gra, viag, tits, p0rn, hunza, horny, sexy, c0ck, xanaax,
p-e-n-i-s, hydrocodone, vicodin, xanax, vicod1n, x@nax, diazepam, p-e-n-i-s, hydrocodone, vicodin, xanax, vicod1n, x@nax, diazepam,
v1@gra, xan@x, cialis, ci@lis, frëe, xãnax, valíum, vãlium, via-gra, v1@gra, xan@x, cialis, ci@lis, frëe, xãnax, valíum, vãlium, via-gra,
x@n3x, vicod3n, penís, c0d1n, phentermine, en1arge, dip1oma, v1codin x@n3x, vicod3n, penís, c0d1n, phentermine, en1arge, dip1oma, v1codin,
valium, rolex
# reject mail with these case sensitive strings in the subject # reject mail with these case sensitive strings in the subject
spam_words = $$$, !!!, XXX, FREE, HGH spam_words = $$$, !!!, XXX, FREE, HGH
@@ -68,6 +69,8 @@ reject_spoofed = 0
;best_guess = 0 ;best_guess = 0
# reject senders that have neither PTR nor SPF records # reject senders that have neither PTR nor SPF records
;reject_noptr = 0 ;reject_noptr = 0
# always accept softfail from these domains
;accept_softfail = bounces.amazon.com
# features intended to clean up outgoing mail # features intended to clean up outgoing mail
[scrub] [scrub]
+75 -7
View File
@@ -24,7 +24,7 @@ ALT="Viewable With Any Browser" BORDER="0"></A>
Stuart D. Gathman</a><br> Stuart D. Gathman</a><br>
This web page is written by Stuart D. Gathman<br>and<br>sponsored by 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> <a href="http://www.bmsi.com">Business Management Systems, Inc.</a> <br>
Last updated Aug 06, 2004</h4> Last updated Nov 24, 2004</h4>
See the <a href="faq.html">FAQ</a> | <a href="#download">Download now</a> | See the <a href="faq.html">FAQ</a> | <a href="#download">Download now</a> |
<a href="/mailman/listinfo/pymilter">Subscribe to mailing list</a> | <a href="/mailman/listinfo/pymilter">Subscribe to mailing list</a> |
@@ -43,14 +43,52 @@ separation features to enhance security.
I recommend upgrading. I recommend upgrading.
<h2> Recent Changes </h2> <h2> Recent Changes </h2>
Release 0.7.2 tightens the authentication screws with a "3 strikes and
your out" policy. A sender must have a valid PTR, HELO, or SPF record
to send email. Specific senders can be whitelisted using the
"delegate" option in the spf configuration section by adding a
default SPF record for them. The PTR and HELO are required
by RFC anyway, so this is not an unreasonable requirement.
There is now a coherent policy for an SPF softfail result. A softfail
is accepted if there is a valid PTR or HELO, or if the domain
is listed in the "accept_softfail" option of the spf configuration section.
A neutral result is accepted by default if there is a valid PTR or
HELO, (and the SPF record was not guessed), unless the domain is listed in the
"reject_neutral" option. Common forms of PTR records for dynamic IPs are
recognized, and do not count as a valid PTR. This does not prevent anyone
from sending mail from a dynamic IP - they just need to configure a
valid HELO name or publish an SPF record.
<p>
The RPM for release 0.7.0 moves the config file and socket locations to The RPM for release 0.7.0 moves the config file and socket locations to
/etc/mail and /var/run/milter respectively. We now parse Microsoft CID records /etc/mail and /var/run/milter respectively. We now parse Microsoft CID records
- but only hotmail.com uses them. They seem to have a patent on the brilliant - but only hotmail.com uses them. They seem to have applied for a patent on
idea of examining the mail headers to see who the message is from. the brilliant idea of examining the mail headers to see who the message is
We aren't doing that here, so not to worry - but I am not a lawyer, so if you from. We aren't doing that here, so not to worry - but I am not a lawyer, so
are worried, change spf.py around line 626 to return None instead of if you are worried, change spf.py around line 626 to return None instead of
calling CIDParser(). There is a new option to reject mail with no PTR calling CIDParser(). There is a new option to reject mail with no PTR
and no SPF. and no SPF.
<p>
Microsoft is pushing an anti-opensource license for their pending patent
along with their sender-ID proposal before the IETF.
It is royalty free - but requires anyone distributing a binary they've
compiled from source to sign a license agreement. The Apache Software
Foundation <a
href="http://www.apache.org/foundation/docs/sender-id-position.html"> explains
the problem with sender-ID</a>, and Debian <a
href="http://www.debian.org/News/2004/20040904">concurs</a>. Since
the <a href="http://download.microsoft.com/download/4/3/9/439b024b-09fd-44ee-8ff0-10e834004c36/senderid_FAQ.PDF">Microsoft license</a> is
<a href="http://www.circleid.com/article/732_0_1_0_C/">incompatible with free
software in general</a> and the <a
href="http://www.imc.org/ietf-mxcomp/mail-archive/msg03678.html">GPL in
particular</a>, Python milter will not be able to implement sender-ID in its
current form. This was, no doubt, Microsoft's intent all along.
<p>
Sender-ID attempts to do for RFC2822 headers what SPF does for RFC2821 headers.
Unlike SPF, it has never been tried, and is encumbered by a stupid patent. I
recommend ignoring it and continuing to implement and improve SPF until a
working and unencumbered proposal for RFC2822 headers surfaces.
<p> <p>
<a href="http://spf.pobox.com"> <a href="http://spf.pobox.com">
<img src="SPF.gif" align=left alt="SPF logo"></a> <img src="SPF.gif" align=left alt="SPF logo"></a>
@@ -168,13 +206,40 @@ in the <a href="milter.cfg">sample config file</a> for the bms.py milter.
<h3><a name=download>Downloading</a></h3> <h3><a name=download>Downloading</a></h3>
The latest stable release is <a href="#stable">0.7.0</a>. A stable The latest stable release is <a href="#stable">0.7.2</a>. A stable
release is one which has been installed (and working correctly) on release is one which has been installed (and working correctly) on
production systems long enough to convince me that it is stable. As production systems long enough to convince me that it is stable. As
the package gains more features and complexity, stable will mean no the package gains more features and complexity, stable will mean no
bug reports from outside users either. bug reports from outside users either.
<p> <p>
The latest version is 0.7.0-1. See the <a href=NEWS>Change Log</a>. The latest version is 0.7.2-2. See the <a href=NEWS>Change Log</a>.
PLEASE NOTE - if you are using the modules, but not the bms milter application,
then ignore the RPMs and milter.spec. Use 'python setup.py bdist_rpm' to
build source and binary rpms that do not include the milter application.
<p>
I want to split the bms milter application to a new project once I figure
out the renaming. The current plan is to rename 'milter' to 'pymilter', which
will have the Python modules. The bms milter application will still be named
'milter' and depend on pymilter (so that my installs won't notice anything).
<p>
<a name="stable"><b>Stable</b></a>
<a href="http://bmsi.com/python/milter-0.7.2.tar.gz">
milter-0.7.2.tar.gz</a> Three strikes and your out policy. Some SPF fixes.
Recognizes PTR records for dynamic IPs.
<p>
<a href="http://bmsi.com/python/milter-0.7.1.tar.gz">
milter-0.7.1.tar.gz</a> Support setmlreply, handle some more exceptions
for malformed spam. Compiling pymilter with sendmail-8.12.10, requires
sendmail-devel with _FFR_MULTILINE set. The binary will work with older
sendmails. The _FFR_MULTILINE option only affects libmilter.a.
<br>
<a href="http://bmsi.com/linux/rh72/milter-0.7.1-1.i386.rpm">
milter-0.7.1-1.i386.rpm</a> Binary RPM for Redhat 7.x, now requires
sendmail-8.12 and <a href="http://www.python.org/2.3.3/rpms.html">
python2.3</a>.
<br>
<a href="http://bmsi.com/linux/rh9/milter-0.7.1-1.src.rpm">
milter-0.7.1-1.src.rpm</a> Source RPM for Redhat 9,7.x.
<p> <p>
<a name="stable"><b>Stable</b></a> <a name="stable"><b>Stable</b></a>
<a href="http://bmsi.com/python/milter-0.7.0.tar.gz"> <a href="http://bmsi.com/python/milter-0.7.0.tar.gz">
@@ -191,6 +256,9 @@ milter-0.7.0-1rh9.i386.rpm</a> Binary RPM for Redhat 9, requires
sendmail-8.12 and <a href="http://www.python.org/2.3.3/rpms.html"> sendmail-8.12 and <a href="http://www.python.org/2.3.3/rpms.html">
python2.3</a>. python2.3</a>.
<br> <br>
<a href="http://bmsi.com/aix/milter-0.7.0-1.ppc.rpm">
milter-0.7.0-1.ppc.rpm</a> Binary RPM for AIX, requires sendmail-8.13.1.
<br>
<a href="http://bmsi.com/linux/rh9/milter-0.7.0-1.src.rpm"> <a href="http://bmsi.com/linux/rh9/milter-0.7.0-1.src.rpm">
milter-0.7.0-1.src.rpm</a> Source RPM for Redhat 9,7.x. milter-0.7.0-1.src.rpm</a> Source RPM for Redhat 9,7.x.
<p> <p>
+31 -4
View File
@@ -1,6 +1,6 @@
%define name milter %define name milter
%define version 0.7.1 %define version 0.7.2
%define release 1 %define release 2
# Redhat 7.x and earlier (multiple ps lines per thread) # Redhat 7.x and earlier (multiple ps lines per thread)
%define sysvinit milter.rc7 %define sysvinit milter.rc7
# RH9, other systems (single ps line per process) # RH9, other systems (single ps line per process)
@@ -25,6 +25,9 @@ Vendor: Stuart D. Gathman <stuart@bmsi.com>
Packager: Stuart D. Gathman <stuart@bmsi.com> Packager: Stuart D. Gathman <stuart@bmsi.com>
Url: http://www.bmsi.com/python/milter.html Url: http://www.bmsi.com/python/milter.html
Requires: %{python} >= 2.2.2, sendmail >= 8.12.10 Requires: %{python} >= 2.2.2, sendmail >= 8.12.10
%ifnos aix4.1
Requires: chkconfig
%endif
BuildRequires: %{python}-devel >= 2.2.2, sendmail-devel >= 8.12.10 BuildRequires: %{python}-devel >= 2.2.2, sendmail-devel >= 8.12.10
%description %description
@@ -59,10 +62,15 @@ EOF
# purge saved defanged message copies # purge saved defanged message copies
mkdir -p $RPM_BUILD_ROOT/etc/cron.daily mkdir -p $RPM_BUILD_ROOT/etc/cron.daily
%ifos aix4.1
R=
%else
R='-r'
%endif
cat >$RPM_BUILD_ROOT/etc/cron.daily/milter <<'EOF' cat >$RPM_BUILD_ROOT/etc/cron.daily/milter <<'EOF'
#!/bin/sh #!/bin/sh
find /var/log/milter/save -mtime +7 | xargs -r rm find /var/log/milter/save -mtime +7 | xargs $R rm
EOF EOF
chmod a+x $RPM_BUILD_ROOT/etc/cron.daily/milter chmod a+x $RPM_BUILD_ROOT/etc/cron.daily/milter
@@ -96,6 +104,8 @@ EOF
chmod a+x $RPM_BUILD_ROOT/var/log/milter/start.sh chmod a+x $RPM_BUILD_ROOT/var/log/milter/start.sh
mkdir -p $RPM_BUILD_ROOT/var/run/milter mkdir -p $RPM_BUILD_ROOT/var/run/milter
mkdir -p $RPM_BUILD_ROOT/usr/share/sendmail-cf/hack
cp -p rhsbl.m4 $RPM_BUILD_ROOT/usr/share/sendmail-cf/hack
%ifos aix4.1 %ifos aix4.1
%post %post
@@ -107,7 +117,13 @@ if [ $1 = 0 ]; then
fi fi
%else %else
%post %post
echo "pythonsock has moved to /var/run/milter, update /etc/mail/sendmail.cf" #echo "pythonsock has moved to /var/run/milter, update /etc/mail/sendmail.cf"
/sbin/chkconfig --add milter
%preun
if [ $1 = 0 ]; then
/sbin/chkconfig --del milter
fi
%endif %endif
%clean %clean
@@ -130,8 +146,19 @@ rm -rf $RPM_BUILD_ROOT
%config /var/log/milter/start.sh %config /var/log/milter/start.sh
%config /var/log/milter/bms.py %config /var/log/milter/bms.py
%config(noreplace) /etc/mail/pymilter.cfg %config(noreplace) /etc/mail/pymilter.cfg
/usr/share/sendmail-cf/hack/rhsbl.m4
%changelog %changelog
* Mon Aug 30 2004 Stuart Gathman <stuart@bmsi.com> 0.7.2-1
- Fix various SPF bugs
- Recognize dynamic PTR names, and don't count them as authentication.
- Three strikes and yer out rule.
- Block softfail by default unless valid PTR or HELO
- Return unknown for null mechanism
- Return unknown for invalid ip address in mechanism
- Try best guess on HELO also
- Expand setreply for common errors
- make rhsbl.m4 hack available for sendmail.mc
* Sun Aug 22 2004 Stuart Gathman <stuart@bmsi.com> 0.7.1-1 * Sun Aug 22 2004 Stuart Gathman <stuart@bmsi.com> 0.7.1-1
- Handle modifying mislabeled multipart messages without an exception - Handle modifying mislabeled multipart messages without an exception
- Support setbacklog, setmlreply - Support setbacklog, setmlreply
+44
View File
@@ -0,0 +1,44 @@
divert(-1)
#
# Copyright (c) 2002 Derek J. Balling
# All rights reserved.
#
# Permission to use granted for all purposes. If modifications are made
# they are requested to be sent to <dredd@megacity.org> for inclusion in future
# versions
#
# Allows (hopefully) for checking of access.db whitelisting now. This ONLY
# works on sendmail-8.12.x ... use on any other version may require tinkering
# by you the downloader.
#
# Incorporates many changes by Sergey S. Mokryshev <mokr@mokr.net>
#
#
divert(0)
ifdef(`_RHSBL_R_',`dnl',`dnl
VERSIONID(`$Id$')
define(`_RHSBL_R_',`')
ifdef(`_DNSBL_R_',`dnl',`dnl
LOCAL_CONFIG
# map for DNS based blacklist lookups based on the sender RHS
Kdnsbl host -T<TMP>')')
divert(-1)
define(`_RHSBL_SRV_', `_ARG_')dnl
define(`_RHSBL_MSG_', `ifelse(len(X`'_ARG2_),`1',`"550 Mail from " $`'&{RHS} " refused by blackhole site '_RHSBL_SRV_`"',`_ARG2_')')dnl
define(`_RHSBL_MSG_TMP_', `ifelse(_ARG3_,`t',`"451 Temporary lookup failure of " $`'&{RHS} " at '_RHSBL_SRV_`"',`_ARG3_')')dnl
MAILER_DEFINITIONS
SLocal_check_mail
# DNS based RHS spam list blackholes.bmsi.com
R$* $: <?> $>CanonAddr $1
R<?> $*<@$+.> $: <?> $1<@$2.> $| $>SearchList <+ rhs> $| <F:$1@$2> <D:$2> <>
R<?> $* $| <$={Accept}> $: OKSOFAR
R<?> $*<@$+.> $| $* $: <?> $(dnsbl $2._RHSBL_SRV_. $: OK $) $(macro {RHS} $@ $2 $)
R<?> OK $: OKSOFAR
R<?> $*<@$*> $: OKSOFAR
ifelse(len(X`'_ARG3_),`1',
`R<?>$+<TMP> $: TMPOK',
`R<?>$+<TMP> $#error $@ 4.7.1 $: _RHSBL_MSG_TMP_')
R<?>$+ $#error $@ 5.7.1 $: _RHSBL_MSG_
+1 -1
View File
@@ -12,7 +12,7 @@ if sys.version < '2.2.3':
DistributionMetadata.classifiers = None DistributionMetadata.classifiers = None
DistributionMetadata.download_url = None DistributionMetadata.download_url = None
setup(name = "milter", version = "0.7.1", setup(name = "milter", version = "0.7.2",
description="Python interface to sendmail milter API", description="Python interface to sendmail milter API",
long_description="""\ long_description="""\
This is a python extension module to enable python scripts to This is a python extension module to enable python scripts to
+61 -25
View File
@@ -45,6 +45,25 @@ For news, bugfixes, etc. visit the home page for this implementation at
# Terrence is not responding to email. # Terrence is not responding to email.
# #
# $Log$ # $Log$
# Revision 1.21 2004/11/20 16:37:03 stuart
# Handle multi-segment TXT records.
#
# Revision 1.20 2004/11/19 06:10:30 stuart
# Use PermError exception instead of reporting unknown.
#
# Revision 1.19 2004/11/09 23:00:18 stuart
# Limit recursion and DNS lookups separately.
#
#
# Revision 1.17 2004/09/10 18:08:26 stuart
# Return unknown for null mechanism
#
# Revision 1.16 2004/09/04 23:27:06 stuart
# More mechanism aliases.
#
# Revision 1.15 2004/08/30 21:19:05 stuart
# Return unknown for invalid ip syntax in mechanism
#
# Revision 1.14 2004/08/23 02:28:24 stuart # Revision 1.14 2004/08/23 02:28:24 stuart
# Remove Perl usage message. # Remove Perl usage message.
# #
@@ -120,7 +139,7 @@ import xml.sax
# Date: 2004-02-25 # Date: 2004-02-25
# #
# A complete reverse translation (SPF -> CID) might be impossible, since # A complete reverse translation (SPF -> CID) might be impossible, since
# there are no way to handle: # there are no ways to handle:
# - PTR and EXISTS mechanism # - PTR and EXISTS mechanism
# - MX mechanism with an different domain as argument # - MX mechanism with an different domain as argument
# - macros # - macros
@@ -284,6 +303,16 @@ except NameError:
# standard default SPF record # standard default SPF record
DEFAULT_SPF = 'v=spf1 a/24 mx/24 ptr' DEFAULT_SPF = 'v=spf1 a/24 mx/24 ptr'
# maximum DNS lookups allowed
MAX_LOOKUP = 50
MAX_RECURSION = 20
class TempError(Exception):
"Temporary SPF error"
class PermError(Exception):
"Permanent SPF error"
def check(i, s, h,local=None): def check(i, s, h,local=None):
"""Test an incoming MAIL FROM:<s>, from a client with ip address i. """Test an incoming MAIL FROM:<s>, from a client with ip address i.
h is the HELO/EHLO domain name. h is the HELO/EHLO domain name.
@@ -329,6 +358,7 @@ class query(object):
self.cache = {} self.cache = {}
self.exps = dict(EXPLANATIONS) self.exps = dict(EXPLANATIONS)
self.local = local # local policy self.local = local # local policy
self.lookups = 0
def set_default_explanation(self,exp): def set_default_explanation(self,exp):
exps = self.exps exps = self.exps
@@ -357,27 +387,32 @@ class query(object):
return ('pass', 250, 'local connections always pass') return ('pass', 250, 'local connections always pass')
try: try:
self.lookups = 0
if not spf: if not spf:
spf = self.dns_spf(self.d) spf = self.dns_spf(self.d)
if self.local and spf: if self.local and spf:
spf += ' ' + self.local spf += ' ' + self.local
return self.check1(spf, self.d, 0) return self.check1(spf, self.d, 0)
except DNS.DNSError: except DNS.DNSError,x:
return ('error', 450, 'SPF DNS Error') return ('error', 450, 'SPF DNS Error: ' + str(x))
except TempError,x:
return ('error', 450, 'SPF Temporary Error: ' + str(x))
except PermError,x:
return ('error', 550, 'SPF Permanent Error: ' + str(x))
def check1(self, spf, domain, recursion): def check1(self, spf, domain, recursion):
# spf rfc: 3.7 Processing Limits # spf rfc: 3.7 Processing Limits
# #
if recursion > 20: if recursion > MAX_RECURSION:
self.prob = 'Mechanisms used too many DNS lookups' self.prob = 'Too many levels of recursion'
return ('unknown', 250, 'SPF recursion limit exceeded') return ('unknown', 250, 'SPF recursion limit exceeded')
try: try:
tmp, self.d = self.d, domain tmp, self.d = self.d, domain
return self.check0(spf, recursion) return self.check0(spf,recursion)
finally: finally:
self.d = tmp self.d = tmp
def check0(self, spf, recursion): def check0(self, spf,recursion):
"""Test this query information against SPF text. """Test this query information against SPF text.
Returns (result, mta-status-code, explanation) where Returns (result, mta-status-code, explanation) where
@@ -425,6 +460,7 @@ class query(object):
m, arg, cidrlength = parse_mechanism(mech, self.d) m, arg, cidrlength = parse_mechanism(mech, self.d)
# map '?' '+' or '-' to 'unknown' 'pass' or 'fail' # map '?' '+' or '-' to 'unknown' 'pass' or 'fail'
if m:
result = RESULTS.get(m[0]) result = RESULTS.get(m[0])
if result: if result:
# eat '?' '+' or '-' # eat '?' '+' or '-'
@@ -433,7 +469,7 @@ class query(object):
# default pass # default pass
result = 'pass' result = 'pass'
if m in ['a', 'mx', 'ptr', 'exists', 'include']: if m in ['a', 'mx', 'ptr', 'prt', 'exists', 'include']:
arg = self.expand(arg) arg = self.expand(arg)
if m == 'include': if m == 'include':
@@ -442,18 +478,12 @@ class query(object):
arg, recursion + 1) arg, recursion + 1)
if res == 'pass': if res == 'pass':
break break
if res in ('fail','neutral','softfail'):
continue
if res == 'none': if res == 'none':
self.prob = \ raise PermError(
'Could not find a valid SPF record' 'No valid SPF record for included domain')
res = 'unknown' continue
return res,code,txt
else: else:
self.prob = 'Required option is missing' raise PermError('include mechanism missing domain')
self.mech.append(mech)
return ('unknown', 250, 'missing SPF option')
elif m == 'all': elif m == 'all':
break break
@@ -472,9 +502,15 @@ class query(object):
break break
elif m in ('ip4', 'ipv4', 'ip') and arg != self.d: elif m in ('ip4', 'ipv4', 'ip') and arg != self.d:
try:
if cidrmatch(self.i, [arg], cidrlength): if cidrmatch(self.i, [arg], cidrlength):
break break
elif m == 'ip6': except socket.error:
self.mech.append(mech)
self.prob = 'Bad mechanism syntax found'
return ('unknown',250,'SPF mechanism syntax error')
elif m in ('ip6', 'ipv6'):
# Until we support IPV6, we should never # Until we support IPV6, we should never
# get an IPv6 connection. So this mech # get an IPv6 connection. So this mech
# will never match. # will never match.
@@ -488,15 +524,12 @@ class query(object):
else: else:
# unknown mechanisms cause immediate unknown # unknown mechanisms cause immediate unknown
# abort results # abort results
self.mech.append(mech) raise PermError('Unknown mechanism found: ' + mech)
self.prob = 'Unknown mechanism found'
return ('unknown',250,'unknown SPF mechanism')
else: else:
# no matches # no matches
if redirect: if redirect:
return self.check1(self.dns_spf(redirect), return self.check1(self.dns_spf(redirect),
redirect, recursion+1) redirect, recursion + 1)
else: else:
result = default result = default
@@ -630,7 +663,7 @@ class query(object):
def dns_txt(self, domainname): def dns_txt(self, domainname):
"Get a list of TXT records for a domain name." "Get a list of TXT records for a domain name."
if domainname: if domainname:
return [t for a in self.dns(domainname, 'TXT') for t in a] return [''.join(a) for a in self.dns(domainname, 'TXT')]
return [] return []
def dns_mx(self, domainname): def dns_mx(self, domainname):
@@ -672,6 +705,9 @@ class query(object):
pre: qtype in ['A', 'AAAA', 'MX', 'PTR', 'TXT', 'SPF'] pre: qtype in ['A', 'AAAA', 'MX', 'PTR', 'TXT', 'SPF']
post: isinstance(__return__, types.ListType) post: isinstance(__return__, types.ListType)
""" """
self.lookups += 1
if self.lookups > MAX_LOOKUP:
raise PermError('Too many DNS lookups')
result = self.cache.get( (name, qtype) ) result = self.cache.get( (name, qtype) )
cname = None cname = None
if not result: if not result:
+128
View File
@@ -0,0 +1,128 @@
From leec@windowsshop.com Fri Sep 10 11:48:25 2004
Message-ID: <4141CDD4.7040305@windowsshop.com>
Date: Fri, 10 Sep 2004 11:52:52 -0400
From: Lee Connor <leec@windowsshop.com>
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.0; en-US; rv:1.4) Gecko/20030624 Netscape/7.1 (ax)
X-Accept-Language: en-us, en
MIME-Version: 1.0
To: Cleo Matthews-Conley <cleom@windowsshop.com>,
Tony Collini <tonyc@windowsshop.com>,
John Higinbothom <johnh@windowsshop.com>
CC: Rich Higgins <richh@windowsshop.com>
Subject: [Fwd: [Fwd: Customer Concerns]]
Content-Type: multipart/mixed;
boundary="------------020209070802060007090105"
This is a multi-part message in MIME format.
--------------020209070802060007090105
Content-Type: text/plain; charset=us-ascii; format=flowed
Content-Transfer-Encoding: 7bit
Cleo - please review attached feedback from Sales team.......I recall at
an early meeting after we moved in you and Tony (and maybe 1 or 2
others) were going to develop a voice mail procedure or instruction
sheet for all staff. It looks like we really need this to get what we
are looking for from the system. Please let me know when you can produce
this and give a draft to the managers here for review.
Thanks,
Lee
--------------020209070802060007090105
Content-Type: message/rfc822;
name="[Fwd: Customer Concerns]"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
filename="[Fwd: Customer Concerns]"
Return-Path: <richh@windowsshop.com>
Received: from windowsshop.com (pc147.windowsshop.com [192.168.100.147] (may be forged))
by lord.windowsshop.com (8.12.10/8.12.10) with ESMTP id i89KCClX003425
for <leec@windowsshop.com>; Thu, 9 Sep 2004 16:12:12 -0400
Message-ID: <4140B851.3020501@windowsshop.com>
Date: Thu, 09 Sep 2004 16:08:49 -0400
From: Rich <richh@windowsshop.com>
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.0.2) Gecko/20021120 Netscape/7.01
X-Accept-Language: en-us, en
MIME-Version: 1.0
To: Lee Connor <leec@windowsshop.com>
Subject: [Fwd: Customer Concerns]
Content-Type: multipart/mixed;
boundary="------------030301030706020401010801"
X-DSpam-Score: 0.000000
This is a multi-part message in MIME format.
--------------030301030706020401010801
Content-Type: text/plain; charset=us-ascii; format=flowed
Content-Transfer-Encoding: 7bit
Lee - do you want me to do anything else with this?
Rich
<!DSPAM:FEE4D3278234264874834386>
--------------030301030706020401010801
Content-Type: message/rfc822; name="Customer Concerns";
boundary="===============0045392615=="
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
filename="Customer Concerns"
Return-Path: <joes@windowsshop.com>
Received: from joes (pc148.windowsshop.com [192.168.100.148] (may be forged))
by lord.windowsshop.com (8.12.10/8.12.10) with SMTP id i89K9BlX003262
for <richh@windowsshop.com>; Thu, 9 Sep 2004 16:09:11 -0400
From: "Joe Schmuck" <joes@windowsshop.com>
To: <richh@windowsshop.com>
Subject: Customer Concerns
Date: Thu, 9 Sep 2004 16:08:26 -0400
Message-ID: <OFEPKHCCLPIECLFBLDHBAEAECAAA.joes@windowsshop.com>
MIME-Version: 1.0
Content-Type: text/plain;
charset="iso-8859-1"
Content-Transfer-Encoding: 7bit
X-Priority: 3 (Normal)
X-MSMail-Priority: Normal
X-Mailer: Microsoft Outlook IMO, Build 9.0.2416 (9.0.2910.0)
Importance: Normal
X-MimeOLE: Produced By Microsoft MimeOLE V6.00.2800.1106
X-DSpam-Score: 0.000000
Rich:
Following is a summary of concerns from customers regarding internal
communications within WS:
- Not all employees have activated their voice mail - when this is the
case, the system will automatically cut you off
- When employees are out of the office, phones are not forwarded to a back
up, ie manager
- Reception has no record of employee attendance, and therefore will
forward call to individual requested - see point 2
- Reception directs calls to incorrect individuals
- When entering voice mail, if you press '0', system does not default to
operator, but puts you back into individual voice mail
- Reception phone demeanor has no 'pep'
Thanks
Joe
---
Outgoing mail is certified Virus Free.
Checked by AVG anti-virus system (http://www.grisoft.com).
Version: 6.0.752 / Virus Database: 503 - Release Date: 9/3/2004
<!DSPAM:FEE4D05F1332634871908793>
--===============0045392615==--
--------------030301030706020401010801--
--------------020209070802060007090105--