Doxygen docs.
This commit is contained in:
+104
-16
@@ -1,9 +1,13 @@
|
|||||||
# Author: Stuart D. Gathman <stuart@bmsi.com>
|
## @package Milter
|
||||||
# Copyright 2001 Business Management Systems, Inc.
|
# A thin OO wrapper for the milter module.
|
||||||
|
#
|
||||||
|
# Clients generally subclass Milter.Base and define callback
|
||||||
|
# methods.
|
||||||
|
#
|
||||||
|
# author Stuart D. Gathman <stuart@bmsi.com>
|
||||||
|
# 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.
|
||||||
|
|
||||||
# A thin OO wrapper for the milter module
|
|
||||||
|
|
||||||
__version__ = '0.9.2'
|
__version__ = '0.9.2'
|
||||||
|
|
||||||
import os
|
import os
|
||||||
@@ -36,6 +40,14 @@ OPTIONAL_CALLBACKS = {
|
|||||||
'header':(P_NR_HDR,P_NOHDRS)
|
'header':(P_NR_HDR,P_NOHDRS)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
## Function decorator to disable callback methods.
|
||||||
|
# If the MTA supports it, tells the MTA not to call this callback,
|
||||||
|
# increasing efficiency. All the callbacks (except negotiate)
|
||||||
|
# are disabled in Milter.Base, and overriding them reenables the
|
||||||
|
# callback. An application may need to use @@callback when it extends
|
||||||
|
# another milter and wants to disable a callback again.
|
||||||
|
# The disabled method should still return Milter.CONTINUE, in case the MTA does
|
||||||
|
# not support protocol negotiation.
|
||||||
def nocallback(func):
|
def nocallback(func):
|
||||||
try:
|
try:
|
||||||
func.milter_protocol = OPTIONAL_CALLBACKS[func.__name__][1]
|
func.milter_protocol = OPTIONAL_CALLBACKS[func.__name__][1]
|
||||||
@@ -44,6 +56,12 @@ def nocallback(func):
|
|||||||
'@nocallback applied to non-optional method: '+func.__name__)
|
'@nocallback applied to non-optional method: '+func.__name__)
|
||||||
return func
|
return func
|
||||||
|
|
||||||
|
## Function decorator to disable callback reply.
|
||||||
|
# If the MTA supports it, tells the MTA not to wait for a reply from
|
||||||
|
# this callback, and assume CONTINUE. The method should still return
|
||||||
|
# CONTINUE in case the MTA does not support protocol negotiation.
|
||||||
|
# The decorator arranges to change the return code to NOREPLY
|
||||||
|
# when supported by the MTA.
|
||||||
def noreply(func):
|
def noreply(func):
|
||||||
try:
|
try:
|
||||||
nr_mask = OPTIONAL_CALLBACKS[func.__name__][0]
|
nr_mask = OPTIONAL_CALLBACKS[func.__name__][0]
|
||||||
@@ -57,12 +75,20 @@ def noreply(func):
|
|||||||
wrapper.milter_protocol = nr_mask
|
wrapper.milter_protocol = nr_mask
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
|
## Disabled action exception.
|
||||||
|
# set_flags() can tell the MTA that this application will not use certain
|
||||||
|
# features (such as CHGFROM). This can also be negotiated for each
|
||||||
|
# connection in the negotiate callback. If the application then calls
|
||||||
|
# the feature anyway via an instance method, this exception is
|
||||||
|
# thrown.
|
||||||
class DisabledAction(RuntimeError):
|
class DisabledAction(RuntimeError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# A do nothing Milter base class from which python milters should derive
|
## A do nothing Milter base class.
|
||||||
# unless they are using the milter C module directly.
|
# Python milters should derive from this class
|
||||||
|
# unless they are using the low lever milter module directly.
|
||||||
|
# All optional callbacks are disabled, and automatically
|
||||||
|
# reenabled when overridden.
|
||||||
class Base(object):
|
class Base(object):
|
||||||
"The core class interface to the milter module."
|
"The core class interface to the milter module."
|
||||||
|
|
||||||
@@ -72,30 +98,54 @@ class Base(object):
|
|||||||
self._protocol = 0 # no protocol options by default
|
self._protocol = 0 # no protocol options by default
|
||||||
if ctx:
|
if ctx:
|
||||||
ctx.setpriv(self)
|
ctx.setpriv(self)
|
||||||
|
## Defined by subclasses to write log messages.
|
||||||
def log(self,*msg): pass
|
def log(self,*msg): pass
|
||||||
|
## Called for each connection to the MTA.
|
||||||
@nocallback
|
@nocallback
|
||||||
def connect(self,hostname,family,hostaddr): return CONTINUE
|
def connect(self,hostname,family,hostaddr): return CONTINUE
|
||||||
|
## Called when the SMTP client says HELO.
|
||||||
|
# Returning REJECT prevents progress until a valid HELO is provided;
|
||||||
|
# this almost always results in terminating the connection.
|
||||||
@nocallback
|
@nocallback
|
||||||
def hello(self,hostname): return CONTINUE
|
def hello(self,hostname): return CONTINUE
|
||||||
|
## Called when the SMTP client says MAIL FROM.
|
||||||
|
# Returning REJECT rejects the message, but not the connection.
|
||||||
@nocallback
|
@nocallback
|
||||||
def envfrom(self,f,*str): return CONTINUE
|
def envfrom(self,f,*str): return CONTINUE
|
||||||
|
## Called when the SMTP client says RCPT TO.
|
||||||
|
# Returning REJECT rejects the current recipient, not the entire message.
|
||||||
@nocallback
|
@nocallback
|
||||||
def envrcpt(self,to,*str): return CONTINUE
|
def envrcpt(self,to,*str): return CONTINUE
|
||||||
|
## Called when the SMTP client says DATA.
|
||||||
|
# Returning REJECT rejects the message without wasting bandwidth
|
||||||
|
# on the unwanted message.
|
||||||
@nocallback
|
@nocallback
|
||||||
def data(self): return CONTINUE
|
def data(self): return CONTINUE
|
||||||
|
## Called for each header field in the message body.
|
||||||
@nocallback
|
@nocallback
|
||||||
def header(self,field,value): return CONTINUE
|
def header(self,field,value): return CONTINUE
|
||||||
|
## Called at the blank line that terminates the header fields.
|
||||||
@nocallback
|
@nocallback
|
||||||
def eoh(self): return CONTINUE
|
def eoh(self): return CONTINUE
|
||||||
|
## Called to copy the body of the message by chunks.
|
||||||
|
# @param blk a block of message bytes
|
||||||
@nocallback
|
@nocallback
|
||||||
def body(self,unused): return CONTINUE
|
def body(self,blk): return CONTINUE
|
||||||
|
## Called when the SMTP client issues an unknown command.
|
||||||
|
# @param cmd the unknown command
|
||||||
@nocallback
|
@nocallback
|
||||||
def unknown(self,cmd): return CONTINUE
|
def unknown(self,cmd): return CONTINUE
|
||||||
|
## Called at the end of the message body.
|
||||||
|
# Most of the message manipulation actions can only take place from
|
||||||
|
# the eom callback.
|
||||||
def eom(self): return CONTINUE
|
def eom(self): return CONTINUE
|
||||||
|
## Called when the connection is abnormally terminated.
|
||||||
|
# The close callback is still called also.
|
||||||
def abort(self): return CONTINUE
|
def abort(self): return CONTINUE
|
||||||
|
## Called when the connection is closed.
|
||||||
def close(self): return CONTINUE
|
def close(self): return CONTINUE
|
||||||
|
|
||||||
# Return mask of SMFIP_N.. protocol option bits to clear for this class
|
## Return mask of SMFIP_N.. protocol option bits to clear for this class
|
||||||
@classmethod
|
@classmethod
|
||||||
def protocol_mask(klass):
|
def protocol_mask(klass):
|
||||||
try:
|
try:
|
||||||
@@ -110,8 +160,10 @@ class Base(object):
|
|||||||
klass._protocol_mask = p
|
klass._protocol_mask = p
|
||||||
return p
|
return p
|
||||||
|
|
||||||
|
## Negotiate milter protocol options.
|
||||||
# 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
|
# marked @@nocallback and @@noreply respectively, leaves all
|
||||||
|
# actions enabled, and enables Milter.SKIP.
|
||||||
def negotiate(self,opts):
|
def negotiate(self,opts):
|
||||||
try:
|
try:
|
||||||
self._actions,p,f1,f2 = opts
|
self._actions,p,f1,f2 = opts
|
||||||
@@ -126,15 +178,22 @@ class Base(object):
|
|||||||
return CONTINUE
|
return CONTINUE
|
||||||
|
|
||||||
# Milter methods which can be invoked from most callbacks
|
# Milter methods which can be invoked from most callbacks
|
||||||
|
|
||||||
|
## Return the value of an MTA macro. Sendmail macro names
|
||||||
|
# are either single chars (e.g. "j") or multiple chars enclosed
|
||||||
|
# in braces (e.g. "{auth_type}"). Macro names are MTA dependent.
|
||||||
|
# @param sym the macro name
|
||||||
def getsymval(self,sym):
|
def getsymval(self,sym):
|
||||||
return self._ctx.getsymval(sym)
|
return self._ctx.getsymval(sym)
|
||||||
|
|
||||||
# If sendmail does not support setmlreply, then only the
|
## Set the SMTP reply code and message.
|
||||||
|
# If the MTA does not support setmlreply, then only the
|
||||||
# first msg line is used.
|
# first msg line is used.
|
||||||
def setreply(self,rcode,xcode=None,msg=None,*ml):
|
def setreply(self,rcode,xcode=None,msg=None,*ml):
|
||||||
return self._ctx.setreply(rcode,xcode,msg,*ml)
|
return self._ctx.setreply(rcode,xcode,msg,*ml)
|
||||||
|
|
||||||
# may only be called from negotiate callback
|
## Tell the MTA which macro names will be used.
|
||||||
|
# May only be called from negotiate callback.
|
||||||
def setsmlist(self,stage,macros):
|
def setsmlist(self,stage,macros):
|
||||||
if not self._actions & SETSMLIST: raise DisabledAction("SETSMLIST")
|
if not self._actions & SETSMLIST: raise DisabledAction("SETSMLIST")
|
||||||
if type(macros) in (list,tuple):
|
if type(macros) in (list,tuple):
|
||||||
@@ -142,22 +201,43 @@ class Base(object):
|
|||||||
return self._ctx.setsmlist(stage,macros)
|
return self._ctx.setsmlist(stage,macros)
|
||||||
|
|
||||||
# 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.
|
||||||
|
# @param field the header field name
|
||||||
|
# @param value the header field value
|
||||||
|
# @param idx header field index from the top of the message to insert at
|
||||||
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.
|
||||||
|
# @param field the name of the field to change
|
||||||
|
# @param idx index of the field to change when there are multiple instances
|
||||||
|
# @param value the new value of the field
|
||||||
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.
|
||||||
|
# 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
|
||||||
|
# RCPT TO command (and as delivered to the envrcpt callback), for example
|
||||||
|
# "self.addrcpt('<foo@example.com>')".
|
||||||
|
# @param rcpt the message recipient
|
||||||
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")
|
||||||
return self._ctx.addrcpt(rcpt,params)
|
return self._ctx.addrcpt(rcpt,params)
|
||||||
|
## Delete a recipient from the message.
|
||||||
|
# The recipient should match one passed to the envrcpt callback.
|
||||||
|
# @param rcpt the message recipient to delete
|
||||||
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.
|
||||||
|
# The entire message body must be replaced.
|
||||||
|
# Call repeatedly with blocks of data until the entire body is transferred.
|
||||||
|
# @param body a chunk of body data
|
||||||
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)
|
||||||
@@ -166,18 +246,23 @@ class Base(object):
|
|||||||
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.
|
||||||
# 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.
|
||||||
|
# Called from eom callback only.
|
||||||
|
# @param reason a string describing the reason for quarantine
|
||||||
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.
|
||||||
|
# Resets timeouts in the MTA that detect a "hung" milter.
|
||||||
def progress(self):
|
def progress(self):
|
||||||
return self._ctx.progress()
|
return self._ctx.progress()
|
||||||
|
|
||||||
# A logging but otherwise do nothing Milter base class included
|
## A logging but otherwise do nothing Milter base class.
|
||||||
# for compatibility with previous versions of pymilter.
|
# This is included for compatibility with previous versions of pymilter.
|
||||||
|
# The logging callbacks are marked @@noreply.
|
||||||
class Milter(Base):
|
class Milter(Base):
|
||||||
"A simple class interface to the milter module."
|
"A simple class interface to the milter module."
|
||||||
|
|
||||||
@@ -348,3 +433,6 @@ __all__ = globals().copy()
|
|||||||
for priv in ('os','milter','thread','factory','_seq','_seq_lock','__version__'):
|
for priv in ('os','milter','thread','factory','_seq','_seq_lock','__version__'):
|
||||||
del __all__[priv]
|
del __all__[priv]
|
||||||
__all__ = __all__.keys()
|
__all__ = __all__.keys()
|
||||||
|
|
||||||
|
## @example milter-template.py
|
||||||
|
#
|
||||||
|
|||||||
+6
-4
@@ -15,7 +15,7 @@ from socket import AF_INET, AF_INET6
|
|||||||
from Milter import parse_addr
|
from Milter import parse_addr
|
||||||
|
|
||||||
|
|
||||||
class myMilter(Milter.Milter):
|
class myMilter(Milter.Base):
|
||||||
|
|
||||||
def __init__(self): # A new instance with each new connection.
|
def __init__(self): # A new instance with each new connection.
|
||||||
self.id = Milter.uniqueID() # Integer incremented with each call.
|
self.id = Milter.uniqueID() # Integer incremented with each call.
|
||||||
@@ -23,6 +23,7 @@ class myMilter(Milter.Milter):
|
|||||||
# 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
|
||||||
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,
|
||||||
@@ -70,6 +71,7 @@ class myMilter(Milter.Milter):
|
|||||||
|
|
||||||
|
|
||||||
## def envrcpt(self, to, *str):
|
## def envrcpt(self, to, *str):
|
||||||
|
@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)
|
||||||
@@ -77,21 +79,21 @@ class myMilter(Milter.Milter):
|
|||||||
return Milter.CONTINUE
|
return Milter.CONTINUE
|
||||||
|
|
||||||
|
|
||||||
|
@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
|
||||||
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
|
||||||
def body(self, chunk):
|
def body(self, chunk):
|
||||||
self.fp.write(chunk)
|
self.fp.write(chunk)
|
||||||
return Milter.CONTINUE
|
return Milter.CONTINUE
|
||||||
|
|
||||||
|
|
||||||
def eom(self):
|
def eom(self):
|
||||||
self.fp.seek(0)
|
self.fp.seek(0)
|
||||||
msg = email.message_from_file(self.fp)
|
msg = email.message_from_file(self.fp)
|
||||||
|
|||||||
Reference in New Issue
Block a user