Compare commits

..

23 Commits

Author SHA1 Message Date
Stuart D. Gathman 255624ea80 print function 2016-07-26 09:36:23 -04:00
Stuart D. Gathman f5bd952f64 Pull Scott's changes 2016-07-26 07:58:40 -04:00
Stuart D. Gathman 687bebcd45 Merge fix test case from master 2016-07-26 07:56:52 -04:00
Scott Kitterman 5252278804 Fixup test bits missing from the python3 branch. 2016-07-26 01:21:47 -04:00
Stuart D. Gathman e5cff32526 Target python2.7 for master
Conflicts:
	setup.cfg
2016-07-26 01:09:35 -04:00
Stuart D. Gathman 702c6c126f Use unicode literal to join unicode strings.
Conflicts:
	Milter/utils.py
2016-07-26 01:07:13 -04:00
Stuart D. Gathman bf327f95e0 Add section to link projects using pymilter. 2016-07-26 01:04:19 -04:00
Stuart D. Gathman 883b19f131 Fix test case 2016-07-26 01:03:59 -04:00
Stuart D. Gathman 0863bb5602 Fix spurious cleanup error. 2016-07-26 01:03:26 -04:00
Scott Kitterman 67d974638a Import print_function from future so we still work with python2.7 2016-07-25 17:42:19 -04:00
Scott Kitterman 9058f1c2aa sample.py: print is a function in python3 2016-07-25 17:29:29 -04:00
Scott Kitterman 932216e1bf Convert tabs to spaces for sample.py 2016-07-25 17:27:34 -04:00
Stuart D. Gathman 91a70384ed Update README 2016-07-25 10:05:19 -04:00
Stuart Gathman bbd6771a74 Handle missing padding in encoded header 2016-07-25 10:04:55 -04:00
Stuart Gathman 8b36939747 Test case for missing padding. 2016-07-25 10:04:35 -04:00
Stuart Gathman 0c1726614d Link to related packages.
Conflicts:
	doc/mainpage.py
2016-07-25 10:04:06 -04:00
Stuart Gathman 74b8b1ae19 Copy sendmail-devel libmilter api into documention, since milter.org is gone.
Conflicts:
	Milter/__init__.py
	doc/milter.py
	makefile
2016-07-25 10:02:46 -04:00
Stuart Gathman d35ed40edf Fix header_leading_space, update doc version.
Conflicts:
	Doxyfile
	testgrey.py
2016-07-25 09:59:03 -04:00
Stuart Gathman 753c417f31 Fix bug from pyspf - caching server altering case of cached names. 2016-07-25 09:56:11 -04:00
Stuart Gathman 73bd1895cd Add dns name support for iniplist() 2016-07-25 09:55:47 -04:00
Stuart D. Gathman e01b7dabf2 Use python3 for build 2016-07-23 12:19:45 -04:00
Stuart Gathman 1b4903f905 Python3 changes 2013-01-13 04:26:30 +00:00
cvs2svn e1d29fdf6a This commit was manufactured by cvs2svn to create branch 'python3-branch'.
Sprout from master 2012-11-10 03:38:47 UTC Stuart Gathman <stuart@gathman.org> 'Update doc version to 0.9.6'
Cherrypick from bmsi 2005-05-31 18:10:47 UTC Stuart Gathman <stuart@gathman.org> 'Release 0.7.2':
    test/big5
    test/bounce
    test/bounce1
    test/bound
    test/honey
    test/missingboundary
    test/samp1
    test/spam44
    test/spam7
    test/spam8
    test/test1
    test/test8
    test/virus1
    test/virus13
    test/virus2
    test/virus3
    test/virus4
    test/virus5
    test/virus6
    test/virus7
2012-11-10 03:38:48 +00:00
16 changed files with 18813 additions and 132 deletions
+1 -1
View File
@@ -31,7 +31,7 @@ PROJECT_NAME = pymilter
# This could be handy for archiving the generated documentation or # This could be handy for archiving the generated documentation or
# if some version control system is used. # if some version control system is used.
PROJECT_NUMBER = 0.9.6 PROJECT_NUMBER = 1.0
# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute)
# base path where the generated documentation will be put. # base path where the generated documentation will be put.
+29 -24
View File
@@ -11,7 +11,6 @@
__version__ = '0.9.7' __version__ = '0.9.7'
import os import os
import re
import milter import milter
import thread import thread
@@ -49,9 +48,6 @@ OPTIONAL_CALLBACKS = {
'header':(P_NR_HDR,P_NOHDRS) 'header':(P_NR_HDR,P_NOHDRS)
} }
## @private
R = re.compile(r'%+')
## @private ## @private
def decode_mask(bits,names): def decode_mask(bits,names):
t = [ (s,getattr(milter,s)) for s in names] t = [ (s,getattr(milter,s)) for s in names]
@@ -107,7 +103,7 @@ def rejected_recipients(klass):
return enable_protocols(klass,P_RCPT_REJ) return enable_protocols(klass,P_RCPT_REJ)
## Milter leading space on headers. A class decorator that calls ## Milter leading space on headers. A class decorator that calls
# enable_protocols() with the P_HEAD_LEADSPC flag. By default, # enable_protocols() with the P_HDR_LEADSPC flag. By default,
# header continuation lines are collected and joined before getting # header continuation lines are collected and joined before getting
# sent to a milter. Headers modified or added by the milter are # sent to a milter. Headers modified or added by the milter are
# folded by the MTA as necessary according to its own standards. # folded by the MTA as necessary according to its own standards.
@@ -125,7 +121,7 @@ def rejected_recipients(klass):
# @param klass the %milter application class to modify # @param klass the %milter application class to modify
# @return the modified %milter class # @return the modified %milter class
def header_leading_space(klass): def header_leading_space(klass):
return enable_protocols(klass,P_HEAD_LEADSPC) return enable_protocols(klass,P_HDR_LEADSPC)
## Function decorator to disable callback methods. ## Function decorator to disable callback methods.
# If the MTA supports it, tells the MTA not to invoke this callback, # If the MTA supports it, tells the MTA not to invoke this callback,
@@ -263,7 +259,7 @@ class Base(object):
## Defined by subclasses to write log messages. ## Defined by subclasses to write log messages.
def log(self,*msg): pass def log(self,*msg): pass
## Called for each connection to the MTA. Called by the ## Called for each connection to the MTA. Called by the
# <a href="https://www.milter.org/developers/api/xxfi_connect"> # <a href="milter_api/xxfi_connect.html">
# xxfi_connect</a> callback. # xxfi_connect</a> callback.
# The <code>hostname</code> provided by the local MTA is either # The <code>hostname</code> provided by the local MTA is either
# the PTR name or the IP in the form "[1.2.3.4]" if no PTR is available. # the PTR name or the IP in the form "[1.2.3.4]" if no PTR is available.
@@ -300,7 +296,7 @@ class Base(object):
@nocallback @nocallback
def hello(self,hostname): return CONTINUE def hello(self,hostname): return CONTINUE
## Called when the SMTP client says MAIL FROM. Called by the ## Called when the SMTP client says MAIL FROM. Called by the
# <a href="https://www.milter.org/developers/api/xxfi_envfrom"> # <a href="milter_api/xxfi_envfrom.html">
# xxfi_envfrom</a> callback. # xxfi_envfrom</a> callback.
# Returning REJECT rejects the message, but not the connection. # Returning REJECT rejects the message, but not the connection.
# The sender is the "envelope" from as defined by # The sender is the "envelope" from as defined by
@@ -311,7 +307,7 @@ class Base(object):
@nocallback @nocallback
def envfrom(self,f,*str): return CONTINUE def envfrom(self,f,*str): return CONTINUE
## Called when the SMTP client says RCPT TO. Called by the ## Called when the SMTP client says RCPT TO. Called by the
# <a href="https://www.milter.org/developers/api/xxfi_envrcpt"> # <a href="milter_api/xxfi_envrcpt.html">
# xxfi_envrcpt</a> callback. # xxfi_envrcpt</a> callback.
# Returning REJECT rejects the current recipient, not the entire message. # Returning REJECT rejects the current recipient, not the entire message.
# The recipient is the "envelope" recipient as defined by # The recipient is the "envelope" recipient as defined by
@@ -377,7 +373,7 @@ class Base(object):
return p return p
## Negotiate milter protocol options. Called by the ## Negotiate milter protocol options. Called by the
# <a href="https://www.milter.org/developers/api/xxfi_negotiate"> # <a href="milter_api/xxfi_negotiate.html">
# xffi_negotiate</a> callback. This is an advanced callback, # xffi_negotiate</a> callback. This is an advanced callback,
# do not override unless you know what you are doing. Most # do not override unless you know what you are doing. Most
# negotiation can be done simply by using the supplied # negotiation can be done simply by using the supplied
@@ -408,7 +404,7 @@ class Base(object):
## Return the value of an MTA macro. Sendmail macro names ## Return the value of an MTA macro. Sendmail macro names
# are either single chars (e.g. "j") or multiple chars enclosed # are either single chars (e.g. "j") or multiple chars enclosed
# in braces (e.g. "{auth_type}"). Macro names are MTA dependent. # in braces (e.g. "{auth_type}"). Macro names are MTA dependent.
# See <a href="https://www.milter.org/developers/api/smfi_getsymval"> # See <a href="milter_api/smfi_getsymval.html">
# smfi_getsymval</a> for default sendmail macros. # smfi_getsymval</a> for default sendmail macros.
# @param sym the macro name # @param sym the macro name
def getsymval(self,sym): def getsymval(self,sym):
@@ -420,8 +416,17 @@ class Base(object):
# must be doubled, or libmilter will silently ignore the setreply. # must be doubled, or libmilter will silently ignore the setreply.
# Beginning with 0.9.6, we test for that case and throw ValueError to avoid # Beginning with 0.9.6, we test for that case and throw ValueError to avoid
# head scratching. What will <i>really</i> irritate you, however, # head scratching. What will <i>really</i> irritate you, however,
# is that if you carefully double any '%', your message will be # is that if you carefully double any '%%', your message will be
# sent - but with the '%' still doubled! # sent - but with the '%%' still doubled!
# See <a href="milter_api/smfi_setreply.html">
# smfi_setreply</a> for more information.
# @param rcode The three-digit (RFC 821/2821) SMTP reply code as a string.
# rcode cannot be None, and <b>must be a valid 4XX or 5XX reply code</b>.
# @param xcode The extended (RFC 1893/2034) reply code. If xcode is None,
# no extended code is used. Otherwise, xcode must conform to RFC 1893/2034.
# @param msg The text part of the SMTP reply. If msg is None,
# an empty message is used.
# @param ml Optional additional message lines.
def setreply(self,rcode,xcode=None,msg=None,*ml): def setreply(self,rcode,xcode=None,msg=None,*ml):
for m in (msg,)+ml: for m in (msg,)+ml:
if 1 in [len(s)&1 for s in R.findall(m)]: if 1 in [len(s)&1 for s in R.findall(m)]:
@@ -444,7 +449,7 @@ class Base(object):
# Milter methods which can only be called from eom callback. # Milter methods which can only be called from eom callback.
## Add a mail header field. ## Add a mail header field.
# Calls <a href="https://www.milter.org/developers/api/smfi_addheader"> # Calls <a href="milter_api/smfi_addheader.html">
# smfi_addheader</a>. # smfi_addheader</a>.
# The <code>Milter.ADDHDRS</code> action flag must be set. # The <code>Milter.ADDHDRS</code> action flag must be set.
# #
@@ -458,7 +463,7 @@ class Base(object):
return self._ctx.addheader(field,value,idx) return self._ctx.addheader(field,value,idx)
## Change the value of a mail header field. ## Change the value of a mail header field.
# Calls <a href="https://www.milter.org/developers/api/smfi_chgheader"> # Calls <a href="milter_api/smfi_chgheader.html">
# smfi_chgheader</a>. # smfi_chgheader</a>.
# The <code>Milter.CHGHDRS</code> action flag must be set. # The <code>Milter.CHGHDRS</code> action flag must be set.
# #
@@ -472,7 +477,7 @@ class Base(object):
return self._ctx.chgheader(field,idx,value) return self._ctx.chgheader(field,idx,value)
## Add a recipient to the message. ## Add a recipient to the message.
# Calls <a href="https://www.milter.org/developers/api/smfi_addrcpt"> # Calls <a href="milter_api/smfi_addrcpt.html">
# smfi_addrcpt</a>. # smfi_addrcpt</a>.
# If no corresponding mail header is added, this is like a Bcc. # If no corresponding mail header is added, this is like a Bcc.
# The syntax of the recipient is the same as used in the SMTP # The syntax of the recipient is the same as used in the SMTP
@@ -492,7 +497,7 @@ class Base(object):
raise DisabledAction("ADDRCPT_PAR") raise DisabledAction("ADDRCPT_PAR")
return self._ctx.addrcpt(rcpt,params) return self._ctx.addrcpt(rcpt,params)
## Delete a recipient from the message. ## Delete a recipient from the message.
# Calls <a href="https://www.milter.org/developers/api/smfi_delrcpt"> # Calls <a href="milter_api/smfi_delrcpt.html">
# smfi_delrcpt</a>. # smfi_delrcpt</a>.
# The recipient should match one passed to the envrcpt callback. # The recipient should match one passed to the envrcpt callback.
# The <code>Milter.DELRCPT</code> action flag must be set. # The <code>Milter.DELRCPT</code> action flag must be set.
@@ -505,7 +510,7 @@ class Base(object):
return self._ctx.delrcpt(rcpt) return self._ctx.delrcpt(rcpt)
## Replace the message body. ## Replace the message body.
# Calls <a href="https://www.milter.org/developers/api/smfi_replacebody"> # Calls <a href="milter_api/smfi_replacebody.html">
# smfi_replacebody</a>. # smfi_replacebody</a>.
# The entire message body must be replaced. # The entire message body must be replaced.
# Call repeatedly with blocks of data until the entire body is transferred. # Call repeatedly with blocks of data until the entire body is transferred.
@@ -519,7 +524,7 @@ class Base(object):
return self._ctx.replacebody(body) return self._ctx.replacebody(body)
## Change the SMTP envelope sender address. ## Change the SMTP envelope sender address.
# Calls <a href="https://www.milter.org/developers/api/smfi_chgfrom"> # Calls <a href="milter_api/smfi_chgfrom.html">
# smfi_chgfrom</a>. # smfi_chgfrom</a>.
# The syntax of the sender is that same as used in the SMTP # The syntax of the sender is that same as used in the SMTP
# MAIL FROM command (and as delivered to the envfrom callback), # MAIL FROM command (and as delivered to the envfrom callback),
@@ -536,7 +541,7 @@ class Base(object):
return self._ctx.chgfrom(sender,params) return self._ctx.chgfrom(sender,params)
## Quarantine the message. ## Quarantine the message.
# Calls <a href="https://www.milter.org/developers/api/smfi_quarantine"> # Calls <a href="milter_api/smfi_quarantine.html">
# smfi_quarantine</a>. # smfi_quarantine</a>.
# When quarantined, a message goes into the mailq as if to be delivered, # When quarantined, a message goes into the mailq as if to be delivered,
# but delivery is deferred until the message is unquarantined. # but delivery is deferred until the message is unquarantined.
@@ -550,7 +555,7 @@ class Base(object):
return self._ctx.quarantine(reason) return self._ctx.quarantine(reason)
## Tell the MTA to wait a bit longer. ## Tell the MTA to wait a bit longer.
# Calls <a href="https://www.milter.org/developers/api/smfi_progress"> # Calls <a href="milter_api/smfi_progress.html">
# smfi_progress</a>. # smfi_progress</a>.
# Resets timeouts in the MTA that detect a "hung" milter. # Resets timeouts in the MTA that detect a "hung" milter.
def progress(self): def progress(self):
@@ -564,9 +569,9 @@ class Milter(Base):
## Provide simple logging to sys.stdout ## Provide simple logging to sys.stdout
def log(self,*msg): def log(self,*msg):
print 'Milter:', print('Milter:',end=None)
for i in msg: print i, for i in msg: print(i,end=None)
print print()
@noreply @noreply
def connect(self,hostname,family,hostaddr): def connect(self,hostname,family,hostaddr):
+2 -1
View File
@@ -73,6 +73,7 @@ class Session(object):
if name.endswith('.'): name = name[:-1] if name.endswith('.'): name = name[:-1]
if not reduce(lambda x,y:x and 0 < len(y) < 64, name.split('.'),True): if not reduce(lambda x,y:x and 0 < len(y) < 64, name.split('.'),True):
return [] # invalid DNS name (too long or empty) return [] # invalid DNS name (too long or empty)
name = name.lower()
result = self.cache.get( (name, qtype) ) result = self.cache.get( (name, qtype) )
cname = None cname = None
if result: return result if result: return result
@@ -96,7 +97,7 @@ class Session(object):
#return result # if too many == NX_DOMAIN #return result # if too many == NX_DOMAIN
raise DNSError('Length of CNAME chain exceeds %d' % MAX_CNAME) raise DNSError('Length of CNAME chain exceeds %d' % MAX_CNAME)
cnames[name] = cname cnames[name] = cname
if cname in cnames: if cname.lower().rstrip('.') in cnames:
raise DNSError('CNAME loop') raise DNSError('CNAME loop')
result = self.dns(cname, qtype, cnames=cnames) result = self.dns(cname, qtype, cnames=cnames)
if result: if result:
+32 -3
View File
@@ -6,11 +6,14 @@ import re
import struct import struct
import socket import socket
import email.Errors import email.Errors
import email.base64mime
from fnmatch import fnmatchcase from fnmatch import fnmatchcase
from email.Header import decode_header from email.Header import decode_header
from binascii import a2b_base64
#import email.Utils #import email.Utils
import rfc822 import rfc822
dnsre = re.compile(r'^[a-z][-a-z\d.]+$', re.IGNORECASE)
PAT_IP4 = r'\.'.join([r'(?:\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])']*4) PAT_IP4 = r'\.'.join([r'(?:\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])']*4)
ip4re = re.compile(PAT_IP4+'$') ip4re = re.compile(PAT_IP4+'$')
ip6re = re.compile( '(?:%(hex4)s:){6}%(ls32)s$' ip6re = re.compile( '(?:%(hex4)s:){6}%(ls32)s$'
@@ -67,6 +70,12 @@ def iniplist(ipaddr,iplist):
True True
>>> iniplist('192.168.0.45',['192.168.0.*']) >>> iniplist('192.168.0.45',['192.168.0.*'])
True True
>>> iniplist('4.2.2.2',['b.resolvers.Level3.net'])
True
>>> iniplist('2607:f8b0:4004:801::',['google.com/40'])
True
>>> iniplist('4.2.2.2',['nothing.example.com'])
False
>>> iniplist('2001:610:779:0:223:6cff:fe9a:9cf3',['127.0.0.1','172.20.1.0/24','2001:610:779::/48']) >>> iniplist('2001:610:779:0:223:6cff:fe9a:9cf3',['127.0.0.1','172.20.1.0/24','2001:610:779::/48'])
True True
>>> iniplist('2G01:610:779:0:223:6cff:fe9a:9cf3',['127.0.0.1','172.20.1.0/24','2001:610:779::/48']) >>> iniplist('2G01:610:779:0:223:6cff:fe9a:9cf3',['127.0.0.1','172.20.1.0/24','2001:610:779::/48'])
@@ -75,8 +84,10 @@ def iniplist(ipaddr,iplist):
ValueError: Invalid ip syntax:2G01:610:779:0:223:6cff:fe9a:9cf3 ValueError: Invalid ip syntax:2G01:610:779:0:223:6cff:fe9a:9cf3
""" """
if ip4re.match(ipaddr): if ip4re.match(ipaddr):
fam = socket.AF_INET
ipnum = addr2bin(ipaddr) ipnum = addr2bin(ipaddr)
elif ip6re.match(ipaddr): elif ip6re.match(ipaddr):
fam = socket.AF_INET6
ipnum = bin2long6(inet_pton(ipaddr)) ipnum = bin2long6(inet_pton(ipaddr))
else: else:
raise ValueError('Invalid ip syntax:'+ipaddr) raise ValueError('Invalid ip syntax:'+ipaddr)
@@ -96,6 +107,13 @@ def iniplist(ipaddr,iplist):
n = 128 n = 128
if cidr(bin2long6(inet_pton(p[0])),n,MASK6) == cidr(ipnum,n,MASK6): if cidr(bin2long6(inet_pton(p[0])),n,MASK6) == cidr(ipnum,n,MASK6):
return True return True
elif dnsre.match(p[0]):
try:
sfx = '/'.join(['']+p[1:])
addrlist = [r[4][0]+sfx for r in socket.getaddrinfo(p[0],25,fam)]
if iniplist(ipaddr,addrlist):
return True
except socket.gaierror: pass
elif fnmatchcase(ipaddr,pat): elif fnmatchcase(ipaddr,pat):
return True return True
return False return False
@@ -103,6 +121,7 @@ def iniplist(ipaddr,iplist):
## Split email into Fullname and address. ## Split email into Fullname and address.
# This replaces <code>email.Utils.parseaddr</code> but fixes # This replaces <code>email.Utils.parseaddr</code> but fixes
# some <a href="http://bugs.python.org/issue1025395">tricky test cases</a>. # some <a href="http://bugs.python.org/issue1025395">tricky test cases</a>.
# Additional tricky cases are still broken. Patches welcome.
# #
def parseaddr(t): def parseaddr(t):
"""Split email into Fullname and address. """Split email into Fullname and address.
@@ -141,6 +160,16 @@ def parseaddr(t):
return rfc822.parseaddr('%s<%s>' % (t[:pos].strip(),addrspec)) return rfc822.parseaddr('%s<%s>' % (t[:pos].strip(),addrspec))
return res return res
## Fix email.base64mime.decode to add any missing padding
def decode(s, convert_eols=None):
if not s: return s
while len(s) % 4: s += '=' # add missing padding
dec = a2b_base64(s)
if convert_eols:
return dec.replace(CRLF, convert_eols)
return dec
email.base64mime.decode = decode
def parse_addr(t): def parse_addr(t):
"""Split email into user,domain. """Split email into user,domain.
@@ -189,9 +218,9 @@ def parse_header(val):
except LookupError: except LookupError:
u.append(unicode(s)) u.append(unicode(s))
else: else:
u.append(unicode(s)) u.append(unicode(s))
u = ''.join(u) u = u''.join(u)
for enc in ('us-ascii','iso-8859-1','utf8'): for enc in ('us-ascii','iso-8859-1','utf-8'):
try: try:
return u.encode(enc) return u.encode(enc)
except UnicodeError: continue except UnicodeError: continue
+4 -27
View File
@@ -11,25 +11,24 @@ any point, tell Sendmail to reject, discard, or accept the message.
Requirements Requirements
------------ ------------
This python milter extension: http://www.bmsi.com/python/milter.html Python milter extension: http://https://pypi.python.org/pypi/pymilter/
Python: http://www.python.org Python: http://www.python.org
Sendmail: http://www.sendmail.org Sendmail: http://www.sendmail.org
NB: From Sendmail's libmilter/README: NB: From Sendmail's libmilter/README:
libmilter requires pthread support in the operating system. Moreover, it libmilter requires pthread support in the operating system. Moreover, it
requires that the library functions it uses are thread safe; which is true requires that the library functions it uses are thread safe; which is true
for the operating systems libmilter has been developed and tested on. On for the operating systems libmilter has been developed and tested on. On
some operating systems this requires special compile time options (e.g., some operating systems this requires special compile time options (e.g.,
not just -pthread). libmilter is currently known to work on (modulo not just -pthread). libmilter is currently known to work on (modulo problems
problems in the pthread support of some specific versions): in the pthread support of some specific versions):
FreeBSD 3.x, 4.x FreeBSD 3.x, 4.x
SunOS 5.x (x >= 5) SunOS 5.x (x >= 5)
AIX 4.3.x AIX 4.3.x
HP UX 11.x HP UX 11.x
Linux (recent versions/distributions) Linux (recent versions/distributions)
OpenBSD
AIX 4.1.5
libmilter is currently not supported on: libmilter is currently not supported on:
@@ -110,28 +109,6 @@ _FFR_MILTER for the cf macros. For example,
m4 -D_FFR_MILTER ../m4/cf.m4 myconfig.mc > myconfig.cf m4 -D_FFR_MILTER ../m4/cf.m4 myconfig.mc > myconfig.cf
RedHat 6.2 Notes
----------------
The Redhat 6.2 sendmail RPM does not enable milter. You can obtain a
modified spec file at
http://www.bmsi.com/linux/rh62/sendmail-rhmilter.spec
use it to rebuild the Redhat 7.2 SRPM. The RH6.2 SRPM does not have
recent sendmail security patches.
RedHat 7.2 Notes
----------------
The Redhat 7.2 sendmail RPM enables milter in sendmail - but does not include
the headers needed for compiling a milter. You can obtain a modified spec
file with a sendmail-devel package that includes the needed static libraries
and headers at
http://www.bmsi.com/linux/sendmail-rh72.spec
IPv6 Notes IPv6 Notes
---------- ----------
+37 -4
View File
@@ -1,7 +1,7 @@
## @mainpage Writing Milters in Python ## @mainpage Writing Milters in Python
# #
# At the lowest level, the <code>milter</code> module provides a thin wrapper # At the lowest level, the <code>milter</code> module provides a thin wrapper
# around the <a href="https://www.milter.org/developers/api/index"> sendmail # around the <a href="milter_api/index.html"> sendmail
# libmilter API</a>. This API lets you register callbacks for a number of # libmilter API</a>. This API lets you register callbacks for a number of
# events in the process of sendmail receiving a message via SMTP. These # events in the process of sendmail receiving a message via SMTP. These
# events include the initial connection from a MTA, the envelope sender and # events include the initial connection from a MTA, the envelope sender and
@@ -37,10 +37,10 @@
# @section threading # @section threading
# #
# The libmilter library which pymilter wraps # The libmilter library which pymilter wraps
# <a href="https://www.milter.org/developers/overview#SignalHandling">handles # <a href="milter_overview#SignalHandling">handles
# all signals</a> itself, and expects to be called from a single main thread. # all signals</a> itself, and expects to be called from a single main thread.
# It handles SIGTERM, SIGHUP, and SIGINT, mapping the first two to # It handles SIGTERM, SIGHUP, and SIGINT, mapping the first two to
# <a href="https://www.milter.org/developers/api/smfi_stop">smfi_stop</a> # <a href="milter_api/smfi_stop.html">smfi_stop</a>
# and the last to an internal ABORT. # and the last to an internal ABORT.
# #
# If you use python threads or threading modules, then signal handling gets # If you use python threads or threading modules, then signal handling gets
@@ -50,4 +50,37 @@
# You may find the # You may find the
# <a href="http://docs.python.org/release/2.6.6/library/multiprocessing.html"> # <a href="http://docs.python.org/release/2.6.6/library/multiprocessing.html">
# multiprocessing</a> module useful. It can be a drop-in # multiprocessing</a> module useful. It can be a drop-in
# replacement for threading as illustrated in @ref milter-template.py. # replacement for threading as illustrated in
# <a href="milter-template_8py-example.html">milter-template.py</a>.
#
# @section Useful python packages for milters
#
# <a href="https://pypi.python.org/pypi/pyspf">pyspf</a> checks the
# SMTP envelope sender (MAIL FROM, passed to the Milter.Base.envfrom callback)
# against a Sender Policy published in DNS by the sending domain. This
# can prevent forgery of the MAIL FROM. SPF is Sender Policy Framework.
#
# <a href="https://launchpad.net/dkimpy">pydkim</a> checks a DKIM signature
# of the email body and headers against a public key published in DNS by
# the signing domain. DKIM is DomainKeys Identified Mail.
#
# The <a href="https://pypi.python.org/pypi/authres/">authres</a> module
# parses and formats the Authentication-Results email header, providing
# a standard place to summarize the results from DKIM, SPF, rDNS, SMTP AUTH,
# and other email authentication methods.
#
# <a href="https://pypi.python.org/pypi/pydspam/">pydspam</a> wraps
# the libdspam API of the <a href="http://dspam.sourceforge.net/">DSPAM</a>
# project.
#
# @section Milters written with pymilter
#
# <a href="https://github.com/croessner/vrfydmn">Verify Domain</a> is a
# Postfix milter that rejects/fixes manipulated From: header
# on a mail host with multiple virtual domains.
#
# <a href="https://pypi.python.org/pypi/milter/">BMS Milter</a> has several
# milters, a big complicated spam filter that integrates multiple
# authentication protocols with pydpsm, and two simple ones: spfmilter.py and
# dkim-milter.py.
#
+36 -20
View File
@@ -20,41 +20,47 @@
# and converts function callbacks to instance method invocations. # and converts function callbacks to instance method invocations.
# #
class milterContext(object): class milterContext(object):
## Calls <a href="https://www.milter.org/developers/api/smfi_getsymval">smfi_getsymval</a>. ## Calls <a href="milter_api/smfi_getsymval.html">smfi_getsymval</a>.
def getsymval(self,sym): pass def getsymval(self,sym): pass
## Calls <a href="https://www.milter.org/developers/api/smfi_setreply"> ## Calls <a href="milter_api/smfi_setreply.html">
# smfi_setreply</a> or # smfi_setreply</a> or
# <a href="https://www.milter.org/developers/api/smfi_setmlreply"> # <a href="milter_api/smfi_setmlreply.html">
# smfi_setmlreply</a>. # smfi_setmlreply</a>.
# @param rcode SMTP response code # @param rcode SMTP response code
# @param xcode extended SMTP response code # @param xcode extended SMTP response code
# @param msg one or more message lines. If the MTA does not support # @param msg one or more message lines. If the MTA does not support
# multiline messages, only the first is used. # multiline messages, only the first is used.
def setreply(self,rcode,xcode,*msg): pass def setreply(self,rcode,xcode,*msg): pass
## Calls <a href="https://www.milter.org/developers/api/smfi_addheader">smfi_addheader</a>. ## Calls <a href="milter_api/smfi_addheader.html">smfi_addheader</a>.
def addheader(self,name,value,idx=-1): pass def addheader(self,name,value,idx=-1): pass
## Calls <a href="https://www.milter.org/developers/api/smfi_chgheader">smfi_chgheader</a>. ## Calls <a href="milter_api/smfi_chgheader.html">smfi_chgheader</a>.
def chgheader(self,name,idx,value): pass def chgheader(self,name,idx,value): pass
## Calls <a href="https://www.milter.org/developers/api/smfi_addrcpt">smfi_addrcpt</a>. ## Calls <a href="milter_api/smfi_addrcpt.html">smfi_addrcpt</a>.
def addrcpt(self,rcpt,params=None): pass def addrcpt(self,rcpt,params=None): pass
## Calls <a href="https://www.milter.org/developers/api/smfi_delrcpt">smfi_delrcpt</a>. ## Calls <a href="milter_api/smfi_delrcpt.html">smfi_delrcpt</a>.
def delrcpt(self,rcpt): pass def delrcpt(self,rcpt): pass
## Calls <a href="https://www.milter.org/developers/api/smfi_replacebody">smfi_replacebody</a>. ## Calls <a href="milter_api/smfi_replacebody.html">smfi_replacebody</a>.
def replacebody(self,data): pass def replacebody(self,data): pass
## Attach a Python object to this connection context. ## Attach a Python object to this connection context.
# @return the old value or None # @return the old value or None
def setpriv(self,priv): pass def setpriv(self,priv): pass
## Return the Python object attached to this connection context. ## Return the Python object attached to this connection context.
def getpriv(self): pass def getpriv(self): pass
## Calls <a href="https://www.milter.org/developers/api/smfi_quarantine">smfi_quarantine</a>. ## Calls <a href="milter_api/smfi_quarantine.html">smfi_quarantine</a>.
def quarantine(self,reason): pass def quarantine(self,reason): pass
## Calls <a href="https://www.milter.org/developers/api/smfi_progress">smfi_progress</a>. ## Calls <a href="milter_api/smfi_progress.html">smfi_progress</a>.
def progress(self): pass def progress(self): pass
## Calls <a href="https://www.milter.org/developers/api/smfi_chgfrom">smfi_chgfrom</a>. ## Calls <a href="milter_api/smfi_chgfrom.html">smfi_chgfrom</a>.
def chgfrom(self,sender,param=None): pass def chgfrom(self,sender,param=None): pass
## Tell the MTA which macro values we are interested in for a given stage. ## Tell the MTA which macro values we are interested in for a given stage.
# Of interest only when you need to squeeze a few more bytes of bandwidth. # Of interest only when you need to squeeze a few more bytes of bandwidth.
def setsmlist(self,stage,macrolist): pass # It may only be called from the negotiate callback.
# The protocol stages are
# M_CONNECT, M_HELO, M_ENVFROM, M_ENVRCPT, M_DATA, M_EOM, M_EOH.
# Calls <a href="milter_api/smfi_setsymlist.html">smfi_setsymlist</a>.
# @param stage protocol stage in which the macro list should be used
# @param macrolist a space separated list of macro names
def setsymlist(self,stage,macrolist): pass
class error(Exception): pass class error(Exception): pass
@@ -91,40 +97,50 @@ def set_exception_policy(code): pass
# in the future (perhaps keeping the set functions for compatibility). # in the future (perhaps keeping the set functions for compatibility).
# @param name the milter name by which the MTA finds us # @param name the milter name by which the MTA finds us
# @param negotiate the # @param negotiate the
# <a href="https://www.milter.org/developers/api/xxfi_negotiate"> # <a href="milter_api/xxfi_negotiate.html">
# xxfi_negotiate</a> callback, called to negotiate supported # xxfi_negotiate</a> callback, called to negotiate supported
# actions, callbacks, and protocol steps. # actions, callbacks, and protocol steps.
# @param unknown the # @param unknown the
# <a href="https://www.milter.org/developers/api/xxfi_unknown"> # <a href="milter_api/xxfi_unknown.html">
# xxfi_unknown</a> callback, called when for SMTP commands # xxfi_unknown</a> callback, called when for SMTP commands
# not recognized by the MTA. (Extend SMTP in your milter!) # not recognized by the MTA. (Extend SMTP in your milter!)
# @param data the # @param data the
# <a href="https://www.milter.org/developers/api/xxfi_data"> # <a href="milter_api/xxfi_data.html">
# xxfi_data</a> callback, called when the DATA # xxfi_data</a> callback, called when the DATA
# SMTP command is received. # SMTP command is received.
def register(name,negotiate=None,unknown=None,data=None): pass def register(name,negotiate=None,unknown=None,data=None): pass
## Attempt to create the socket used to communicate with the MTA.
# milter.opensocket() attempts to create the socket specified previously by a
# call to milter.setconn() which will be the interface between MTAs and the
# %milter. This allows the calling application to ensure that the socket can be
# created. If this is not called, milter.main() will do so implicitly.
# Calls <a href="milter_api/smfi_opensocket.html">
# smfi_opensocket</a>. While not documented for libmilter, my experiments
# indicate that you must call register() before calling opensocket().
# @param rmsock Try to remove an existing unix domain socket if true.
def opensocket(rmsock): pass def opensocket(rmsock): pass
## Transfer control to libmilter. ## Transfer control to libmilter.
# Calls <a href="https://www.milter.org/developers/api/smfi_main"> # Calls <a href="milter_api/smfi_main.html">
# smfi_main</a>. # smfi_main</a>.
def main(): pass def main(): pass
## Set the libmilter debugging level. ## Set the libmilter debugging level.
# <a href="https://www.milter.org/developers/api/smfi_setdbg">smfi_setdbg</a> # <a href="milter_api/smfi_setdbg.html">smfi_setdbg</a>
# sets the milter library's internal debugging level to a new level # sets the %milter library's internal debugging level to a new level
# so that code details may be traced. A level of zero turns off debugging. The # so that code details may be traced. A level of zero turns off debugging. The
# greater (more positive) the level the more detailed the debugging. Six is the # greater (more positive) the level the more detailed the debugging. Six is the
# current, highest, useful value. Must be called before calling main(). # current, highest, useful value. Must be called before calling main().
def setdbg(lev): pass def setdbg(lev): pass
## Set timeout for MTA communication. ## Set timeout for MTA communication.
# Calls <a href="https://www.milter.org/developers/api/smfi_settimeout"> # Calls <a href="milter_api/smfi_settimeout.html">
# smfi_settimeout</a>. Must be called before calling main(). # smfi_settimeout</a>. Must be called before calling main().
def settimeout(secs): pass def settimeout(secs): pass
## Set socket backlog. ## Set socket backlog.
# Calls <a href="https://www.milter.org/developers/api/smfi_setbacklog"> # Calls <a href="milter_api/smfi_setbacklog.html">
# smfi_setbacklog</a>. Must be called before calling main(). # smfi_setbacklog</a>. Must be called before calling main().
def setbacklog(n): pass def setbacklog(n): pass
+3 -1
View File
@@ -1,6 +1,8 @@
web: web:
doxygen doxygen
rsync -ravK doc/html/ spidey2.bmsi.com:/Public/pymilter test -L doc/html/milter_api || ln -sf /usr/share/doc/sendmail-devel-* doc/html/milter_api
rsync -ravKk doc/html/ spidey2.bmsi.com:/Public/pymilter
cd doc/html; zip -r ../../doc .
VERSION=0.9.6 VERSION=0.9.6
CVSTAG=pymilter-0_9_6 CVSTAG=pymilter-0_9_6
+1 -1
View File
@@ -1,6 +1,6 @@
## To roll your own milter, create a class that extends Milter. ## To roll your own milter, create a class that extends Milter.
# See the pymilter project at http://bmsi.com/python/milter.html # See the pymilter project at http://bmsi.com/python/milter.html
# based on Sendmail's milter API http://www.milter.org/milter_api/api.html # based on Sendmail's milter API
# This code is open-source on the same terms as Python. # This code is open-source on the same terms as Python.
## Milter calls methods of your class at milter events. ## Milter calls methods of your class at milter events.
+45 -22
View File
@@ -35,6 +35,10 @@ $ python setup.py help
libraries=["milter","smutil","resolv"] libraries=["milter","smutil","resolv"]
* $Log$ * $Log$
* Revision 1.31 2012/04/12 23:32:50 customdesigned
* Replace redundant callback array with macros. If this doesn't break anything,
* macros can be eliminated with code changes.
*
* Revision 1.30 2012/04/12 23:08:06 customdesigned * Revision 1.30 2012/04/12 23:08:06 customdesigned
* Support RFC2553 on BSD * Support RFC2553 on BSD
* *
@@ -327,7 +331,7 @@ static struct MilterCallback {
{ NULL , NULL } { NULL , NULL }
}; };
staticforward struct smfiDesc description; /* forward declaration */ static struct smfiDesc description; /* forward declaration */
static PyObject *MilterError; static PyObject *MilterError;
/* The interpreter instance that called milter.main */ /* The interpreter instance that called milter.main */
@@ -339,7 +343,7 @@ typedef struct {
static milter_Diag diag; static milter_Diag diag;
staticforward PyTypeObject milter_ContextType; static PyTypeObject milter_ContextType;
typedef struct { typedef struct {
PyObject_HEAD PyObject_HEAD
@@ -676,7 +680,7 @@ _generic_wrapper(milter_ContextObject *self, PyObject *cb, PyObject *arglist) {
result = PyEval_CallObject(cb, arglist); result = PyEval_CallObject(cb, arglist);
Py_DECREF(arglist); Py_DECREF(arglist);
if (result == NULL) return _report_exception(self); if (result == NULL) return _report_exception(self);
if (!PyInt_Check(result)) { if (!PyLong_Check(result)) {
const struct MilterCallback *p; const struct MilterCallback *p;
const char *cbname = "milter"; const char *cbname = "milter";
char buf[40]; char buf[40];
@@ -691,7 +695,7 @@ _generic_wrapper(milter_ContextObject *self, PyObject *cb, PyObject *arglist) {
PyErr_SetString(MilterError,buf); PyErr_SetString(MilterError,buf);
return _report_exception(self); return _report_exception(self);
} }
retval = PyInt_AS_LONG(result); retval = PyLong_AS_LONG(result);
Py_DECREF(result); Py_DECREF(result);
_release_thread(self->t); _release_thread(self->t);
return retval; return retval;
@@ -708,7 +712,7 @@ makeipaddr(struct sockaddr_in *addr) {
sprintf(buf, "%d.%d.%d.%d", sprintf(buf, "%d.%d.%d.%d",
(int) (x>>24) & 0xff, (int) (x>>16) & 0xff, (int) (x>>24) & 0xff, (int) (x>>16) & 0xff,
(int) (x>> 8) & 0xff, (int) (x>> 0) & 0xff); (int) (x>> 8) & 0xff, (int) (x>> 0) & 0xff);
return PyString_FromString(buf); return PyUnicode_FromString(buf);
} }
#ifdef HAVE_IPV6_SUPPORT #ifdef HAVE_IPV6_SUPPORT
@@ -716,8 +720,8 @@ static PyObject *
makeip6addr(struct sockaddr_in6 *addr) { makeip6addr(struct sockaddr_in6 *addr) {
char buf[100]; /* must be at least INET6_ADDRSTRLEN + 1 */ char buf[100]; /* must be at least INET6_ADDRSTRLEN + 1 */
const char *s = inet_ntop(AF_INET6, &addr->sin6_addr, buf, sizeof buf); const char *s = inet_ntop(AF_INET6, &addr->sin6_addr, buf, sizeof buf);
if (s) return PyString_FromString(s); if (s) return PyUnicode_FromString(s);
return PyString_FromString("inet6:unknown"); return PyUnicode_FromString("inet6:unknown");
} }
#endif #endif
@@ -808,7 +812,7 @@ generic_env_wrapper(SMFICTX *ctx, PyObject*cb, char **argv) {
for (i=0;i<count;i++) { for (i=0;i<count;i++) {
/* There's some error checking performed in do_mkvalue() for a string */ /* There's some error checking performed in do_mkvalue() for a string */
/* that's not currently done here - it probably should be */ /* that's not currently done here - it probably should be */
PyObject *o = PyString_FromStringAndSize(argv[i], strlen(argv[i])); PyObject *o = PyUnicode_FromStringAndSize(argv[i], strlen(argv[i]));
if (o == NULL) { /* out of memory */ if (o == NULL) { /* out of memory */
Py_DECREF(arglist); Py_DECREF(arglist);
return _report_exception(self); return _report_exception(self);
@@ -939,7 +943,7 @@ milter_wrap_negotiate(SMFICTX *ctx,
int i; int i;
for (i = 0; i < 4; ++i) { for (i = 0; i < 4; ++i) {
*pa[i] = (i <= len) *pa[i] = (i <= len)
? PyInt_AsUnsignedLongMask(PyList_GET_ITEM(optlist,i)) ? PyLong_AsUnsignedLongMask(PyList_GET_ITEM(optlist,i))
: fa[i]; : fa[i];
} }
if (PyErr_Occurred()) { if (PyErr_Occurred()) {
@@ -1527,11 +1531,6 @@ static PyMethodDef context_methods[] = {
{ NULL, NULL } { NULL, NULL }
}; };
static PyObject *
milter_Context_getattr(PyObject *self, char *name) {
return Py_FindMethod(context_methods, self, name);
}
static struct smfiDesc description = { /* Set some reasonable defaults */ static struct smfiDesc description = { /* Set some reasonable defaults */
"pythonfilter", "pythonfilter",
SMFI_VERSION, SMFI_VERSION,
@@ -1580,14 +1579,13 @@ static PyMethodDef milter_methods[] = {
}; };
static PyTypeObject milter_ContextType = { static PyTypeObject milter_ContextType = {
PyObject_HEAD_INIT(&PyType_Type) PyVarObject_HEAD_INIT(&PyType_Type,0)
0,
"milterContext", "milterContext",
sizeof(milter_ContextObject), sizeof(milter_ContextObject),
0, 0,
milter_Context_dealloc, /* tp_dealloc */ milter_Context_dealloc, /* tp_dealloc */
0, /* tp_print */ 0, /* tp_print */
milter_Context_getattr, /* tp_getattr */ 0, /* tp_getattr */
0, /* tp_setattr */ 0, /* tp_setattr */
0, /* tp_compare */ 0, /* tp_compare */
0, /* tp_repr */ 0, /* tp_repr */
@@ -1601,6 +1599,13 @@ static PyTypeObject milter_ContextType = {
0, /* tp_setattro */ 0, /* tp_setattro */
0, /* tp_as_buffer */ 0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT, /* tp_flags */ Py_TPFLAGS_DEFAULT, /* tp_flags */
NULL, /* Documentation string */
0, /* call function for all accessible objects */
0, /* delete references to contained objects */
0, /* rich comparisons */
0, /* weak reference enabler */
0, 0, /* Iterators */
context_methods, /* Attribute descriptor and subclassing stuff */
}; };
static char milter_documentation[] = static char milter_documentation[] =
@@ -1610,17 +1615,27 @@ 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) { static void setitem(PyObject *d,const char *name,long val) {
PyObject *v = PyInt_FromLong(val); PyObject *v = PyLong_FromLong(val);
PyDict_SetItemString(d,name,v); PyDict_SetItemString(d,name,v);
Py_DECREF(v); Py_DECREF(v);
} }
void static struct PyModuleDef moduledef = {
initmilter(void) { PyModuleDef_HEAD_INIT,
"milter", /* m_name */
milter_documentation,/* m_doc */
-1, /* m_size */
milter_methods, /* m_methods */
NULL, /* m_reload */
NULL, /* m_traverse */
NULL, /* m_clear */
NULL, /* m_free */
};
PyMODINIT_FUNC PyInit_milter(void) {
PyObject *m, *d; PyObject *m, *d;
m = Py_InitModule4("milter", milter_methods, milter_documentation, m = PyModule_Create(&moduledef);
(PyObject*)NULL, PYTHON_API_VERSION);
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);
@@ -1647,6 +1662,13 @@ initmilter(void) {
#endif #endif
#ifdef SMFIF_SETSMLIST #ifdef SMFIF_SETSMLIST
setitem(d,"SETSMLIST",SMFIF_SETSMLIST); setitem(d,"SETSMLIST",SMFIF_SETSMLIST);
setitem(d,"M_CONNECT",SMFIM_CONNECT);/* connect */
setitem(d,"M_HELO",SMFIM_HELO); /* HELO/EHLO */
setitem(d,"M_ENVFROM",SMFIM_ENVFROM);/* MAIL From */
setitem(d,"M_ENVRCPT",SMFIM_ENVRCPT);/* RCPT To */
setitem(d,"M_DATA",SMFIM_DATA); /* DATA */
setitem(d,"M_EOM",SMFIM_EOM); /* end of message (final dot) */
setitem(d,"M_EOH",SMFIM_EOH); /* end of header */
#endif #endif
#ifdef SMFIS_ALL_OPTS #ifdef SMFIS_ALL_OPTS
setitem(d,"P_RCPT_REJ",SMFIP_RCPT_REJ); setitem(d,"P_RCPT_REJ",SMFIP_RCPT_REJ);
@@ -1679,4 +1701,5 @@ initmilter(void) {
setitem(d,"DISCARD", SMFIS_DISCARD); setitem(d,"DISCARD", SMFIS_DISCARD);
setitem(d,"ACCEPT", SMFIS_ACCEPT); setitem(d,"ACCEPT", SMFIS_ACCEPT);
setitem(d,"TEMPFAIL", SMFIS_TEMPFAIL); setitem(d,"TEMPFAIL", SMFIS_TEMPFAIL);
return m;
} }
+1 -2
View File
@@ -6,7 +6,7 @@
Summary: Python interface to sendmail milter API Summary: Python interface to sendmail milter API
Name: %{pythonbase}-pymilter Name: %{pythonbase}-pymilter
Version: 0.9.7 Version: 0.9.6
Release: 1%{dist} Release: 1%{dist}
Source: http://downloads.sourceforge.net/pymilter/pymilter-%{version}.tar.gz Source: http://downloads.sourceforge.net/pymilter/pymilter-%{version}.tar.gz
License: GPLv2+ License: GPLv2+
@@ -78,7 +78,6 @@ rm -rf $RPM_BUILD_ROOT
* Sat Feb 25 2012 Stuart Gathman <stuart@bmsi.com> 0.9.7-1 * Sat Feb 25 2012 Stuart Gathman <stuart@bmsi.com> 0.9.7-1
- Raise RuntimeError when result != CONTINUE for @noreply and @nocallback - Raise RuntimeError when result != CONTINUE for @noreply and @nocallback
- Remove redundant table in miltermodule - Remove redundant table in miltermodule
- Fix CNAME chain duplicating TXT records in Milter.dns (from pyspf).
* Sat Feb 25 2012 Stuart Gathman <stuart@bmsi.com> 0.9.6-1 * Sat Feb 25 2012 Stuart Gathman <stuart@bmsi.com> 0.9.6-1
- Raise ValueError on unescaped '%' passed to setreply - Raise ValueError on unescaped '%' passed to setreply
+19 -19
View File
@@ -1,4 +1,4 @@
from __future__ import print_function
# A simple milter. # A simple milter.
# Author: Stuart D. Gathman <stuart@bmsi.com> # Author: Stuart D. Gathman <stuart@bmsi.com>
@@ -21,9 +21,9 @@ class sampleMilter(Milter.Milter):
"Milter to replace attachments poisonous to Windows with a WARNING message." "Milter to replace attachments poisonous to Windows with a WARNING message."
def log(self,*msg): def log(self,*msg):
print "%s [%d]" % (strftime('%Y%b%d %H:%M:%S'),self.id), print("%s [%d]" % (strftime('%Y%b%d %H:%M:%S'),self.id), end=' ')
for i in msg: print i, for i in msg: print(i, end=' ')
print print()
def __init__(self): def __init__(self):
self.tempname = None self.tempname = None
@@ -60,23 +60,23 @@ class sampleMilter(Milter.Milter):
# even if we wanted the Taiwanese spam, we can't read Chinese # even if we wanted the Taiwanese spam, we can't read Chinese
# (delete if you read chinese mail) # (delete if you read chinese mail)
if val.startswith('=?big5') or val.startswith('=?ISO-2022-JP'): if val.startswith('=?big5') or val.startswith('=?ISO-2022-JP'):
self.log('REJECT: %s: %s' % (name,val)) self.log('REJECT: %s: %s' % (name,val))
#self.setreply('550','','Go away spammer') #self.setreply('550','','Go away spammer')
return Milter.REJECT return Milter.REJECT
# check for common spam keywords # check for common spam keywords
if val.find("$$$") >= 0 or val.find("XXX") >= 0 \ if val.find("$$$") >= 0 or val.find("XXX") >= 0 \
or val.find("!!!") >= 0 or val.find("FREE") >= 0: or val.find("!!!") >= 0 or val.find("FREE") >= 0:
self.log('REJECT: %s: %s' % (name,val)) self.log('REJECT: %s: %s' % (name,val))
#self.setreply('550','','Go away spammer') #self.setreply('550','','Go away spammer')
return Milter.REJECT return Milter.REJECT
# check for spam that pretends to be legal # check for spam that pretends to be legal
lval = val.lower() lval = val.lower()
if lval.startswith("adv:") or lval.startswith("adv.") \ if lval.startswith("adv:") or lval.startswith("adv.") \
or lval.find('viagra') >= 0: or lval.find('viagra') >= 0:
self.log('REJECT: %s: %s' % (name,val)) self.log('REJECT: %s: %s' % (name,val))
return Milter.REJECT return Milter.REJECT
# check for invalid message id # check for invalid message id
if lname == 'message-id' and len(val) < 4: if lname == 'message-id' and len(val) < 4:
@@ -123,7 +123,7 @@ class sampleMilter(Milter.Milter):
h = msg.getheaders(name) h = msg.getheaders(name)
cnt = len(h) cnt = len(h)
for i in range(cnt,0,-1): for i in range(cnt,0,-1):
self.chgheader(name,i-1,'') self.chgheader(name,i-1,'')
def eom(self): def eom(self):
if not self.fp: return Milter.ACCEPT if not self.fp: return Milter.ACCEPT
@@ -145,9 +145,9 @@ class sampleMilter(Milter.Milter):
msg = rfc822.Message(out) msg = rfc822.Message(out)
msg.rewindbody() msg.rewindbody()
while 1: while 1:
buf = out.read(8192) buf = out.read(8192)
if len(buf) == 0: break if len(buf) == 0: break
self.replacebody(buf) # feed modified message to sendmail self.replacebody(buf) # feed modified message to sendmail
return Milter.ACCEPT # ACCEPT modified message return Milter.ACCEPT # ACCEPT modified message
finally: finally:
out.close() out.close()
@@ -171,13 +171,13 @@ if __name__ == "__main__":
socketname = os.getenv("HOME") + "/pythonsock" socketname = os.getenv("HOME") + "/pythonsock"
Milter.factory = sampleMilter Milter.factory = sampleMilter
Milter.set_flags(Milter.CHGBODY + Milter.CHGHDRS + Milter.ADDHDRS) Milter.set_flags(Milter.CHGBODY + Milter.CHGHDRS + Milter.ADDHDRS)
print """To use this with sendmail, add the following to sendmail.cf: print("""To use this with sendmail, add the following to sendmail.cf:
O InputMailFilters=pythonfilter O InputMailFilters=pythonfilter
Xpythonfilter, S=local:%s Xpythonfilter, S=local:%s
See the sendmail README for libmilter. See the sendmail README for libmilter.
sample milter startup""" % socketname sample milter startup""" % socketname)
sys.stdout.flush() sys.stdout.flush()
Milter.runmilter("pythonfilter",socketname,240) Milter.runmilter("pythonfilter",socketname,240)
print "sample milter shutdown" print("sample milter shutdown")
+3 -3
View File
@@ -1,5 +1,5 @@
[bdist_rpm] [bdist_rpm]
python=python2.6 python=python3
doc_files=README NEWS TODO doc_files=README NEWS TODO COPYING CREDITS
packager=Stuart D. Gathman <stuart@bmsi.com> packager=Stuart D. Gathman <stuart@gathman.org>
release=1 release=1
+18587
View File
File diff suppressed because it is too large Load Diff
+6 -3
View File
@@ -1,4 +1,7 @@
# $Log$ # $Log$
# Revision 1.5 2011/06/09 17:27:42 customdesigned
# Documentation updates.
#
# Revision 1.4 2005/07/20 14:49:44 customdesigned # Revision 1.4 2005/07/20 14:49:44 customdesigned
# Handle corrupt and empty ZIP files. # Handle corrupt and empty ZIP files.
# #
@@ -69,7 +72,7 @@ class MimeTestCase(unittest.TestCase):
# python 2.4 doesn't get exceptions on missing boundaries, and # python 2.4 doesn't get exceptions on missing boundaries, and
# if message is modified, output is readable by mail clients # if message is modified, output is readable by mail clients
if sys.hexversion < 0x02040000: if sys.hexversion < 0x02040000:
self.fail('should get boundary error parsing bad rfc822 attachment') self.fail('should get boundary error parsing bad rfc822 attachment')
except Errors.BoundaryError: except Errors.BoundaryError:
pass pass
@@ -170,7 +173,7 @@ class MimeTestCase(unittest.TestCase):
msg = mime.message_from_file(open('test/'+fname,'r')) msg = mime.message_from_file(open('test/'+fname,'r'))
mime.defang(msg,scan_zip=True) mime.defang(msg,scan_zip=True)
self.failIf(msg.ismodified()) self.failIf(msg.ismodified())
msg = mime.message_from_file(open('test/tmpytgcE5.fail','r')) msg = mime.message_from_file(open('test/test2','r'))
rc = mime.check_attachments(msg,self._chk_attach) rc = mime.check_attachments(msg,self._chk_attach)
self.assertEquals(self.filename,"7501'S FOR TWO GOLDEN SOURCES SHIPMENTS FOR TAX & DUTY PURPOSES ONLY.PDF") self.assertEquals(self.filename,"7501'S FOR TWO GOLDEN SOURCES SHIPMENTS FOR TAX & DUTY PURPOSES ONLY.PDF")
self.assertEquals(rc,Milter.CONTINUE) self.assertEquals(rc,Milter.CONTINUE)
@@ -198,4 +201,4 @@ if __name__ == '__main__':
fp = open(fname,'r') fp = open(fname,'r')
msg = mime.message_from_file(fp) msg = mime.message_from_file(fp)
mime.defang(msg,scan_zip=True) mime.defang(msg,scan_zip=True)
print msg.as_string() print(msg.as_string())
+7 -1
View File
@@ -11,7 +11,8 @@ class AddrCacheTestCase(unittest.TestCase):
self.fname = 'test.dat' self.fname = 'test.dat'
def tearDown(self): def tearDown(self):
os.remove(self.fname) if os.path.exists(self.fname):
os.remove(self.fname)
def testAdd(self): def testAdd(self):
cache = AddrCache(fname=self.fname) cache = AddrCache(fname=self.fname)
@@ -38,6 +39,11 @@ class AddrCacheTestCase(unittest.TestCase):
cache.load(self.fname,30) cache.load(self.fname,30)
self.failUnless('spammer.com' in cache) self.failUnless('spammer.com' in cache)
def testParseHeader(self):
s='=?UTF-8?B?TGFzdCBGZXcgQ29sZHBsYXkgQWxidW0gQXJ0d29ya3MgQXZhaWxhYmxlAA?='
h = Milter.utils.parse_header(s)
self.assertEqual(h,'Last Few Coldplay Album Artworks Available\x00')
def suite(): def suite():
s = unittest.makeSuite(AddrCacheTestCase,'test') s = unittest.makeSuite(AddrCacheTestCase,'test')
s.addTest(doctest.DocTestSuite(Milter.utils)) s.addTest(doctest.DocTestSuite(Milter.utils))