Compare commits

..

14 Commits

Author SHA1 Message Date
cvs2svn 4b1a24df60 This commit was manufactured by cvs2svn to create tag 'pymilter-0_9_5'.
Sprout from master 2011-08-19 04:57:20 UTC Stuart Gathman <stuart@gathman.org> 'Release 0.9.5'
Cherrypick from bmsi 2005-05-31 18:23:49 UTC Stuart Gathman <stuart@gathman.org> 'Development changes since 0.7.2':
    sample.py
    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
2011-08-19 04:57:21 +00:00
Stuart Gathman 1ba522e501 Release 0.9.5 2011-08-19 04:57:20 +00:00
Stuart Gathman a43649f2ce Make addr2bin and dynip handle IP6. 2011-08-19 04:53:56 +00:00
Stuart Gathman de679b1514 Add parameterless class decorators for P_RCPT_REJ and P_HEAD_LEADSPC 2011-06-17 19:41:23 +00:00
Stuart Gathman b946759857 Document threading limitations and show multiprocessing example. 2011-06-10 01:39:59 +00:00
Stuart Gathman f6702e39dd Require python-2.6.5 2011-06-09 21:36:26 +00:00
Stuart Gathman 5a8aaf85d7 Release 0.9.5 2011-06-09 19:55:31 +00:00
Stuart Gathman 720db3d7bd Doc updates. 2011-06-09 18:14:23 +00:00
Stuart Gathman a46627959c Documentation updates. 2011-06-09 17:27:43 +00:00
Stuart Gathman 4e0d3da07d Print callback name for non-int return error. 2011-06-09 15:45:27 +00:00
Stuart Gathman 53c7519922 Generate special exception when callback return not int. 2011-06-08 23:13:48 +00:00
Stuart Gathman b3d6328167 Fix template so it actually runs - makes a better example that way :-) 2011-06-08 22:34:53 +00:00
Stuart Gathman 2133942c19 Tolerate illegal chars 2011-05-17 21:51:57 +00:00
Stuart Gathman eef3cde27e Python2.6 SMTP.close() fails when instance never connected. 2011-03-18 20:41:31 +00:00
15 changed files with 397 additions and 93 deletions
+2 -2
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.3 PROJECT_NUMBER = 0.9.5
# 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.
@@ -814,7 +814,7 @@ DOCSET_FEEDNAME = "Doxygen generated docs"
# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen # reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen
# will append .docset to the name. # will append .docset to the name.
DOCSET_BUNDLE_ID = org.doxygen.Project DOCSET_BUNDLE_ID = com.bmsi.pymilter
# If the GENERATE_HTMLHELP tag is set to YES, additional index files # If the GENERATE_HTMLHELP tag is set to YES, additional index files
# will be generated that can be used as input for tools like the # will be generated that can be used as input for tools like the
+153 -37
View File
@@ -8,7 +8,7 @@
# Copyright 2001,2009 Business Management Systems, Inc. # Copyright 2001,2009 Business Management Systems, Inc.
# This code is under the GNU General Public License. See COPYING for details. # This code is under the GNU General Public License. See COPYING for details.
__version__ = '0.9.3' __version__ = '0.9.5'
import os import os
import milter import milter
@@ -19,8 +19,14 @@ from milter import *
_seq_lock = thread.allocate_lock() _seq_lock = thread.allocate_lock()
_seq = 0 _seq = 0
## @fn set_flags(flags)
# @brief Enable optional %milter actions.
# Certain %milter actions need to be enabled before calling milter.runmilter()
# or they throw an exception.
# @param flags Bit ored mask of optional actions to enable
def uniqueID(): def uniqueID():
"""Return a sequence number unique to this process. """Return a unique sequence number (incremented on each call).
""" """
global _seq global _seq
_seq_lock.acquire() _seq_lock.acquire()
@@ -28,6 +34,7 @@ def uniqueID():
_seq_lock.release() _seq_lock.release()
return seqno return seqno
## @private
OPTIONAL_CALLBACKS = { OPTIONAL_CALLBACKS = {
'connect':(P_NR_CONN,P_NOCONNECT), 'connect':(P_NR_CONN,P_NOCONNECT),
'hello':(P_NR_HELO,P_NOHELO), 'hello':(P_NR_HELO,P_NOHELO),
@@ -40,6 +47,7 @@ OPTIONAL_CALLBACKS = {
'header':(P_NR_HDR,P_NOHDRS) 'header':(P_NR_HDR,P_NOHDRS)
} }
## @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]
nms = [s for s,m in t if bits & m] nms = [s for s,m in t if bits & m]
@@ -49,16 +57,17 @@ def decode_mask(bits,names):
## Class decorator to enable optional protocol steps. ## Class decorator to enable optional protocol steps.
# P_SKIP is enabled by default when supported, but # P_SKIP is enabled by default when supported, but
# milter applications may wish to enable P_HDR_LEADSPC # applications may wish to enable P_HDR_LEADSPC
# to send and receive the leading space of header continuation # to send and receive the leading space of header continuation
# lines unchanged, and/or P_RCPT_REJ to have recipients # lines unchanged, and/or P_RCPT_REJ to have recipients
# detected as invalid by the MTA passed to the envcrpt callback. # detected as invalid by the MTA passed to the envcrpt callback.
# #
# Applications may want to check whether the protocol is actually # Applications may want to check whether the protocol is actually
# supported by the MTA in use. The <code>_protocol</code> # supported by the MTA in use. Base._protocol
# member is a bitmask of protocol options negotiated. So, # is a bitmask of protocol options negotiated. So,
# for instance, if <code>self._protocol & Milter.P_RCPT_REJ</code> # for instance, if <code>self._protocol & Milter.P_RCPT_REJ</code>
# is true, then that feature was successfully negotiated with the MTA. # is true, then that feature was successfully negotiated with the MTA
# and the application will see recipients the MTA has flagged as invalid.
# #
# Sample use: # Sample use:
# <pre> # <pre>
@@ -68,21 +77,59 @@ def decode_mask(bits,names):
# myMilter = Milter.enable_protocols(myMilter,Milter.P_RCPT_REJ) # myMilter = Milter.enable_protocols(myMilter,Milter.P_RCPT_REJ)
# </pre> # </pre>
# @since 0.9.3 # @since 0.9.3
# @param klass the milter application class to modify # @param klass the %milter application class to modify
# @param mask a bitmask of protocol steps to enable # @param mask a bitmask of protocol steps to enable
# @return the modified milter class # @return the modified %milter class
def enable_protocols(klass,mask): def enable_protocols(klass,mask):
klass._protocol_mask = klass.protocol_mask() & ~mask klass._protocol_mask = klass.protocol_mask() & ~mask
return klass return klass
## Milter rejected recipients. A class decorator that calls
# enable_protocols() with the P_RCPT_REJ flag. By default, the MTA
# does not pass recipients that it knows are invalid on to the milter.
# This decorator enables a %milter app to see all recipients if supported
# by the MTA. Use like this with python-2.6 and later:
# <pre>
# @@Milter.rejected_recipients
# class myMilter(Milter.Base):
# def envrcpt(self,to,*params):
# return Milter.CONTINUE
# </pre>
# @since 0.9.5
# @param klass the %milter application class to modify
# @return the modified %milter class
def rejected_recipients(klass):
return enable_protocols(klass,P_RCPT_REJ)
## Milter leading space on headers. A class decorator that calls
# enable_protocols() with the P_HEAD_LEADSPC flag. By default,
# header continuation lines are collected and joined before getting
# sent to a milter. Headers modified or added by the milter are
# folded by the MTA as necessary according to its own standards.
# With this flag, header continuation lines are preserved
# with their newlines and leading space. In addition, header folding
# done by the milter is preserved as well.
# Use like this with python-2.6 and later:
# <pre>
# @@Milter.header_leading_space
# class myMilter(Milter.Base):
# def header(self,hname,value):
# return Milter.CONTINUE
# </pre>
# @since 0.9.5
# @param klass the %milter application class to modify
# @return the modified %milter class
def header_leading_space(klass):
return enable_protocols(klass,P_HEAD_LEADSPC)
## Function decorator to disable callback methods. ## Function decorator to disable callback methods.
# If the MTA supports it, tells the MTA not to call this callback, # If the MTA supports it, tells the MTA not to invoke this callback,
# increasing efficiency. All the callbacks (except negotiate) # increasing efficiency. All the callbacks (except negotiate)
# are disabled in Milter.Base, and overriding them reenables the # are disabled in Milter.Base, and overriding them reenables the
# callback. An application may need to use @@nocallback when it extends # callback. An application may need to use @@nocallback when it extends
# another milter and wants to disable a callback again. # another %milter and wants to disable a callback again.
# The disabled method should still return Milter.CONTINUE, in case the MTA does # The disabled method should still return Milter.CONTINUE, in case the MTA does
# not support protocol negotiation. # not support protocol negotiation, and for when called from a test harness.
# @since 0.9.2 # @since 0.9.2
def nocallback(func): def nocallback(func):
try: try:
@@ -122,45 +169,81 @@ def noreply(func):
class DisabledAction(RuntimeError): class DisabledAction(RuntimeError):
pass pass
## A do "nothing" Milter base class. ## A do "nothing" Milter base class representing an SMTP connection.
#
# Python milters should derive from this class # Python milters should derive from this class
# unless they are using the low lever milter module directly. # unless they are using the low level milter module directly.
# All optional callbacks are disabled, and automatically #
# reenabled when overridden. # Most of the methods are either "actions" or "callbacks". Callbacks
# are invoked by the MTA at certain points in the SMTP protocol. For
# instance when the HELO command is seen, the MTA calls the helo
# callback before returning a response code. All callbacks must
# return one of these constants: CONTINUE, TEMPFAIL, REJECT, ACCEPT,
# DISCARD, SKIP. The NOREPLY response is supplied automatically by
# the @@noreply decorator if negotiation with the MTA is successful.
# @@noreply and @@nocallback methods should return CONTINUE for two reasons:
# the MTA may not support negotiation, and the class may be running in a test
# harness.
#
# Optional callbacks are disabled with the @@nocallback decorator, and
# automatically reenabled when overridden. Disabled callbacks should
# still return CONTINUE for testing and MTAs that do not support
# negotiation.
# Each SMTP connection to the MTA calls the factory method you provide to
# create an instance derived from this class. This is typically the
# constructor for a class derived from Base. The _setctx() method attaches
# the instance to the low level milter.milterContext object. When the SMTP
# connection terminates, the close callback is called, the low level connection
# object is destroyed, and this normally causes instances of this class to be
# garbage collected as well. The close() method should release any global
# resources held by instances.
# @since 0.9.2 # @since 0.9.2
class Base(object): class Base(object):
"The core class interface to the milter module." "The core class interface to the %milter module."
## Attach this Milter to the low level milter.milterContext object. ## Attach this Milter to the low level milter.milterContext object.
def _setctx(self,ctx): def _setctx(self,ctx):
## The low level @ref milter.milterContext object.
self._ctx = ctx self._ctx = ctx
self._actions = CURR_ACTS # all actions enabled by default ## A bitmask of actions this connection has negotiated to use.
self._protocol = 0 # no protocol options by default # By default, all actions are enabled. High throughput milters
if ctx: # may want to disable unused actions to increase efficiency.
ctx.setpriv(self) # Some optional actions may be disabled by calling milter.set_flags(), or
## @var _actions # by overriding the negotiate callback. The bits include:
# A bitmask of actions this milter has negotiated to use.
# By default, all actions are enabled. This may be changed
# by calling <code>milter.set_flags</code>, or by overriding
# the negotiate callback. The bits include:
# <code>ADDHDRS,CHGBODY,MODBODY,ADDRCPT,ADDRCPT_PAR,DELRCPT # <code>ADDHDRS,CHGBODY,MODBODY,ADDRCPT,ADDRCPT_PAR,DELRCPT
# CHGHDRS,QUARANTINE,CHGFROM,SETSMLIST</code>. # CHGHDRS,QUARANTINE,CHGFROM,SETSMLIST</code>.
# The <code>Milter.CURR_ACTS</code> bitmask is all actions # The <code>Milter.CURR_ACTS</code> bitmask is all actions
# known when the milter module was compiled. # known when the milter module was compiled.
# Application code can also inspect this field to determine
# which actions are available. This is especially useful in
# generic library code designed to work in multiple milters.
# @since 0.9.2 # @since 0.9.2
# #
self._actions = CURR_ACTS # all actions enabled by default
## @var _protocol ## A bitmask of protocol options this connection has negotiated.
# A bitmask of protocol options this milter has negotiated. # An application may inspect this
# The bits generally indicate that a particular step should be # variable to determine which protocol steps are supported. Options
# skipped, since previous versions of the milter protocol had # of interest to applications: the SKIP result code is allowed
# no provision for skipping steps. # only if the P_SKIP bit is set, rejected recipients are passed to the
# %milter application only if the P_RCPT_REJ bit is set, and
# header values are sent and received with leading spaces (in the
# continuation lines) intact if the P_HDR_LEADSPC bit is set (so
# that the application can customize indenting).
#
# The P_N* bits should be negotiated via the @@noreply and @@nocallback
# method decorators, and P_RCPT_REJ, P_HDR_LEADSPC should
# be enabled using the enable_protocols class decorator.
#
# The bits include: <code> # The bits include: <code>
# P_RCPT_REJ P_NR_CONN P_NR_HELO P_NR_MAIL P_NR_RCPT P_NR_DATA P_NR_UNKN # P_RCPT_REJ P_NR_CONN P_NR_HELO P_NR_MAIL P_NR_RCPT P_NR_DATA P_NR_UNKN
# P_NR_EOH P_NR_BODY P_NR_HDR P_NOCONNECT P_NOHELO P_NOMAIL P_NORCPT # P_NR_EOH P_NR_BODY P_NR_HDR P_NOCONNECT P_NOHELO P_NOMAIL P_NORCPT
# P_NODATA P_NOUNKNOWN P_NOEOH P_NOBODY P_NOHDRS P_HDR_LEADSPC P_SKIP # P_NODATA P_NOUNKNOWN P_NOEOH P_NOBODY P_NOHDRS P_HDR_LEADSPC P_SKIP
# </code> (all under the Milter namespace). # </code> (all under the Milter namespace).
# @since 0.9.2 # @since 0.9.2
self._protocol = 0 # no protocol options by default
if ctx:
ctx.setpriv(self)
## Defined by subclasses to write log messages. ## Defined by subclasses to write log messages.
def log(self,*msg): pass def log(self,*msg): pass
@@ -251,10 +334,17 @@ class Base(object):
klass._protocol_mask = p klass._protocol_mask = p
return p return p
## Negotiate milter protocol options. ## Negotiate milter protocol options. Called by the
# <a href="https://www.milter.org/developers/api/xxfi_negotiate">
# xffi_negotiate</a> callback.
# Options are passed as
# a list of 4 32-bit ints which can be modified and are passed
# back to libmilter on return.
# Default negotiation sets P_NO* and P_NR* for callbacks # Default negotiation sets P_NO* and P_NR* for callbacks
# marked @@nocallback and @@noreply respectively, leaves all # marked @@nocallback and @@noreply respectively, leaves all
# actions enabled, and enables Milter.SKIP. # actions enabled, and enables Milter.SKIP. The @@enable_protocols
# class decorator can customize which protocol steps are implemented.
# @param opts a modifiable list of 4 ints with negotiated options
# @since 0.9.2 # @since 0.9.2
def negotiate(self,opts): def negotiate(self,opts):
try: try:
@@ -299,28 +389,36 @@ 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">
# smfi_addheader</a>.
# The <code>Milter.ADDHDRS</code> action flag must be set. # The <code>Milter.ADDHDRS</code> action flag must be set.
# #
# May be called from eom callback only. # May be called from eom callback only.
# @param field the header field name # @param field the header field name
# @param value the header field value # @param value the header field value
# @param idx header field index from the top of the message to insert at # @param idx header field index from the top of the message to insert at
# @throws DisabledAction if ADDHDRS is not enabled
def addheader(self,field,value,idx=-1): def addheader(self,field,value,idx=-1):
if not self._actions & ADDHDRS: raise DisabledAction("ADDHDRS") if not self._actions & ADDHDRS: raise DisabledAction("ADDHDRS")
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">
# smfi_chgheader</a>.
# The <code>Milter.CHGHDRS</code> action flag must be set. # The <code>Milter.CHGHDRS</code> action flag must be set.
# #
# May be called from eom callback only. # May be called from eom callback only.
# @param field the name of the field to change # @param field the name of the field to change
# @param idx index of the field to change when there are multiple instances # @param idx index of the field to change when there are multiple instances
# @param value the new value of the field # @param value the new value of the field
# @throws DisabledAction if CHGHDRS is not enabled
def chgheader(self,field,idx,value): def chgheader(self,field,idx,value):
if not self._actions & CHGHDRS: raise DisabledAction("CHGHDRS") if not self._actions & CHGHDRS: raise DisabledAction("CHGHDRS")
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">
# 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
# RCPT TO command (and as delivered to the envrcpt callback), for example # RCPT TO command (and as delivered to the envrcpt callback), for example
@@ -332,33 +430,42 @@ class Base(object):
# May be called from eom callback only. # May be called from eom callback only.
# @param rcpt the message recipient # @param rcpt the message recipient
# @param params an optional list of ESMTP parameters # @param params an optional list of ESMTP parameters
# @throws DisabledAction if ADDRCPT or ADDRCPT_PAR is not enabled
def addrcpt(self,rcpt,params=None): def addrcpt(self,rcpt,params=None):
if not self._actions & ADDRCPT: raise DisabledAction("ADDRCPT") if not self._actions & ADDRCPT: raise DisabledAction("ADDRCPT")
if params and not self._actions & ADDRCPT_PAR: if params and not self._actions & ADDRCPT_PAR:
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">
# 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.
# #
# May be called from eom callback only. # May be called from eom callback only.
# @param rcpt the message recipient to delete # @param rcpt the message recipient to delete
# @throws DisabledAction if DELRCPT is not enabled
def delrcpt(self,rcpt): def delrcpt(self,rcpt):
if not self._actions & DELRCPT: raise DisabledAction("DELRCPT") if not self._actions & DELRCPT: raise DisabledAction("DELRCPT")
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">
# 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.
# The <code>Milter.MODBODY</code> action flag must be set. # The <code>Milter.MODBODY</code> action flag must be set.
# #
# May be called from eom callback only. # May be called from eom callback only.
# @param body a chunk of body data # @param body a chunk of body data
# @throws DisabledAction if MODBODY is not enabled
def replacebody(self,body): def replacebody(self,body):
if not self._actions & MODBODY: raise DisabledAction("MODBODY") if not self._actions & MODBODY: raise DisabledAction("MODBODY")
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">
# 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),
# for example <code>self.chgfrom('<bar@example.com>')</code>. # for example <code>self.chgfrom('<bar@example.com>')</code>.
@@ -368,22 +475,28 @@ class Base(object):
# @since 0.9.1 # @since 0.9.1
# @param sender the new sender address # @param sender the new sender address
# @param params an optional list of ESMTP parameters # @param params an optional list of ESMTP parameters
# @throws DisabledAction if CHGFROM is not enabled
def chgfrom(self,sender,params=None): def chgfrom(self,sender,params=None):
if not self._actions & CHGFROM: raise DisabledAction("CHGFROM") if not self._actions & CHGFROM: raise DisabledAction("CHGFROM")
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">
# 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.
# The <code>Milter.QUARANTINE</code> action flag must be set. # The <code>Milter.QUARANTINE</code> action flag must be set.
# #
# May be called from eom callback only. # May be called from eom callback only.
# @param reason a string describing the reason for quarantine # @param reason a string describing the reason for quarantine
# @throws DisabledAction if QUARANTINE is not enabled
def quarantine(self,reason): def quarantine(self,reason):
if not self._actions & QUARANTINE: raise DisabledAction("QUARANTINE") if not self._actions & QUARANTINE: raise DisabledAction("QUARANTINE")
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">
# 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):
return self._ctx.progress() return self._ctx.progress()
@@ -465,12 +578,14 @@ class Milter(Base):
factory = Milter factory = Milter
## @private ## @private
# @brief Connect context to connection instance and return enabled callbacks.
def negotiate_callback(ctx,opts): def negotiate_callback(ctx,opts):
m = factory() m = factory()
m._setctx(ctx) m._setctx(ctx)
return m.negotiate(opts) return m.negotiate(opts)
## @private ## @private
# @brief Connect context if needed and invoke connect method.
def connect_callback(ctx,hostname,family,hostaddr,nr_mask=P_NR_CONN): def connect_callback(ctx,hostname,family,hostaddr,nr_mask=P_NR_CONN):
m = ctx.getpriv() m = ctx.getpriv()
if not m: if not m:
@@ -481,6 +596,7 @@ def connect_callback(ctx,hostname,family,hostaddr,nr_mask=P_NR_CONN):
return m.connect(hostname,family,hostaddr) return m.connect(hostname,family,hostaddr)
## @private ## @private
# @brief Disconnect milterContext and call close method.
def close_callback(ctx): def close_callback(ctx):
m = ctx.getpriv() m = ctx.getpriv()
if not m: return CONTINUE if not m: return CONTINUE
@@ -527,11 +643,11 @@ def envcallback(c,args):
pargs.append(s) pargs.append(s)
return c(*pargs,**kw) return c(*pargs,**kw)
## Run the milter. ## Run the %milter.
# @param name the name of the milter known by the MTA # @param name the name of the %milter known to the MTA
# @param socketname the socket to be passed to <code>milter.setconn</code> # @param socketname the socket to be passed to milter.setconn()
# @param timeout the time in secs the MTA should wait for a response before # @param timeout the time in secs the MTA should wait for a response before
# considering this milter dead # considering this %milter dead
def runmilter(name,socketname,timeout = 0): def runmilter(name,socketname,timeout = 0):
# This bit is here on the assumption that you will be starting this filter # This bit is here on the assumption that you will be starting this filter
# before sendmail. If sendmail is not running and the socket already exists, # before sendmail. If sendmail is not running and the socket already exists,
+4 -1
View File
@@ -5,6 +5,9 @@
# Send DSNs, do call back verification, # Send DSNs, do call back verification,
# and generate DSN messages from a template # and generate DSN messages from a template
# $Log$ # $Log$
# Revision 1.21 2011/03/03 05:11:58 customdesigned
# Release 0.9.4
#
# Revision 1.20 2010/10/11 00:29:47 customdesigned # Revision 1.20 2010/10/11 00:29:47 customdesigned
# Handle multiple recipients. For CBV or auto whitelist of multiple emails. # Handle multiple recipients. For CBV or auto whitelist of multiple emails.
# #
@@ -149,7 +152,7 @@ def send_dsn(mailfrom,receiver,msg=None,timeout=600,session=None,ourfrom=''):
pass # MX didn't accept connections, try next one pass # MX didn't accept connections, try next one
except socket.timeout: except socket.timeout:
pass # MX too slow, try next one pass # MX too slow, try next one
smtp.close() if hasattr(smtp,'sock'): smtp.close()
if time.time() > toolate: if time.time() > toolate:
return (450,'No MX response within %f minutes'%(timeout/60.0)) return (450,'No MX response within %f minutes'%(timeout/60.0))
return (450,'No MX servers available') # temp error return (450,'No MX servers available') # temp error
+2 -1
View File
@@ -48,9 +48,10 @@ def is_dynip(host,addr):
True True
""" """
if host.startswith('[') and host.endswith(']'): if host.startswith('[') and host.endswith(']'):
return True return True # no ptr
if addr: if addr:
if host.find(addr) >= 0: return True if host.find(addr) >= 0: return True
if addr.find(':') >= 0: return False # IP6
a = addr.split('.') a = addr.split('.')
ia = map(int,a) ia = map(int,a)
h = host h = host
+12 -6
View File
@@ -28,16 +28,21 @@ ip6re = re.compile( '(?:%(hex4)s:){6}%(ls32)s$'
}, re.IGNORECASE) }, re.IGNORECASE)
# from spf.py # from spf.py
def addr2bin(str): def addr2bin(s):
"""Convert a string IPv4 address into an unsigned integer.""" """Convert a string IPv4 address into an unsigned integer."""
if s.find(':') >= 0:
try: try:
return struct.unpack("!L", socket.inet_aton(str))[0] return bin2long6(inet_pton(s))
except:
raise socket.error("Invalid IP6 address: "+s)
try:
return struct.unpack("!L", socket.inet_aton(s))[0]
except socket.error: except socket.error:
raise socket.error("Invalid IP4 address: "+str) raise socket.error("Invalid IP4 address: "+s)
def bin2long6(str): def bin2long6(s):
"""Convert binary IP6 address into an unsigned Python long integer.""" """Convert binary IP6 address into an unsigned Python long integer."""
h, l = struct.unpack("!QQ", str) h, l = struct.unpack("!QQ", s)
return h << 64 | l return h << 64 | l
if hasattr(socket,'has_ipv6') and socket.has_ipv6: if hasattr(socket,'has_ipv6') and socket.has_ipv6:
@@ -180,7 +185,7 @@ def parse_header(val):
for s,enc in h: for s,enc in h:
if enc: if enc:
try: try:
u.append(unicode(s,enc)) u.append(unicode(s,enc,'replace'))
except LookupError: except LookupError:
u.append(unicode(s)) u.append(unicode(s))
else: else:
@@ -192,5 +197,6 @@ def parse_header(val):
except UnicodeError: continue except UnicodeError: continue
except UnicodeDecodeError: pass except UnicodeDecodeError: pass
except LookupError: pass except LookupError: pass
except ValueError: pass
except email.Errors.HeaderParseError: pass except email.Errors.HeaderParseError: pass
return val return val
+1 -2
View File
@@ -69,8 +69,7 @@ Not-so-quick Installation
First install Sendmail. Make sure you read libmilter/README in the Sendmail First install Sendmail. Make sure you read libmilter/README in the Sendmail
source directory, and make sure you enable libmilter before you build. The source directory, and make sure you enable libmilter before you build. The
8.11 series had libmilter marked as FFR (For Future Release); 8.12 8.11 series had libmilter marked as FFR (For Future Release); 8.12
officially officially supports libmilter, but it's still not built by default.
supports libmilter, but it's still not built by default.
Install Python, and enable threading in Modules/Setup. Install Python, and enable threading in Modules/Setup.
+18 -1
View File
@@ -1,6 +1,5 @@
## @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="https://www.milter.org/developers/api/index"> 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
@@ -34,3 +33,21 @@
# The <code>mime</code> module provides a wrapper for the Python email package # The <code>mime</code> module provides a wrapper for the Python email package
# that fixes some bugs, and simplifies modifying selected parts of a MIME # that fixes some bugs, and simplifies modifying selected parts of a MIME
# message. # message.
#
# @section threading
#
# The libmilter library which pymilter wraps
# <a href="https://www.milter.org/developers/overview#SignalHandling">handles
# 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
# <a href="https://www.milter.org/developers/api/smfi_stop">smfi_stop</a>
# and the last to an internal ABORT.
#
# If you use python threads or threading modules, then signal handling gets
# confused. Threads may still be useful, but you may need to provide an
# alternate means of causing graceful shutdown.
#
# You may find the
# <a href="http://docs.python.org/release/2.6.6/library/multiprocessing.html">
# multiprocessing</a> module useful. It can be a drop-in
# replacement for threading as illustrated in @ref milter-template.py.
+69 -2
View File
@@ -20,23 +20,52 @@
# 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>.
def getsymval(self,sym): pass def getsymval(self,sym): pass
## Calls <a href="https://www.milter.org/developers/api/smfi_setreply">
# smfi_setreply</a> or
# <a href="https://www.milter.org/developers/api/smfi_setmlreply">
# smfi_setmlreply</a>.
# @param rcode SMTP response code
# @param xcode extended SMTP response code
# @param msg one or more message lines. If the MTA does not support
# 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>.
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>.
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>.
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>.
def delrcpt(self,rcpt): pass def delrcpt(self,rcpt): pass
## Calls <a href="https://www.milter.org/developers/api/smfi_replacebody">smfi_replacebody</a>.
def replacebody(self,data): pass def replacebody(self,data): pass
## Attach a Python object to this connection context.
# @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.
def getpriv(self): pass def getpriv(self): pass
## Calls <a href="https://www.milter.org/developers/api/smfi_quarantine">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>.
def progress(self): pass def progress(self): pass
## Calls <a href="https://www.milter.org/developers/api/smfi_chgfrom">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.
# Of interest only when you need to squeeze a few more bytes of bandwidth.
def setsmlist(self,stage,macrolist): pass def setsmlist(self,stage,macrolist): pass
class error(Exception): pass class error(Exception): pass
## Enable optional milter actions.
# Certain milter actions need to be enabled before calling main()
# or they throw an exception. Pymilter enables them all by
# default (since 0.9.2), but you may wish to disable unneeded
# actions as an optimization.
# @param flags Bit or mask of optional actions to enable
def set_flags(flags): pass def set_flags(flags): pass
def set_connect_callback(cb): pass def set_connect_callback(cb): pass
def set_helo_callback(cb): pass def set_helo_callback(cb): pass
def set_envfrom_callback(cb): pass def set_envfrom_callback(cb): pass
@@ -46,19 +75,57 @@ def set_eoh_callback(cb): pass
def set_body_callback(cb): pass def set_body_callback(cb): pass
def set_abort_callback(cb): pass def set_abort_callback(cb): pass
def set_close_callback(cb): pass def set_close_callback(cb): pass
## Sets the return code for untrapped Python exceptions during a callback.
# Must be one of TEMPFAIL,REJECT,CONTINUE
def set_exception_policy(code): pass def set_exception_policy(code): pass
## Register python milter with libmilter.
# The name we pass is used to identify the milter in the MTA configuration.
# Callback functions must be set using the set_*_callback() functions before
# registering the milter.
# Three additional callbacks are specified as keyword parameters. These
# were added by recent versions of libmilter. The keyword parameters is
# a nicer way to do it, I think, since it makes clear that you have to do
# it before registering. I may move all the callbacks
# in the future (perhaps keeping the set functions for compatibility).
# @param name the milter name by which the MTA finds us
# @param negotiate the
# <a href="https://www.milter.org/developers/api/xxfi_negotiate">
# xxfi_negotiate</a> callback, called to negotiate supported
# actions, callbacks, and protocol steps.
# @param unknown the
# <a href="https://www.milter.org/developers/api/xxfi_unknown">
# xxfi_unknown</a> callback, called when for SMTP commands
# not recognized by the MTA. (Extend SMTP in your milter!)
# @param data the
# <a href="https://www.milter.org/developers/api/xxfi_data">
# xxfi_data</a> callback, called when the DATA
# SMTP command is received.
def register(name,negotiate=None,unknown=None,data=None): pass def register(name,negotiate=None,unknown=None,data=None): pass
def opensocket(rmsock): pass def opensocket(rmsock): pass
## Transfer control to libmilter.
# Calls <a href="https://www.milter.org/developers/api/smfi_main">
# smfi_main</a>.
def main(): pass def main(): pass
## Set the libmilter debugging level. ## Set the libmilter debugging level.
# smfi_setdbg sets the milter library's internal debugging level to a new level # <a href="https://www.milter.org/developers/api/smfi_setdbg">smfi_setdbg</a>
# 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. # current, highest, useful value. Must be called before calling main().
def setdbg(lev): pass def setdbg(lev): pass
## Set timeout for MTA communication.
# Calls <a href="https://www.milter.org/developers/api/smfi_settimeout">
# smfi_settimeout</a>. Must be called before calling main().
def settimeout(secs): pass def settimeout(secs): pass
## Set socket backlog.
# Calls <a href="https://www.milter.org/developers/api/smfi_setbacklog">
# smfi_setbacklog</a>. Must be called before calling main().
def setbacklog(n): pass def setbacklog(n): pass
## Set the socket used to communicate with the MTA. ## Set the socket used to communicate with the MTA.
+2 -2
View File
@@ -2,8 +2,8 @@ web:
doxygen doxygen
rsync -ravK doc/html/ spidey2.bmsi.com:/Public/pymilter rsync -ravK doc/html/ spidey2.bmsi.com:/Public/pymilter
VERSION=0.9.4 VERSION=0.9.5
CVSTAG=pymilter-0_9_4 CVSTAG=pymilter-0_9_5
PKG=pymilter-$(VERSION) PKG=pymilter-$(VERSION)
SRCTAR=$(PKG).tar.gz SRCTAR=$(PKG).tar.gz
+27 -8
View File
@@ -11,9 +11,16 @@ import Milter
import StringIO import StringIO
import time import time
import email import email
import sys
from socket import AF_INET, AF_INET6 from socket import AF_INET, AF_INET6
from Milter import parse_addr from Milter.utils import parse_addr
if True:
from multiprocessing import Process as Thread, Queue
else:
from threading import Thread
from Queue import Queue
logq = Queue(maxsize=4)
class myMilter(Milter.Base): class myMilter(Milter.Base):
@@ -23,7 +30,7 @@ class myMilter(Milter.Base):
# each connection runs in its own thread and has its own myMilter # each connection runs in its own thread and has its own myMilter
# instance. Python code must be thread safe. This is trivial if only stuff # instance. Python code must be thread safe. This is trivial if only stuff
# in myMilter instances is referenced. # in myMilter instances is referenced.
@noreply @Milter.noreply
def connect(self, IPname, family, hostaddr): def connect(self, IPname, family, hostaddr):
# (self, 'ip068.subnet71.example.com', AF_INET, ('215.183.71.68', 4720) ) # (self, 'ip068.subnet71.example.com', AF_INET, ('215.183.71.68', 4720) )
# (self, 'ip6.mxout.example.com', AF_INET6, # (self, 'ip6.mxout.example.com', AF_INET6,
@@ -71,7 +78,7 @@ class myMilter(Milter.Base):
## def envrcpt(self, to, *str): ## def envrcpt(self, to, *str):
@noreply @Milter.noreply
def envrcpt(self, recipient, *str): def envrcpt(self, recipient, *str):
rcptinfo = to,Milter.dictfromlist(str) rcptinfo = to,Milter.dictfromlist(str)
self.R.append(rcptinfo) self.R.append(rcptinfo)
@@ -79,17 +86,17 @@ class myMilter(Milter.Base):
return Milter.CONTINUE return Milter.CONTINUE
@noreply @Milter.noreply
def header(self, name, hval): def header(self, name, hval):
self.fp.write("%s: %s\n" % (name,hval)) # add header to buffer self.fp.write("%s: %s\n" % (name,hval)) # add header to buffer
return Milter.CONTINUE return Milter.CONTINUE
@noreply @Milter.noreply
def eoh(self): def eoh(self):
self.fp.write("\n") # terminate headers self.fp.write("\n") # terminate headers
return Milter.CONTINUE return Milter.CONTINUE
@noreply @Milter.noreply
def body(self, chunk): def body(self, chunk):
self.fp.write(chunk) self.fp.write(chunk)
return Milter.CONTINUE return Milter.CONTINUE
@@ -116,15 +123,25 @@ class myMilter(Milter.Base):
## === Support Functions === ## === Support Functions ===
def log(self,*msg): def log(self,*msg):
print "%s [%d]" % (time.strftime('%Y%b%d %H:%M:%S'),self.id), logq.put((msg,self.id,time.time()))
def background():
while True:
t = logq.get()
if not t: break
msg,id,ts = t
print "%s [%d]" % (time.strftime('%Y%b%d %H:%M:%S',time.localtime(ts)),id),
# 2005Oct13 02:34:11 [1] msg1 msg2 msg3 ... # 2005Oct13 02:34:11 [1] msg1 msg2 msg3 ...
for i in msg: print i, for i in msg: print i,
print print
## === ## ===
def main(): def main():
bt = Thread(target=background)
bt.start()
socketname = "/home/stuart/pythonsock"
timeout = 600
# Register to have the Milter factory create instances of your class: # Register to have the Milter factory create instances of your class:
Milter.factory = myMilter Milter.factory = myMilter
flags = Milter.CHGBODY + Milter.CHGHDRS + Milter.ADDHDRS flags = Milter.CHGBODY + Milter.CHGHDRS + Milter.ADDHDRS
@@ -134,6 +151,8 @@ def main():
print "%s milter startup" % time.strftime('%Y%b%d %H:%M:%S') print "%s milter startup" % time.strftime('%Y%b%d %H:%M:%S')
sys.stdout.flush() sys.stdout.flush()
Milter.runmilter("pythonfilter",socketname,timeout) Milter.runmilter("pythonfilter",socketname,timeout)
logq.put(None)
bt.join()
print "%s bms milter shutdown" % time.strftime('%Y%b%d %H:%M:%S') print "%s bms milter shutdown" % time.strftime('%Y%b%d %H:%M:%S')
if __name__ == "__main__": if __name__ == "__main__":
+48 -7
View File
@@ -35,6 +35,12 @@ $ python setup.py help
libraries=["milter","smutil","resolv"] libraries=["milter","smutil","resolv"]
* $Log$ * $Log$
* Revision 1.28 2011/06/08 23:13:48 customdesigned
* Generate special exception when callback return not int.
*
* Revision 1.27 2009/07/28 21:45:54 customdesigned
* Add getversion() to return runtime version.
*
* Revision 1.26 2009/07/28 21:08:20 customdesigned * Revision 1.26 2009/07/28 21:08:20 customdesigned
* Increment del count. * Increment del count.
* *
@@ -268,7 +274,6 @@ $ python setup.py help
#endif #endif
#endif #endif
/* Yes, these are static. If you need multiple different callbacks, */ /* Yes, these are static. If you need multiple different callbacks, */
/* it's cleaner to use multiple filters, or convert to OO method calls. */ /* it's cleaner to use multiple filters, or convert to OO method calls. */
static PyObject *connect_callback = NULL; static PyObject *connect_callback = NULL;
@@ -281,6 +286,32 @@ static PyObject *body_callback = NULL;
static PyObject *eom_callback = NULL; static PyObject *eom_callback = NULL;
static PyObject *abort_callback = NULL; static PyObject *abort_callback = NULL;
static PyObject *close_callback = NULL; static PyObject *close_callback = NULL;
#ifdef SMFIS_ALL_OPTS
static PyObject *unknown_callback = NULL;
static PyObject *data_callback = NULL;
static PyObject *negotiate_callback = NULL;
#endif
static struct MilterCallback {
PyObject **cbp;
const char *name;
} callback_names[] = {
{ &connect_callback,"connect" },
{ &helo_callback,"helo" },
{ &envfrom_callback,"envfrom" },
{ &envrcpt_callback,"envrcpt" },
{ &header_callback,"header" },
{ &eoh_callback,"eoh" },
{ &body_callback,"body" },
{ &eom_callback,"eom" },
{ &abort_callback,"abort" },
{ &close_callback,"close" },
#ifdef SMFIS_ALL_OPTS
{ &unknown_callback,"unknown" },
{ &data_callback,"data" },
{ &negotiate_callback,"negotiate" },
#endif
{ NULL, NULL }
};
staticforward struct smfiDesc description; /* forward declaration */ staticforward struct smfiDesc description; /* forward declaration */
@@ -631,9 +662,23 @@ _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);
retval = PyInt_AsLong(result); if (!PyInt_Check(result)) {
const struct MilterCallback *p;
const char *cbname = "milter";
char buf[40];
Py_DECREF(result);
for (p = callback_names; p->cbp; ++p) {
if (cb == *p->cbp) {
cbname = p->name;
break;
}
}
sprintf(buf,"The %s callback must return int",cbname);
PyErr_SetString(MilterError,buf);
return _report_exception(self);
}
retval = PyInt_AS_LONG(result);
Py_DECREF(result); Py_DECREF(result);
if (PyErr_Occurred()) return _report_exception(self);
_release_thread(self->t); _release_thread(self->t);
return retval; return retval;
} }
@@ -822,10 +867,6 @@ milter_wrap_abort(SMFICTX *ctx) {
} }
#ifdef SMFIS_ALL_OPTS #ifdef SMFIS_ALL_OPTS
static PyObject *unknown_callback = NULL;
static PyObject *data_callback = NULL;
static PyObject *negotiate_callback = NULL;
static int static int
milter_wrap_unknown(SMFICTX *ctx, const char *cmd) { milter_wrap_unknown(SMFICTX *ctx, const char *cmd) {
PyObject *arglist; PyObject *arglist;
+11 -4
View File
@@ -1,20 +1,21 @@
%define __python python2.6 %define __python python2.6
%define pythonbase python26
%define libdir %{_libdir}/pymilter %define libdir %{_libdir}/pymilter
%{!?python_sitearch: %define python_sitearch %(%{__python} -c "from distutils.sysconfig import get_python_lib; print get_python_lib(1)")} %{!?python_sitearch: %define python_sitearch %(%{__python} -c "from distutils.sysconfig import get_python_lib; print get_python_lib(1)")}
%define pythonbase python26
Summary: Python interface to sendmail milter API Summary: Python interface to sendmail milter API
Name: %{pythonbase}-pymilter Name: %{pythonbase}-pymilter
Version: 0.9.4 Version: 0.9.5
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+
Group: Development/Libraries Group: Development/Libraries
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root
Url: http://www.bmsi.com/python/milter.html Url: http://www.bmsi.com/python/milter.html
Requires: %{pythonbase}, sendmail >= 8.13 # python-2.6.4 gets RuntimeError: not holding the import lock
# Need python2.4 specific pydns, not the version for system python Requires: %{pythonbase} >= 2.6.5, sendmail >= 8.13
# Need python2.6 specific pydns, not the version for system python
Requires: %{pythonbase}-pydns Requires: %{pythonbase}-pydns
# Needed for callbacks, not a core function but highly useful for milters # Needed for callbacks, not a core function but highly useful for milters
BuildRequires: ed, %{pythonbase}-devel, sendmail-devel >= 8.13 BuildRequires: ed, %{pythonbase}-devel, sendmail-devel >= 8.13
@@ -74,6 +75,12 @@ chmod a+x $RPM_BUILD_ROOT%{libdir}/start.sh
rm -rf $RPM_BUILD_ROOT rm -rf $RPM_BUILD_ROOT
%changelog %changelog
* Wed Mar 02 2010 Stuart Gathman <stuart@bmsi.com> 0.9.5-1
- Print milter.error for invalid callback return type.
(Since stacktrace is empty, the TypeError exception is confusing.)
- Fix milter-template.py
- Tweak Milter.utils.addr2bin and Milter.dynip to handle IP6
* Wed Mar 02 2010 Stuart Gathman <stuart@bmsi.com> 0.9.4-1 * Wed Mar 02 2010 Stuart Gathman <stuart@bmsi.com> 0.9.4-1
- Handle IP6 in Milter.utils.iniplist() - Handle IP6 in Milter.utils.iniplist()
- python-2.6 - python-2.6
+4 -8
View File
@@ -2,6 +2,9 @@ import os
import sys import sys
from distutils.core import setup, Extension from distutils.core import setup, Extension
if sys.version < '2.6.5':
sys.exit('ERROR: Sorry, python 2.6.5 is required for this module.')
# FIXME: on some versions of sendmail, smutil is renamed to sm. # FIXME: on some versions of sendmail, smutil is renamed to sm.
# On slackware and debian, leave it out entirely. It depends # On slackware and debian, leave it out entirely. It depends
# on how libmilter was built by the sendmail package. # on how libmilter was built by the sendmail package.
@@ -9,15 +12,8 @@ from distutils.core import setup, Extension
libs = ["milter"] libs = ["milter"]
libdirs = ["/usr/lib/libmilter"] # needed for Debian libdirs = ["/usr/lib/libmilter"] # needed for Debian
# patch distutils if it can't cope with the "classifiers" or
# "download_url" keywords
if sys.version < '2.2.3':
from distutils.dist import DistributionMetadata
DistributionMetadata.classifiers = None
DistributionMetadata.download_url = None
# NOTE: importing Milter to obtain version fails when milter.so not built # NOTE: importing Milter to obtain version fails when milter.so not built
setup(name = "pymilter", version = '0.9.4', setup(name = "pymilter", version = '0.9.5',
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
+6 -3
View File
@@ -1,13 +1,16 @@
#!/bin/sh #!/bin/sh
appname="$1" appname="$1"
script="${2:-${appname}}" script="${2:-${appname}}"
datadir="/var/log/milter" datadir="/var/lib/milter"
logdir="/var/log/milter"
piddir="/var/run/milter" piddir="/var/run/milter"
libdir="/usr/lib/pymilter" libdir="/usr/lib/pymilter"
python="python2.4" python="python2.4"
exec >>${datadir}/${appname}.log 2>&1 exec >>${logdir}/${appname}.log 2>&1
if test -s ${datadir}/${script}.py; then if test -s ${datadir}/${script}.py; then
cd ${datadir} # use version in log dir if it exists for debugging cd ${datadir} # use version in data dir if it exists for debugging
elif test -s ${logdir}/${script}.py; then
cd ${logdir} # use version in log dir if it exists for debugging
else else
cd ${libdir} cd ${libdir}
fi fi
+29
View File
@@ -1,4 +1,7 @@
# $Log$ # $Log$
# Revision 1.4 2005/07/20 14:49:44 customdesigned
# Handle corrupt and empty ZIP files.
#
# Revision 1.3 2005/06/17 01:49:39 customdesigned # Revision 1.3 2005/06/17 01:49:39 customdesigned
# Handle zip within zip. # Handle zip within zip.
# #
@@ -26,6 +29,7 @@ import socket
import StringIO import StringIO
import email import email
import sys import sys
import Milter
from email import Errors from email import Errors
samp1_txt1 = """Dear Agent 1 samp1_txt1 = """Dear Agent 1
@@ -146,6 +150,31 @@ class MimeTestCase(unittest.TestCase):
# test zip within zip # test zip within zip
self.testDefang('ziploop',1,'stuart@bmsi.com.zip') self.testDefang('ziploop',1,'stuart@bmsi.com.zip')
def _chk_name(self,name):
self.filename = name
def _chk_attach(self,msg):
"Filter attachments by content."
# check for bad extensions
mime.check_name(msg,ckname=self._chk_name,scan_zip=True)
# remove scripts from HTML
mime.check_html(msg)
# don't let a tricky virus slip one past us
msg = msg.get_submsg()
if isinstance(msg,email.Message.Message):
return mime.check_attachments(msg,self._chk_attach)
return Milter.CONTINUE
def testCheckAttach(self,fname="test1"):
# test1 contains a very long filename
msg = mime.message_from_file(open('test/'+fname,'r'))
mime.defang(msg,scan_zip=True)
self.failIf(msg.ismodified())
msg = mime.message_from_file(open('test/tmpytgcE5.fail','r'))
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(rc,Milter.CONTINUE)
def testHTML(self,fname=""): def testHTML(self,fname=""):
result = StringIO.StringIO() result = StringIO.StringIO()
filter = mime.HTMLScriptFilter(result) filter = mime.HTMLScriptFilter(result)