Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 20fb6efab0 |
@@ -20,3 +20,4 @@ include start.sh
|
||||
include milter.rc
|
||||
include milter.rc7
|
||||
include milter.cfg
|
||||
include rhsbl.m4
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
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
|
||||
Support setbacklog, setmlreply
|
||||
Allow multi-recipient CBV
|
||||
|
||||
@@ -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
|
||||
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.
|
||||
Web admin interface
|
||||
RHSBL
|
||||
Check valid domains allowed by internal senders to detect PCs infected
|
||||
with spam trojans.
|
||||
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
|
||||
sendmail and SMTP. The mockup currently used is probably not very accurate,
|
||||
and doesn't test the threading code.
|
||||
|
||||
|
||||
@@ -1,6 +1,30 @@
|
||||
#!/usr/bin/env python
|
||||
# A simple milter.
|
||||
# $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
|
||||
# Allow multi rcpt CBV. Add some multiline replies.
|
||||
#
|
||||
@@ -292,6 +316,7 @@ srs = None
|
||||
srs_reject_spoofed = False
|
||||
srs_fwdomain = None
|
||||
spf_reject_neutral = ()
|
||||
spf_accept_softfail = ()
|
||||
spf_best_guess = False
|
||||
spf_reject_noptr = False
|
||||
timeout = 600
|
||||
@@ -401,6 +426,7 @@ def read_config(list):
|
||||
global dspam_dict, dspam_users, dspam_userdir, dspam_exempt
|
||||
global dspam_screener,dspam_whitelist,dspam_reject,dspam_sizelimit
|
||||
global spf_reject_neutral,spf_best_guess,SRS,spf_reject_noptr
|
||||
global spf_accept_softfail
|
||||
dspam_dict = cp.getdefault('dspam','dspam_dict')
|
||||
dspam_exempt = cp.getaddrset('dspam','dspam_exempt')
|
||||
dspam_whitelist = cp.getaddrset('dspam','dspam_whitelist')
|
||||
@@ -414,6 +440,7 @@ def read_config(list):
|
||||
if spf:
|
||||
spf.DELEGATE = cp.getdefault('spf','delegate')
|
||||
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_reject_noptr = cp.getboolean('spf','reject_noptr')
|
||||
srs_config = cp.getdefault('srs','config')
|
||||
@@ -461,6 +488,43 @@ def parse_header(val):
|
||||
except LookupError: pass
|
||||
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):
|
||||
"""Milter to replace attachments poisonous to Windows with a WARNING message,
|
||||
check SPF, and other anti-forgery features, and implement wiretapping
|
||||
@@ -500,7 +564,6 @@ class bmsMilter(Milter.Milter):
|
||||
self.log('%s: %s' % (name,val))
|
||||
|
||||
def connect(self,hostname,unused,hostaddr):
|
||||
self.missing_ptr = hostname.startswith('[') and hostname.endswith(']')
|
||||
self.internal_connection = False
|
||||
self.trusted_relay = False
|
||||
self.receiver = self.getsymval('j')
|
||||
@@ -517,6 +580,7 @@ class bmsMilter(Milter.Milter):
|
||||
self.connectip = ipaddr
|
||||
else:
|
||||
self.connectip = None
|
||||
self.missing_ptr = dynip(hostname,self.connectip)
|
||||
for pat in internal_connect:
|
||||
if fnmatchcase(hostname,pat):
|
||||
self.internal_connection = True
|
||||
@@ -602,47 +666,61 @@ class bmsMilter(Milter.Milter):
|
||||
q.set_default_explanation('SPF fail: see http://spf.pobox.com/why.html')
|
||||
res,code,txt = q.check()
|
||||
receiver = self.receiver
|
||||
if res == 'none':
|
||||
if res in ('none', 'softfail'):
|
||||
if self.mailfrom != '<>':
|
||||
# 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'):
|
||||
self.log('REJECT: hello SPF: %s 550 %s' % (hres,htxt))
|
||||
self.setreply('550','5.7.1',htxt,
|
||||
"The hostname given in your MTA's HELO response is not listed",
|
||||
"as a legitimate MTA in the SPF records for your domain.",
|
||||
"If you get this bounce, the message was not in fact a forgery,",
|
||||
"and you should notify your email administrator of the problem."
|
||||
"as a legitimate MTA in the SPF records for your domain. If you",
|
||||
"get this bounce, the message was not in fact a forgery, and you",
|
||||
"should IMMEDIATELY notify your email administrator of the problem."
|
||||
)
|
||||
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')
|
||||
q.set_default_explanation(
|
||||
'SPF guess: see http://spf.pobox.com/why.html')
|
||||
# best_guess should not result in fail
|
||||
res,code,txt = q.best_guess()
|
||||
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()
|
||||
receiver += ': guessing'
|
||||
if self.missing_ptr and res in ('neutral', 'none') and spf_reject_noptr:
|
||||
self.log('REJECT: no PTR or SPF')
|
||||
if self.missing_ptr and res in ('neutral', 'none') \
|
||||
and spf_reject_noptr and hres != 'pass':
|
||||
self.log('REJECT: no PTR, HELO or SPF')
|
||||
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
|
||||
if res in ('deny', 'fail'):
|
||||
self.log('REJECT: SPF %s %i %s' % (res,code,txt))
|
||||
self.setreply(str(code),'5.7.1',txt)
|
||||
return Milter.REJECT
|
||||
if res == 'softfail':
|
||||
self.log('TEMPFAIL: SPF %s 450 %s' % (res,txt))
|
||||
self.setreply('450','4.3.0',
|
||||
'SPF softfail: will keep trying until your SPF record is fixed.',
|
||||
'If you get this Delivery Status Notice, your email was probably',
|
||||
'legitimate. Your administrator has published SPF records in a',
|
||||
'testing mode. The SPF record reported your email as a forgery,',
|
||||
'which is a mistake if you are reading this. Please notify your',
|
||||
'administrator of the problem.'
|
||||
)
|
||||
return Milter.TEMPFAIL
|
||||
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.setreply('450','4.3.0',
|
||||
'SPF softfail: will keep trying until your SPF record is fixed.',
|
||||
'If you get this Delivery Status Notice, your email was probably',
|
||||
'legitimate. Your administrator has published SPF records in a',
|
||||
'testing mode. The SPF record reported your email as a forgery,',
|
||||
'which is a mistake if you are reading this. Please notify your',
|
||||
'administrator of the problem immediately.'
|
||||
)
|
||||
return Milter.TEMPFAIL
|
||||
if res == 'neutral' and q.o in spf_reject_neutral:
|
||||
self.log('REJECT: SPF neutral for',q.s)
|
||||
self.setreply('550','5.7.1',
|
||||
@@ -655,6 +733,11 @@ class bmsMilter(Milter.Milter):
|
||||
)
|
||||
return Milter.REJECT
|
||||
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)
|
||||
return Milter.TEMPFAIL
|
||||
self.add_header('Received-SPF',q.get_header(res,receiver))
|
||||
@@ -985,6 +1068,9 @@ class bmsMilter(Milter.Milter):
|
||||
self.tempname = None
|
||||
if exc_type == email.Errors.BoundaryError:
|
||||
self.log("MALFORMED: %s" % fname) # log filename
|
||||
if self.internal_connection:
|
||||
# accept anyway for now
|
||||
return Milter.ACCEPT
|
||||
self.setreply('554','5.7.7',
|
||||
'Boundary error in your message, are you a spammer?')
|
||||
return Milter.REJECT
|
||||
|
||||
+5
-2
@@ -20,10 +20,11 @@ log_headers = 0
|
||||
;check_user = joe@mycorp.com, mary@mycorp.com, file:bigcorp.com
|
||||
# reject mail with these case insensitive strings in the subject
|
||||
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,
|
||||
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
|
||||
spam_words = $$$, !!!, XXX, FREE, HGH
|
||||
|
||||
@@ -68,6 +69,8 @@ reject_spoofed = 0
|
||||
;best_guess = 0
|
||||
# reject senders that have neither PTR nor SPF records
|
||||
;reject_noptr = 0
|
||||
# always accept softfail from these domains
|
||||
;accept_softfail = bounces.amazon.com
|
||||
|
||||
# features intended to clean up outgoing mail
|
||||
[scrub]
|
||||
|
||||
+75
-7
@@ -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 06, 2004</h4>
|
||||
Last updated Nov 24, 2004</h4>
|
||||
|
||||
See the <a href="faq.html">FAQ</a> | <a href="#download">Download now</a> |
|
||||
<a href="/mailman/listinfo/pymilter">Subscribe to mailing list</a> |
|
||||
@@ -43,14 +43,52 @@ separation features to enhance security.
|
||||
I recommend upgrading.
|
||||
|
||||
<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
|
||||
/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
|
||||
idea of examining the mail headers to see who the message is from.
|
||||
We aren't doing that here, so not to worry - but I am not a lawyer, so if you
|
||||
are worried, change spf.py around line 626 to return None instead of
|
||||
- but only hotmail.com uses them. They seem to have applied for a patent on
|
||||
the brilliant idea of examining the mail headers to see who the message is
|
||||
from. We aren't doing that here, so not to worry - but I am not a lawyer, so
|
||||
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
|
||||
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>
|
||||
<a href="http://spf.pobox.com">
|
||||
<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>
|
||||
|
||||
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
|
||||
production systems long enough to convince me that it is stable. As
|
||||
the package gains more features and complexity, stable will mean no
|
||||
bug reports from outside users either.
|
||||
<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>
|
||||
<a name="stable"><b>Stable</b></a>
|
||||
<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">
|
||||
python2.3</a>.
|
||||
<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">
|
||||
milter-0.7.0-1.src.rpm</a> Source RPM for Redhat 9,7.x.
|
||||
<p>
|
||||
|
||||
+31
-4
@@ -1,6 +1,6 @@
|
||||
%define name milter
|
||||
%define version 0.7.1
|
||||
%define release 1
|
||||
%define version 0.7.2
|
||||
%define release 2
|
||||
# Redhat 7.x and earlier (multiple ps lines per thread)
|
||||
%define sysvinit milter.rc7
|
||||
# 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>
|
||||
Url: http://www.bmsi.com/python/milter.html
|
||||
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
|
||||
|
||||
%description
|
||||
@@ -59,10 +62,15 @@ EOF
|
||||
|
||||
# purge saved defanged message copies
|
||||
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'
|
||||
#!/bin/sh
|
||||
|
||||
find /var/log/milter/save -mtime +7 | xargs -r rm
|
||||
find /var/log/milter/save -mtime +7 | xargs $R rm
|
||||
EOF
|
||||
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
|
||||
|
||||
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
|
||||
%post
|
||||
@@ -107,7 +117,13 @@ if [ $1 = 0 ]; then
|
||||
fi
|
||||
%else
|
||||
%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
|
||||
|
||||
%clean
|
||||
@@ -130,8 +146,19 @@ rm -rf $RPM_BUILD_ROOT
|
||||
%config /var/log/milter/start.sh
|
||||
%config /var/log/milter/bms.py
|
||||
%config(noreplace) /etc/mail/pymilter.cfg
|
||||
/usr/share/sendmail-cf/hack/rhsbl.m4
|
||||
|
||||
%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
|
||||
- Handle modifying mislabeled multipart messages without an exception
|
||||
- Support setbacklog, setmlreply
|
||||
|
||||
@@ -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_
|
||||
@@ -12,7 +12,7 @@ if sys.version < '2.2.3':
|
||||
DistributionMetadata.classifiers = 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",
|
||||
long_description="""\
|
||||
This is a python extension module to enable python scripts to
|
||||
|
||||
@@ -45,6 +45,25 @@ For news, bugfixes, etc. visit the home page for this implementation at
|
||||
# Terrence is not responding to email.
|
||||
#
|
||||
# $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
|
||||
# Remove Perl usage message.
|
||||
#
|
||||
@@ -120,7 +139,7 @@ import xml.sax
|
||||
# Date: 2004-02-25
|
||||
#
|
||||
# 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
|
||||
# - MX mechanism with an different domain as argument
|
||||
# - macros
|
||||
@@ -284,6 +303,16 @@ except NameError:
|
||||
# standard default SPF record
|
||||
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):
|
||||
"""Test an incoming MAIL FROM:<s>, from a client with ip address i.
|
||||
h is the HELO/EHLO domain name.
|
||||
@@ -329,6 +358,7 @@ class query(object):
|
||||
self.cache = {}
|
||||
self.exps = dict(EXPLANATIONS)
|
||||
self.local = local # local policy
|
||||
self.lookups = 0
|
||||
|
||||
def set_default_explanation(self,exp):
|
||||
exps = self.exps
|
||||
@@ -357,27 +387,32 @@ class query(object):
|
||||
return ('pass', 250, 'local connections always pass')
|
||||
|
||||
try:
|
||||
self.lookups = 0
|
||||
if not spf:
|
||||
spf = self.dns_spf(self.d)
|
||||
if self.local and spf:
|
||||
spf += ' ' + self.local
|
||||
return self.check1(spf, self.d, 0)
|
||||
except DNS.DNSError:
|
||||
return ('error', 450, 'SPF DNS Error')
|
||||
except DNS.DNSError,x:
|
||||
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):
|
||||
# spf rfc: 3.7 Processing Limits
|
||||
#
|
||||
if recursion > 20:
|
||||
self.prob = 'Mechanisms used too many DNS lookups'
|
||||
if recursion > MAX_RECURSION:
|
||||
self.prob = 'Too many levels of recursion'
|
||||
return ('unknown', 250, 'SPF recursion limit exceeded')
|
||||
try:
|
||||
tmp, self.d = self.d, domain
|
||||
return self.check0(spf, recursion)
|
||||
return self.check0(spf,recursion)
|
||||
finally:
|
||||
self.d = tmp
|
||||
|
||||
def check0(self, spf, recursion):
|
||||
def check0(self, spf,recursion):
|
||||
"""Test this query information against SPF text.
|
||||
|
||||
Returns (result, mta-status-code, explanation) where
|
||||
@@ -425,35 +460,30 @@ class query(object):
|
||||
m, arg, cidrlength = parse_mechanism(mech, self.d)
|
||||
|
||||
# map '?' '+' or '-' to 'unknown' 'pass' or 'fail'
|
||||
result = RESULTS.get(m[0])
|
||||
if result:
|
||||
if m:
|
||||
result = RESULTS.get(m[0])
|
||||
if result:
|
||||
# eat '?' '+' or '-'
|
||||
m = m[1:]
|
||||
else:
|
||||
else:
|
||||
# default pass
|
||||
result = 'pass'
|
||||
|
||||
if m in ['a', 'mx', 'ptr', 'exists', 'include']:
|
||||
if m in ['a', 'mx', 'ptr', 'prt', 'exists', 'include']:
|
||||
arg = self.expand(arg)
|
||||
|
||||
if m == 'include':
|
||||
if arg != self.d:
|
||||
res,code,txt = self.check1(self.dns_spf(arg),
|
||||
arg, recursion + 1)
|
||||
if res == 'pass':
|
||||
break
|
||||
if res in ('fail','neutral','softfail'):
|
||||
continue
|
||||
if res == 'none':
|
||||
self.prob = \
|
||||
'Could not find a valid SPF record'
|
||||
res = 'unknown'
|
||||
return res,code,txt
|
||||
else:
|
||||
self.prob = 'Required option is missing'
|
||||
self.mech.append(mech)
|
||||
return ('unknown', 250, 'missing SPF option')
|
||||
|
||||
if arg != self.d:
|
||||
res,code,txt = self.check1(self.dns_spf(arg),
|
||||
arg, recursion + 1)
|
||||
if res == 'pass':
|
||||
break
|
||||
if res == 'none':
|
||||
raise PermError(
|
||||
'No valid SPF record for included domain')
|
||||
continue
|
||||
else:
|
||||
raise PermError('include mechanism missing domain')
|
||||
elif m == 'all':
|
||||
break
|
||||
|
||||
@@ -472,9 +502,15 @@ class query(object):
|
||||
break
|
||||
|
||||
elif m in ('ip4', 'ipv4', 'ip') and arg != self.d:
|
||||
try:
|
||||
if cidrmatch(self.i, [arg], cidrlength):
|
||||
break
|
||||
elif m == 'ip6':
|
||||
break
|
||||
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
|
||||
# get an IPv6 connection. So this mech
|
||||
# will never match.
|
||||
@@ -486,17 +522,14 @@ class query(object):
|
||||
break
|
||||
|
||||
else:
|
||||
# unknown mechanisms cause immediate unknown
|
||||
# abort results
|
||||
self.mech.append(mech)
|
||||
self.prob = 'Unknown mechanism found'
|
||||
return ('unknown',250,'unknown SPF mechanism')
|
||||
|
||||
# unknown mechanisms cause immediate unknown
|
||||
# abort results
|
||||
raise PermError('Unknown mechanism found: ' + mech)
|
||||
else:
|
||||
# no matches
|
||||
if redirect:
|
||||
return self.check1(self.dns_spf(redirect),
|
||||
redirect, recursion+1)
|
||||
redirect, recursion + 1)
|
||||
else:
|
||||
result = default
|
||||
|
||||
@@ -630,7 +663,7 @@ class query(object):
|
||||
def dns_txt(self, domainname):
|
||||
"Get a list of TXT records for a domain name."
|
||||
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 []
|
||||
|
||||
def dns_mx(self, domainname):
|
||||
@@ -672,6 +705,9 @@ class query(object):
|
||||
pre: qtype in ['A', 'AAAA', 'MX', 'PTR', 'TXT', 'SPF']
|
||||
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) )
|
||||
cname = None
|
||||
if not result:
|
||||
|
||||
@@ -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--
|
||||
|
||||
Reference in New Issue
Block a user