Compare commits

..

1 Commits

Author SHA1 Message Date
Stuart Gathman 16dea6e187 Release 0.7.1 2005-05-31 18:09:06 +00:00
11 changed files with 306 additions and 113 deletions
+9 -8
View File
@@ -8,15 +8,12 @@ import milter
import thread import thread
from milter import ACCEPT,CONTINUE,REJECT,DISCARD,TEMPFAIL, \ from milter import ACCEPT,CONTINUE,REJECT,DISCARD,TEMPFAIL, \
set_flags, setdbg, \ set_flags, setdbg, setbacklog, settimeout, \
ADDHDRS, CHGBODY, ADDRCPT, DELRCPT, CHGHDRS, \ ADDHDRS, CHGBODY, ADDRCPT, DELRCPT, CHGHDRS, \
V1_ACTS, V2_ACTS, CURR_ACTS V1_ACTS, V2_ACTS, CURR_ACTS
try: try: from milter import QUARANTINE
from milter import QUARANTINE except: pass
except:
#print 'No QUARANTINE support'
pass
_seq_lock = thread.allocate_lock() _seq_lock = thread.allocate_lock()
_seq = 0 _seq = 0
@@ -100,8 +97,10 @@ class Milter:
def getsymval(self,sym): def getsymval(self,sym):
return self.__ctx.getsymval(sym) return self.__ctx.getsymval(sym)
def setreply(self,rcode,xcode,msg): # If sendmail does not support setmlreply, then only the
return self.__ctx.setreply(rcode,xcode,msg) # first msg line is used.
def setreply(self,rcode,xcode=None,msg=None,*ml):
return self.__ctx.setreply(rcode,xcode,msg,*ml)
# Milter methods which can only be called from eom callback. # Milter methods which can only be called from eom callback.
def addheader(self,field,value): def addheader(self,field,value):
@@ -119,6 +118,8 @@ class Milter:
def replacebody(self,body): def replacebody(self,body):
return self.__ctx.replacebody(body) return self.__ctx.replacebody(body)
# When quarantined, a message goes into the mailq as if to be delivered,
# but delivery is deferred until the message is unquarantined.
def quarantine(self,reason): def quarantine(self,reason):
return self.__ctx.quarantine(reason) return self.__ctx.quarantine(reason)
+4
View File
@@ -1,5 +1,9 @@
Here is a history of user visible changes to Python milter. Here is a history of user visible changes to Python milter.
0.7.1 Handle modifying mislabeled multipart messages without an exception
Support setbacklog, setmlreply
Allow multi-recipient CBV
Return TEMPFAIL for SPF softfail
0.7.0 SPF check hello name 0.7.0 SPF check hello name
Move pythonsock to /var/run/milter Move pythonsock to /var/run/milter
Move milter.cfg to /etc/mail/pymilter.cfg Move milter.cfg to /etc/mail/pymilter.cfg
+3 -14
View File
@@ -1,17 +1,6 @@
Message not saved for following traceback: Move milter,Milter,mime,spf modules to pymilter
Traceback (most recent call last): milter package will have bms.py application
File "/usr/lib/python2.3/site-packages/Milter.py", line 188, in <lambda>
milter.set_eom_callback(lambda ctx: ctx.getpriv().eom())
File "bms.py", line 935, in eom
msg.dump(out)
File "/usr/lib/python2.3/site-packages/mime.py", line 347, in dump
g.flatten(self,unixfrom=unixfrom)
File "/var/tmp/python2.3-2.3.3-root/usr/lib/python2.3/email/Generator.py", line 102, in flatten
File "/var/tmp/python2.3-2.3.3-root/usr/lib/python2.3/email/Generator.py", line 130, in _write
File "/var/tmp/python2.3-2.3.3-root/usr/lib/python2.3/email/Generator.py", line 156, in _dispatch
File "/var/tmp/python2.3-2.3.3-root/usr/lib/python2.3/email/Generator.py", line 199, in _handle_text
TypeError: string payload expected: <type 'list'>
------------
spf.py has no recursion bound on CNAME lookup 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
+53 -15
View File
@@ -1,6 +1,17 @@
#!/usr/bin/env python #!/usr/bin/env python
# A simple milter. # A simple milter.
# $Log$ # $Log$
# Revision 1.117 2004/08/23 02:27:53 stuart
# Allow multi rcpt CBV. Add some multiline replies.
#
# Revision 1.116 2004/08/20 22:27:52 stuart
# Generate TEMPFAIL for SPF softfail.
#
# Revision 1.115 2004/08/19 20:55:49 stuart
# Always show reversed SRS path.
# Check if encodings are an ASCII superset. Some messages were encoded as
# BIG5 and getting rejected even though chars were all in ascii subset.
#
# Revision 1.114 2004/07/27 00:40:12 stuart # Revision 1.114 2004/07/27 00:40:12 stuart
# Make reject on no PTR optional. # Make reject on no PTR optional.
# #
@@ -435,7 +446,10 @@ def parse_header(val):
u = [] u = []
for s,enc in h: for s,enc in h:
if enc: if enc:
try:
u.append(unicode(s,enc)) u.append(unicode(s,enc))
except LookupError:
u.append(unicode(s))
else: else:
u.append(unicode(s)) u.append(unicode(s))
u = ''.join(u) u = ''.join(u)
@@ -443,13 +457,14 @@ def parse_header(val):
try: try:
return u.encode(enc) return u.encode(enc)
except UnicodeError: continue except UnicodeError: continue
except UnicodeDecodeError: except UnicodeDecodeError: pass
return val except LookupError: pass
except LookupError:
return val return val
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
and smart alias redirection."""
def log(self,*msg): def log(self,*msg):
print "%s [%d]" % (time.strftime('%Y%b%d %H:%M:%S'),self.id), print "%s [%d]" % (time.strftime('%Y%b%d %H:%M:%S'),self.id),
@@ -592,8 +607,13 @@ class bmsMilter(Milter.Milter):
# check hello name via spf # check hello name via spf
hres,hcode,htxt = spf.check(self.connectip,'',self.hello_name) hres,hcode,htxt = spf.check(self.connectip,'',self.hello_name)
if hres in ('deny','fail','neutral','softfail'): if hres in ('deny','fail','neutral','softfail'):
self.log('REJECT: hello SPF: %s %i %s' % (hres,hcode,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",
"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."
)
return Milter.REJECT return Milter.REJECT
if spf_best_guess: if spf_best_guess:
#self.log('SPF: no record published, guessing') #self.log('SPF: no record published, guessing')
@@ -612,10 +632,26 @@ class bmsMilter(Milter.Milter):
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':
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 == 'neutral' and q.o in spf_reject_neutral: if res == 'neutral' and q.o in spf_reject_neutral:
self.log('REJECT: SPF neutral for',q.s) self.log('REJECT: SPF neutral for',q.s)
self.setreply('550','5.7.1', self.setreply('550','5.7.1',
'mail from %s must pass SPF: http://spf.pobox.com/why.html' % q.o 'mail from %s must pass SPF: http://spf.pobox.com/why.html' % q.o,
'The %s domain is one that spammers love to forge. Due to' % q.o,
'the volume of forged mail, we can only accept mail that',
'the SPF record for %s explicitly designates as legitimate.' % q.o,
'Sending your email through the recommended outgoing SMTP',
'servers for %s should accomplish this.' % q.o
) )
return Milter.REJECT return Milter.REJECT
if res == 'error': if res == 'error':
@@ -639,17 +675,15 @@ class bmsMilter(Milter.Milter):
if self.mailfrom == '<>' or self.canon_from.startswith('postmaster@') \ if self.mailfrom == '<>' or self.canon_from.startswith('postmaster@') \
or self.canon_from.startswith('mailer-daemon@'): or self.canon_from.startswith('mailer-daemon@'):
if self.recipients: if self.recipients:
self.log('REJECT: Multiple bounce recipients') self.data_allowed = False
self.setreply('550','5.7.1','Multiple bounce recipients') if srs and domain == srs_fwdomain:
return Milter.REJECT
if srs and not (self.internal_connection or self.trusted_relay) \
and domain == srs_fwdomain:
oldaddr = '@'.join(parse_addr(to)) oldaddr = '@'.join(parse_addr(to))
try: try:
newaddr = srs.reverse(oldaddr) newaddr = srs.reverse(oldaddr)
# Currently, a sendmail map reverses SRS. We just log it here. # Currently, a sendmail map reverses SRS. We just log it here.
self.log("srs rcpt:",newaddr) self.log("srs rcpt:",newaddr)
except: except:
if not (self.internal_connection or self.trusted_relay):
if srsre.match(oldaddr): if srsre.match(oldaddr):
self.log("REJECT: srs spoofed:",oldaddr) self.log("REJECT: srs spoofed:",oldaddr)
self.setreply('550','5.7.1','Invalid SRS signature') self.setreply('550','5.7.1','Invalid SRS signature')
@@ -748,8 +782,12 @@ class bmsMilter(Milter.Milter):
def header(self,name,hval): def header(self,name,hval):
if not self.data_allowed: if not self.data_allowed:
if len(self.recipients) > 1:
self.log('REJECT: Multiple bounce recipients')
self.setreply('550','5.7.1','Multiple bounce recipients')
else:
self.log('REJECT: bounce with no SRS encoding') self.log('REJECT: bounce with no SRS encoding')
self.setreply('550','5.7.1',"I did not send you this message.") self.setreply('550','5.7.1',"I did not send you that message.")
return Milter.REJECT return Milter.REJECT
lname = name.lower() lname = name.lower()
# decode near ascii text to unobfuscate # decode near ascii text to unobfuscate
@@ -757,8 +795,8 @@ class bmsMilter(Milter.Milter):
if not self.internal_connection: if not self.internal_connection:
# even if we wanted the Taiwanese spam, we can't read Chinese # even if we wanted the Taiwanese spam, we can't read Chinese
if block_chinese and lname == 'subject': if block_chinese and lname == 'subject':
if hval.startswith('=?big5') or hval.startswith('=?ISO-2022-JP'): if val.startswith('=?big5') or val.startswith('=?ISO-2022-JP'):
self.log('REJECT: %s: %s' % (name,hval)) self.log('REJECT: %s: %s' % (name,val))
self.setreply('550','5.7.1',"We don't understand chinese") self.setreply('550','5.7.1',"We don't understand chinese")
return Milter.REJECT return Milter.REJECT
rc = self.check_header(name,val) rc = self.check_header(name,val)
+34 -17
View File
@@ -1,52 +1,66 @@
# features intended to filter or block incoming mail # features intended to filter or block incoming mail
[milter] [milter]
# the socket used to communicate with sendmail. Must match sendmail.cf
;socket=/var/run/milter/pythonsock ;socket=/var/run/milter/pythonsock
# where to save original copies of defanged and failed messages
tempdir = /var/log/milter/save tempdir = /var/log/milter/save
# how long to wait for a response from sendmail before giving up
;timeout=600 ;timeout=600
# do virus scanning on attached messages also
scan_rfc822 = 1 scan_rfc822 = 1
# can be CPU intensive # Comment out scripts in HTML attachments. Can be CPU intensive.
scan_html = 0 scan_html = 0
# reject asian fonts because we can't read them # reject messages with asian fonts because we can't read them
block_chinese = 1 block_chinese = 1
# users who hate forwarded mail # list users who hate forwarded mail
;block_forward = egghead@mycorp.com, busybee@mycorp.com ;block_forward = egghead@mycorp.com, busybee@mycorp.com
log_headers = 0 log_headers = 0
# Reject mail for domains mentioned unless user is mentioned here also # Reject mail for domains mentioned unless user is mentioned here also
;check_user = joe@mycorp.com, mary@mycorp.com, file:bigcorp.com ;check_user = joe@mycorp.com, mary@mycorp.com, file:bigcorp.com
# porn words are case insensitive # 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,
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
# spam words are case sensitive # reject mail with these case sensitive strings in the subject
spam_words = $$$, !!!, XXX, FREE, HGH spam_words = $$$, !!!, XXX, FREE, HGH
# connection ips and hostnames are matched against this glob style list # connection ips and hostnames are matched against this glob style list
# to recognize internal senders # to recognize internal senders
;internal_connect = 192.168.*.* ;internal_connect = 192.168.*.*
# mail that is not an internal_connect and claims to be from an # mail that is not an internal_connect and claims to be from an
# internal domain is rejected. # internal domain is rejected. You should enable SPF instead if you can.
# SPF is much more comprehensive and flexible.
;internal_domains = mycorp.com ;internal_domains = mycorp.com
# connections from a trusted relay can trust the first Received header # connections from a trusted relay can trust the first Received header
# SPF checks are bypassed for internal connections and trusted relays.
;trusted_relay = 1.2.3.4, 66.12.34.56 ;trusted_relay = 1.2.3.4, 66.12.34.56
# reject external senders with hello names no legit external sender would use # reject external senders with hello names no legit external sender would use
# SPF will do this also, but listing your own domain and mailserver here
# will save some DNS lookups when rejecting certain viruses.
;hello_blacklist = mycorp.com, 66.12.34.56 ;hello_blacklist = mycorp.com, 66.12.34.56
# See http://bmsi.com/python/pysrs.html for details
[srs] [srs]
config=/etc/mail/pysrs.cfg config=/etc/mail/pysrs.cfg
# SRS options can be set here also, but must match the sendmail plugin
;secret="shhhh!" ;secret="shhhh!"
;maxage=21 ;maxage=21
;hashlength=4 ;hashlength=4
;database=/var/log/milter/srsdata ;database=/var/log/milter/srsdata
;fwdomain = mydomain.com ;fwdomain = mydomain.com
# turn this on after a grace period # turn this on after a grace period to reject spoofed DSNs
reject_spoofed = 0 reject_spoofed = 0
# See http://spf.pobox.com for more info on SPF.
[spf] [spf]
# namespace where SPF records can be supplied for domains without one # namespace where SPF records can be supplied for domains without one
# records are search for under _spf.domain.com # records are searched for under _spf.domain.com
;delegate = domain.com ;delegate = domain.com
# domains where a neutral SPF result should cause mail to be rejected # domains where a neutral SPF result should cause mail to be rejected
;reject_neutral = aol.com ;reject_neutral = aol.com
@@ -57,9 +71,9 @@ reject_spoofed = 0
# features intended to clean up outgoing mail # features intended to clean up outgoing mail
[scrub] [scrub]
# domains that stupidly block visible private nodes # domains that block visible private nodes
;hide_path = jcpenney.com ;hide_path = jcpenney.com
# block, don't just replace with warning, viruses from these domains # reject, don't just replace with warning, viruses from these domains
;reject_virus_from = mycorp.com ;reject_virus_from = mycorp.com
# features intended for spying on users and coworkers # features intended for spying on users and coworkers
@@ -88,16 +102,18 @@ blind = 1
;walter1 = cust@othercorp.com,walter@bigcorp.com,boss@bigcorp.com, ;walter1 = cust@othercorp.com,walter@bigcorp.com,boss@bigcorp.com,
; walter@bigcorp.com ; walter@bigcorp.com
# See http://bmsi.com/python/dspam.html
[dspam] [dspam]
# Select a well moderated dspam dictionary to reject spammy headers # Select a well moderated dspam dictionary to reject spammy headers.
# dspam-python must be installed to use: http://bmsi.com/python/dspam.html # To filter on the entire message, use the full setup below.
# only EXTERNAL messages are dspam filtered # only EXTERNAL messages are dspam filtered
;dspam_dict=/var/lib/dspam/moderator.dict ;dspam_dict=/var/lib/dspam/moderator.dict
# Opt-opt recipients from dspam screening and header triage # Opt-opt recipients from dspam screening and header triage
;dspam_exempt=getitall@mycorp.com ;dspam_exempt=getitall@mycorp.com
# Do not scan mail (ostensibly) from these senders # Do not scan mail (ostensibly) from these senders
;dspam_whitelist=getitall@sender.com ;dspam_whitelist=getitall@sender.com
# Reject spam to these domains, perhaps because we are a backup MX server # Reject spam to these domains instead of quarantining it.
;dspam_reject=othercorp.com ;dspam_reject=othercorp.com
# directory for dspam user quarantine, signature db, and dictionaries # directory for dspam user quarantine, signature db, and dictionaries
@@ -115,8 +131,9 @@ blind = 1
;spam=spam@foocorp.com ;spam=spam@foocorp.com
# address to forward false positives to. milter will process and not deliver # address to forward false positives to. milter will process and not deliver
;falsepositive=ham@foocorp.com ;falsepositive=ham@foocorp.com
# the dspam_screener is used to screen mail for all recipients who are # the dspam_screener is a list of dspam users who screen mail for all
# not dspam_users. Spam goes to the screeners quarantine, and the original # recipients who are not dspam_users. Spam goes to the screeners quarantine,
# recipients saved so that false positives can be properly delivered. # and the original recipients are saved so that false positives can be properly
# delivered.
;dspam_screener=david,goliath
# The dspam CGI can also be used: logins must match dspam users # The dspam CGI can also be used: logins must match dspam users
+52 -11
View File
@@ -13,8 +13,8 @@ ALT="Viewable With Any Browser" BORDER="0"></A>
usemap="#banner_4" alt="Your vote?"> usemap="#banner_4" alt="Your vote?">
<map name="banner_4"> <map name="banner_4">
<area shape="rect" coords="330,25,426,59" <area shape="rect" coords="330,25,426,59"
href="http://www.sepschool.org/survey/" alt="I Disagree"> href="http://education-survey.org/" alt="I Disagree">
<area shape="rect" coords="234,28,304,57" href="http://sepschool.org/" alt="I Agree"> <area shape="rect" coords="234,28,304,57" href="http://www.honestEd.com/" alt="I Agree">
</map> </map>
</P> </P>
@@ -24,12 +24,14 @@ 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 Jun 08, 2004</h4> Last updated Aug 06, 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> |
<a href="#overview">Overview</a>
<p> <p>
<img src="python55.gif" align=left alt="A Python"> <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="//www.sendmail.org/">Sendmail</a> introduced a
<a href="http://www.milter.org/milter_api/api.html"> 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> libmilter. The milter module for <a href="//www.python.org">Python</a>
@@ -41,7 +43,17 @@ separation features to enhance security.
I recommend upgrading. I recommend upgrading.
<h2> Recent Changes </h2> <h2> Recent Changes </h2>
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
calling CIDParser(). There is a new option to reject mail with no PTR
and no SPF.
<p>
<a href="http://spf.pobox.com">
<img src="SPF.gif" align=left alt="SPF logo"></a>
Release 0.6.6 adds support for <a href="http://spf.pobox.com/">SPF</a>, Release 0.6.6 adds support for <a href="http://spf.pobox.com/">SPF</a>,
a protocol to prevent forging of the envelope from address. a protocol to prevent forging of the envelope from address.
SPF support requires <a href="http://pydns.sourceforge.net/">pydns</a>. SPF support requires <a href="http://pydns.sourceforge.net/">pydns</a>.
@@ -106,7 +118,7 @@ entries. Be sure to handle both Bcc and file copies, and designating what
mail should be copied. How should "outgoing" be defined? Implementing it is mail should be copied. How should "outgoing" be defined? Implementing it is
easy once the configuration is designed. easy once the configuration is designed.
<h3>Overview</h3> <h3><a name=overview>Overview</a></h3>
This package provides a robust toolkit for Python <a This package provides a robust toolkit for Python <a
href="#milter">milters</a>, and the beginnings of a general purpose mail href="#milter">milters</a>, and the beginnings of a general purpose mail
@@ -138,21 +150,50 @@ methods that
do nothing, and also provides wrappers for the libmilter methods to mutate do nothing, and also provides wrappers for the libmilter methods to mutate
the message. the message.
<p> <p>
The 'spf' module provides an implementation of <a href="http://spf.pobox.com">
SPF</a> useful for detecting email forgery.
<p>
The 'mime' module provides a wrapper for the Python email package that
fixes some bugs, and simplifies modifying selected parts of a MIME message.
<p>
Finally, the bms.py application is both a sample of how to use the Finally, the bms.py application is both a sample of how to use the
Milter module, and the beginnings of a general purpose SPAM filtering, Milter and spf modules, and the beginnings of a general purpose SPAM filtering,
wiretapping, and Win32 virus protection milter. wiretapping, SPF checking, and Win32 virus protecting milter. It can
make use of the <a href="pysrs.html">pysrs</a> package when available for
SRS/SES checking and the <a href="dspam.html">pydspam</a> package for Bayesian
content filtering. SPF checking
requires <a href="http://pydns.sourceforge.net/">
pydns</a>. Configuration documentation is currently included as comments
in the <a href="milter.cfg">sample config file</a> for the bms.py milter.
<h3><a name=download>Downloading</a></h3> <h3><a name=download>Downloading</a></h3>
The latest stable release is <a href="#stable">0.6.9</a>. A stable The latest stable release is <a href="#stable">0.7.0</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.6.9-1. See the <a href=NEWS>Change Log</a>. The latest version is 0.7.0-1. See the <a href=NEWS>Change Log</a>.
<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">
milter-0.7.0.tar.gz</a> Move config file and default socket location.
Parse M$ CID records.
<br>
<a href="http://bmsi.com/linux/rh72/milter-0.7.0-1.i386.rpm">
milter-0.7.0-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.0-1rh9.i386.rpm">
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/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>
<a href="http://bmsi.com/python/milter-0.6.9.tar.gz"> <a href="http://bmsi.com/python/milter-0.6.9.tar.gz">
milter-0.6.9.tar.gz</a> Add SPF test suite driver, and validate milter-0.6.9.tar.gz</a> Add SPF test suite driver, and validate
spf.py against test suite. Add best_guess and get_header to spf.py. spf.py against test suite. Add best_guess and get_header to spf.py.
+8 -3
View File
@@ -1,5 +1,5 @@
%define name milter %define name milter
%define version 0.7.0 %define version 0.7.1
%define release 1 %define release 1
# 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
@@ -24,8 +24,8 @@ Prefix: %{_prefix}
Vendor: Stuart D. Gathman <stuart@bmsi.com> 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 Requires: %{python} >= 2.2.2, sendmail >= 8.12.10
BuildRequires: %{python}-devel >= 2.2.2, sendmail-devel >= 8.12 BuildRequires: %{python}-devel >= 2.2.2, sendmail-devel >= 8.12.10
%description %description
This is a python extension module to enable python scripts to This is a python extension module to enable python scripts to
@@ -132,6 +132,11 @@ rm -rf $RPM_BUILD_ROOT
%config(noreplace) /etc/mail/pymilter.cfg %config(noreplace) /etc/mail/pymilter.cfg
%changelog %changelog
* Sun Aug 22 2004 Stuart Gathman <stuart@bmsi.com> 0.7.1-1
- Handle modifying mislabeled multipart messages without an exception
- Support setbacklog, setmlreply
- allow multi-recipient CBV
- return TEMPFAIL for SPF softfail
* Fri Jul 23 2004 Stuart Gathman <stuart@bmsi.com> 0.7.0-1 * Fri Jul 23 2004 Stuart Gathman <stuart@bmsi.com> 0.7.0-1
- SPF check hello name - SPF check hello name
- Move pythonsock to /var/run/milter - Move pythonsock to /var/run/milter
+104 -23
View File
@@ -33,6 +33,18 @@ $ python setup.py help
libraries=["milter","smutil","resolv"] libraries=["milter","smutil","resolv"]
* $Log$ * $Log$
* Revision 2.31 2004/08/23 02:24:36 stuart
* Support setbacklog
*
* Revision 2.30 2004/08/21 20:29:53 stuart
* Support option of 11 lines max for mlreply.
*
* Revision 2.29 2004/08/21 04:14:29 stuart
* mlreply support
*
* Revision 2.28 2004/08/21 02:45:21 stuart
* Don't leak int constants if module unloaded.
*
* Revision 2.27 2004/04/06 03:19:59 stuart * Revision 2.27 2004/04/06 03:19:59 stuart
* Release 0.6.8 * Release 0.6.8
* *
@@ -127,11 +139,20 @@ $ python setup.py help
* *
*/ */
#ifndef MAX_ML_REPLY
#define MAX_ML_REPLY 32
#endif
#if MAX_ML_REPLY != 1 && MAX_ML_REPLY != 32 && MAX_ML_REPLY != 11
#error MAX_ML_REPLY must be 1 or 11 or 32
#endif
#define _FFR_MULTILINE (MAX_ML_REPLY > 1)
#include <pthread.h> #include <pthread.h>
#include <netinet/in.h> #include <netinet/in.h>
#include <Python.h> #include <Python.h>
#include <libmilter/mfapi.h> #include <libmilter/mfapi.h>
/* See if we have IPv4 and/or IPv6 support in this OS and in /* See if we have IPv4 and/or IPv6 support in this OS and in
* libmilter. We need to make several macro tests because some OS's * libmilter. We need to make several macro tests because some OS's
* may define some if IPv6 is only partially supported, and we may * may define some if IPv6 is only partially supported, and we may
@@ -746,6 +767,18 @@ milter_setdbg(PyObject *self, PyObject *args) {
return _generic_return(smfi_setdbg(val), "cannot set debug value"); return _generic_return(smfi_setdbg(val), "cannot set debug value");
} }
static char milter_setbacklog__doc__[] =
"setbacklog(int) -> None\n\
Set the TCP connection queue size for the milter socket.";
static PyObject *
milter_setbacklog(PyObject *self, PyObject *args) {
int val;
if (!PyArg_ParseTuple(args, "i:setbacklog", &val)) return NULL;
return _generic_return(smfi_setbacklog(val), "cannot set backlog");
}
static char milter_settimeout__doc__[] = static char milter_settimeout__doc__[] =
"settimeout(int) -> None\n\ "settimeout(int) -> None\n\
Set the time (in seconds) that sendmail will wait before\n\ Set the time (in seconds) that sendmail will wait before\n\
@@ -820,13 +853,54 @@ static PyObject *
milter_setreply(PyObject *self, PyObject *args) { milter_setreply(PyObject *self, PyObject *args) {
char *rcode; char *rcode;
char *xcode; char *xcode;
char *message; char *message[MAX_ML_REPLY];
char fmt[MAX_ML_REPLY + 16];
SMFICTX *ctx; SMFICTX *ctx;
if (!PyArg_ParseTuple(args, "szz:setreply", &rcode, &xcode, &message)) int i;
strcpy(fmt,"sz|");
for (i = 0; i < MAX_ML_REPLY; ++i) {
message[i] = 0;
fmt[i+3] = 's';
}
strcpy(fmt+i+3,":setreply");
if (!PyArg_ParseTuple(args, fmt,
&rcode, &xcode, message
#if MAX_ML_REPLY > 1
,message+1,message+2,message+3,message+4,message+5,message+6,
message+7,message+8,message+9,message+10
#if MAX_ML_REPLY > 11
,message+11,message+12,message+13,message+14,message+15,
message+16,message+17,message+18,message+19,message+20,
message+21,message+22,message+23,message+24,message+25,
message+26,message+27,message+28,message+29,message+30,
message+31
#endif
#endif
))
return NULL; return NULL;
ctx = _find_context(self); ctx = _find_context(self);
if (ctx == NULL) return NULL; if (ctx == NULL) return NULL;
return _generic_return(smfi_setreply(ctx, rcode, xcode, message), #if MAX_ML_REPLY > 1
/*
* C varargs might be convenient for some things, but they sure are a pain
* when the number of args is not known at compile time.
*/
if (message[0] && message[1])
return _generic_return(smfi_setmlreply(ctx, rcode, xcode,
message[0],
message[1],message[2],message[3],message[4],message[5],
message[6],message[7],message[8],message[9],message[10],
#if MAX_ML_REPLY > 11
message[11],message[12],message[13],message[14],message[15],
message[16],message[17],message[18],message[19],message[20],
message[21],message[22],message[23],message[24],message[25],
message[26],message[27],message[28],message[29],message[30],
message[31],
#endif
(char *)0
), "cannot set reply");
#endif
return _generic_return(smfi_setreply(ctx, rcode, xcode, message[0]),
"cannot set reply"); "cannot set reply");
} }
@@ -986,7 +1060,7 @@ milter_getpriv(PyObject *self, PyObject *args) {
return o; return o;
} }
#if _FFR_QUARANTINE #ifdef SMFIF_QUARANTINE
static char milter_quarantine__doc__[] = static char milter_quarantine__doc__[] =
"quarantine(string) -> None\n\ "quarantine(string) -> None\n\
Place the message in quarantine. A string with a description of the reason\n\ Place the message in quarantine. A string with a description of the reason\n\
@@ -1035,7 +1109,7 @@ static PyMethodDef context_methods[] = {
{ "replacebody", milter_replacebody, METH_VARARGS, milter_replacebody__doc__}, { "replacebody", milter_replacebody, METH_VARARGS, milter_replacebody__doc__},
{ "setpriv", milter_setpriv, METH_VARARGS, milter_setpriv__doc__}, { "setpriv", milter_setpriv, METH_VARARGS, milter_setpriv__doc__},
{ "getpriv", milter_getpriv, METH_VARARGS, milter_getpriv__doc__}, { "getpriv", milter_getpriv, METH_VARARGS, milter_getpriv__doc__},
#if _FFR_QUARANTINE #ifdef SMFIF_QUARANTINE
{ "quarantine", milter_quarantine, METH_VARARGS, milter_quarantine__doc__}, { "quarantine", milter_quarantine, METH_VARARGS, milter_quarantine__doc__},
#endif #endif
#if _FFR_SMFI_PROGRESS #if _FFR_SMFI_PROGRESS
@@ -1081,6 +1155,7 @@ static PyMethodDef milter_methods[] = {
{ "main", milter_main, METH_VARARGS, milter_main__doc__}, { "main", milter_main, METH_VARARGS, milter_main__doc__},
{ "setdbg", milter_setdbg, METH_VARARGS, milter_setdbg__doc__}, { "setdbg", milter_setdbg, METH_VARARGS, milter_setdbg__doc__},
{ "settimeout", milter_settimeout, METH_VARARGS, milter_settimeout__doc__}, { "settimeout", milter_settimeout, METH_VARARGS, milter_settimeout__doc__},
{ "setbacklog", milter_setbacklog, METH_VARARGS, milter_setbacklog__doc__},
{ "setconn", milter_setconn, METH_VARARGS, milter_setconn__doc__}, { "setconn", milter_setconn, METH_VARARGS, milter_setconn__doc__},
{ "stop", milter_stop, METH_VARARGS, milter_stop__doc__}, { "stop", milter_stop, METH_VARARGS, milter_stop__doc__},
{ NULL, NULL } { NULL, NULL }
@@ -1116,6 +1191,12 @@ allowing one to write email filters directly in Python.\n\
Libmilter is currently marked FFR, and needs to be explicitly installed.\n\ Libmilter is currently marked FFR, and needs to be explicitly installed.\n\
See <sendmailsource>/libmilter/README for details on setting it up.\n"; See <sendmailsource>/libmilter/README for details on setting it up.\n";
static void setitem(PyObject *d,const char *name,long val) {
PyObject *v = PyInt_FromLong(val);
PyDict_SetItemString(d,name,v);
Py_DECREF(v);
}
void void
initmilter(void) { initmilter(void) {
PyObject *m, *d; PyObject *m, *d;
@@ -1125,24 +1206,24 @@ initmilter(void) {
d = PyModule_GetDict(m); d = PyModule_GetDict(m);
MilterError = PyErr_NewException("milter.error", NULL, NULL); MilterError = PyErr_NewException("milter.error", NULL, NULL);
PyDict_SetItemString(d,"error", MilterError); PyDict_SetItemString(d,"error", MilterError);
PyDict_SetItemString(d,"SUCCESS", PyInt_FromLong((long) MI_SUCCESS)); setitem(d,"SUCCESS", MI_SUCCESS);
PyDict_SetItemString(d,"FAILURE", PyInt_FromLong((long) MI_FAILURE)); setitem(d,"FAILURE", MI_FAILURE);
PyDict_SetItemString(d,"VERSION", PyInt_FromLong((long) SMFI_VERSION)); setitem(d,"VERSION", SMFI_VERSION);
PyDict_SetItemString(d,"ADDHDRS", PyInt_FromLong((long) SMFIF_ADDHDRS)); setitem(d,"ADDHDRS", SMFIF_ADDHDRS);
PyDict_SetItemString(d,"CHGBODY", PyInt_FromLong((long) SMFIF_CHGBODY)); setitem(d,"CHGBODY", SMFIF_CHGBODY);
PyDict_SetItemString(d,"MODBODY", PyInt_FromLong((long) SMFIF_MODBODY)); setitem(d,"MODBODY", SMFIF_MODBODY);
PyDict_SetItemString(d,"ADDRCPT", PyInt_FromLong((long) SMFIF_ADDRCPT)); setitem(d,"ADDRCPT", SMFIF_ADDRCPT);
PyDict_SetItemString(d,"DELRCPT", PyInt_FromLong((long) SMFIF_DELRCPT)); setitem(d,"DELRCPT", SMFIF_DELRCPT);
PyDict_SetItemString(d,"CHGHDRS", PyInt_FromLong((long) SMFIF_CHGHDRS)); setitem(d,"CHGHDRS", SMFIF_CHGHDRS);
PyDict_SetItemString(d,"V1_ACTS", PyInt_FromLong((long) SMFI_V1_ACTS)); setitem(d,"V1_ACTS", SMFI_V1_ACTS);
PyDict_SetItemString(d,"V2_ACTS", PyInt_FromLong((long) SMFI_V2_ACTS)); setitem(d,"V2_ACTS", SMFI_V2_ACTS);
PyDict_SetItemString(d,"CURR_ACTS", PyInt_FromLong((long) SMFI_CURR_ACTS)); setitem(d,"CURR_ACTS", SMFI_CURR_ACTS);
#ifdef SMFIF_QUARANTINE #ifdef SMFIF_QUARANTINE
PyDict_SetItemString(d,"QUARANTINE",PyInt_FromLong((long)SMFIF_QUARANTINE)); setitem(d,"QUARANTINE",SMFIF_QUARANTINE);
#endif #endif
PyDict_SetItemString(d,"CONTINUE", PyInt_FromLong((long) SMFIS_CONTINUE)); setitem(d,"CONTINUE", SMFIS_CONTINUE);
PyDict_SetItemString(d,"REJECT", PyInt_FromLong((long) SMFIS_REJECT)); setitem(d,"REJECT", SMFIS_REJECT);
PyDict_SetItemString(d,"DISCARD", PyInt_FromLong((long) SMFIS_DISCARD)); setitem(d,"DISCARD", SMFIS_DISCARD);
PyDict_SetItemString(d,"ACCEPT", PyInt_FromLong((long) SMFIS_ACCEPT)); setitem(d,"ACCEPT", SMFIS_ACCEPT);
PyDict_SetItemString(d,"TEMPFAIL", PyInt_FromLong((long) SMFIS_TEMPFAIL)); setitem(d,"TEMPFAIL", SMFIS_TEMPFAIL);
} }
+22 -2
View File
@@ -1,4 +1,7 @@
# $Log$ # $Log$
# Revision 1.54 2004/08/18 01:59:46 stuart
# Handle mislabeled multipart messages
#
# Revision 1.53 2004/04/24 22:53:20 stuart # Revision 1.53 2004/04/24 22:53:20 stuart
# Rename some local variables to avoid shadowing builtins # Rename some local variables to avoid shadowing builtins
# #
@@ -58,6 +61,18 @@ except: from email.Parser import nlcre as NLCRE
from email import Errors from email import Errors
class MimeGenerator(Generator):
def _dispatch(self, msg):
# Get the Content-Type: for the message, then try to dispatch to
# self._handle_<maintype>_<subtype>(). If there's no handler for the
# full MIME type, then dispatch to self._handle_<maintype>(). If
# that's missing too, then dispatch to self._writeBody().
main = msg.get_content_maintype()
if msg.is_multipart() and main.lower() != 'multipart':
self._handle_multipart(msg)
else:
Generator._dispatch(self,msg)
class MimeParser(Parser): class MimeParser(Parser):
# This is a copy of _parsebody from email.Parser, with a fix # This is a copy of _parsebody from email.Parser, with a fix
@@ -349,9 +364,15 @@ class MimeMessage(Message):
def dump(self,file,unixfrom=False): def dump(self,file,unixfrom=False):
"Write this message (and all subparts) to a file" "Write this message (and all subparts) to a file"
g = Generator(file) g = MimeGenerator(file)
g.flatten(self,unixfrom=unixfrom) g.flatten(self,unixfrom=unixfrom)
def as_string(self, unixfrom=False):
"Return the entire formatted message as a string."
fp = StringIO.StringIO()
self.dump(fp,unixfrom=unixfrom)
return fp.getvalue()
def getencoding(self): def getencoding(self):
return self.get('content-transfer-encoding',None) return self.get('content-transfer-encoding',None)
@@ -577,7 +598,6 @@ class HTMLScriptFilter(SGMLFilter):
def handle_comment(self,comment): def handle_comment(self,comment):
if not self.ignoring: SGMLFilter.handle_comment(self,comment) if not self.ignoring: SGMLFilter.handle_comment(self,comment)
def check_html(msg,savname=None): def check_html(msg,savname=None):
"Remove scripts from HTML attachments." "Remove scripts from HTML attachments."
msgtype = msg.get_content_type().lower() msgtype = msg.get_content_type().lower()
+5 -2
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.0", setup(name = "milter", version = "0.7.1",
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
@@ -28,7 +28,10 @@ querying SPF records.
url="http://www.bmsi.com/python/milter.html", url="http://www.bmsi.com/python/milter.html",
py_modules=["Milter","mime","spf"], py_modules=["Milter","mime","spf"],
ext_modules=[ ext_modules=[
Extension("milter", ["miltermodule.c"],libraries=libs), Extension("milter", ["miltermodule.c"],
libraries=libs,
define_macros = [ ('MAX_ML_REPLY',32) ]
),
], ],
keywords = ['sendmail','milter'], keywords = ['sendmail','milter'],
classifiers = [ classifiers = [
+3 -9
View File
@@ -45,6 +45,9 @@ 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.14 2004/08/23 02:28:24 stuart
# Remove Perl usage message.
#
# Revision 1.13 2004/07/23 19:23:12 stuart # Revision 1.13 2004/07/23 19:23:12 stuart
# Always fail to match on ip6, until we support it properly. # Always fail to match on ip6, until we support it properly.
# #
@@ -115,15 +118,6 @@ import xml.sax
# (c) 2004 Python version by Stuart Gathman # (c) 2004 Python version by Stuart Gathman
# #
# Date: 2004-02-25 # Date: 2004-02-25
# Version: 1.0
#
# Usage:
# ./cid2spf.pl "<ep xmlns='http://ms.net/1'>...</ep>"
#
# Note that the 'include' directives will also have to be checked and
# "translated". Future versions of this script might be able to get a
# domain name as an argument and "crawl" the DNS for the necessary
# information.
# #
# 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 way to handle: