Skip to content
Snippets Groups Projects
Commit f6a3b57f authored by Stuart Gathman's avatar Stuart Gathman
Browse files

enable_protocols class decorator, doc updates

parent 3428477e
No related branches found
No related tags found
No related merge requests found
...@@ -40,6 +40,41 @@ OPTIONAL_CALLBACKS = { ...@@ -40,6 +40,41 @@ OPTIONAL_CALLBACKS = {
'header':(P_NR_HDR,P_NOHDRS) 'header':(P_NR_HDR,P_NOHDRS)
} }
def decode_mask(bits,names):
t = [ (s,getattr(milter,s)) for s in names]
nms = [s for s,m in t if bits & m]
for s,m in t: bits &= ~m
if bits: nms += hex(bits)
return nms
## Class decorator to enable optional protocol steps.
# P_SKIP is enabled by default when supported, but
# milter applications may wish to enable P_HDR_LEADSPC
# to send and receive the leading space of header continuation
# lines unchanged, and/or P_RCPT_REJ to have recipients
# detected as invalid by the MTA passed to the envcrpt callback.
#
# Applications may want to check whether the protocol is actually
# supported by the MTA in use. The <code>_protocol</code>
# member is a bitmask of protocol options negotiated. So,
# for instance, if <code>self._protocol & Milter.P_RCPT_REJ</code>
# is true, then that feature was successfully negotiated with the MTA.
#
# Sample use:
# <pre>
# class myMilter(Milter.Base):
# def envrcpt(self,to,*params):
# return Milter.CONTINUE
# myMilter = Milter.enable_protocols(myMilter,Milter.P_RCPT_REJ)
# </pre>
# @since 0.9.3
# @param klass the milter application class to modify
# @param mask a bitmask of protocol steps to enable
# @return the modified milter class
def enable_protocols(klass,mask):
klass._protocol_mask = klass.protocol_mask() & ~mask
return klass
## 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 call this callback,
# increasing efficiency. All the callbacks (except negotiate) # increasing efficiency. All the callbacks (except negotiate)
...@@ -48,6 +83,7 @@ OPTIONAL_CALLBACKS = { ...@@ -48,6 +83,7 @@ OPTIONAL_CALLBACKS = {
# 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.
# @since 0.9.2
def nocallback(func): def nocallback(func):
try: try:
func.milter_protocol = OPTIONAL_CALLBACKS[func.__name__][1] func.milter_protocol = OPTIONAL_CALLBACKS[func.__name__][1]
...@@ -62,6 +98,7 @@ def nocallback(func): ...@@ -62,6 +98,7 @@ def nocallback(func):
# CONTINUE in case the MTA does not support protocol negotiation. # CONTINUE in case the MTA does not support protocol negotiation.
# The decorator arranges to change the return code to NOREPLY # The decorator arranges to change the return code to NOREPLY
# when supported by the MTA. # when supported by the MTA.
# @since 0.9.2
def noreply(func): def noreply(func):
try: try:
nr_mask = OPTIONAL_CALLBACKS[func.__name__][0] nr_mask = OPTIONAL_CALLBACKS[func.__name__][0]
...@@ -81,6 +118,7 @@ def noreply(func): ...@@ -81,6 +118,7 @@ def noreply(func):
# connection in the negotiate callback. If the application then calls # connection in the negotiate callback. If the application then calls
# the feature anyway via an instance method, this exception is # the feature anyway via an instance method, this exception is
# thrown. # thrown.
# @since 0.9.2
class DisabledAction(RuntimeError): class DisabledAction(RuntimeError):
pass pass
...@@ -89,7 +127,7 @@ class DisabledAction(RuntimeError): ...@@ -89,7 +127,7 @@ class DisabledAction(RuntimeError):
# unless they are using the low lever milter module directly. # unless they are using the low lever milter module directly.
# All optional callbacks are disabled, and automatically # All optional callbacks are disabled, and automatically
# reenabled when overridden. # reenabled when overridden.
# # @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."
...@@ -109,6 +147,7 @@ class Base(object): ...@@ -109,6 +147,7 @@ class Base(object):
# 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.
# @since 0.9.2
# #
## @var _protocol ## @var _protocol
...@@ -121,6 +160,7 @@ class Base(object): ...@@ -121,6 +160,7 @@ class Base(object):
# 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
## Defined by subclasses to write log messages. ## Defined by subclasses to write log messages.
def log(self,*msg): pass def log(self,*msg): pass
...@@ -159,6 +199,7 @@ class Base(object): ...@@ -159,6 +199,7 @@ class Base(object):
## Called when the SMTP client says DATA. ## Called when the SMTP client says DATA.
# Returning REJECT rejects the message without wasting bandwidth # Returning REJECT rejects the message without wasting bandwidth
# on the unwanted message. # on the unwanted message.
# @since 0.9.2
@nocallback @nocallback
def data(self): return CONTINUE def data(self): return CONTINUE
## Called for each header field in the message body. ## Called for each header field in the message body.
...@@ -173,6 +214,7 @@ class Base(object): ...@@ -173,6 +214,7 @@ class Base(object):
def body(self,blk): return CONTINUE def body(self,blk): return CONTINUE
## Called when the SMTP client issues an unknown command. ## Called when the SMTP client issues an unknown command.
# @param cmd the unknown command # @param cmd the unknown command
# @since 0.9.2
@nocallback @nocallback
def unknown(self,cmd): return CONTINUE def unknown(self,cmd): return CONTINUE
## Called at the end of the message body. ## Called at the end of the message body.
...@@ -194,12 +236,13 @@ class Base(object): ...@@ -194,12 +236,13 @@ class Base(object):
# Libmilter passes the protocol bits that the current MTA knows # Libmilter passes the protocol bits that the current MTA knows
# how to skip. We clear the ones we don't want to skip. # how to skip. We clear the ones we don't want to skip.
# The negation is somewhat mind bending, but it is simple. # The negation is somewhat mind bending, but it is simple.
# @since 0.9.2
@classmethod @classmethod
def protocol_mask(klass): def protocol_mask(klass):
try: try:
return klass._protocol_mask return klass._protocol_mask
except AttributeError: except AttributeError:
p = 0 p = P_RCPT_REJ | P_HDR_LEADSPC # turn these new features off by default
for func,(nr,nc) in OPTIONAL_CALLBACKS.items(): for func,(nr,nc) in OPTIONAL_CALLBACKS.items():
func = getattr(klass,func) func = getattr(klass,func)
ca = getattr(func,'milter_protocol',0) ca = getattr(func,'milter_protocol',0)
...@@ -212,11 +255,11 @@ class Base(object): ...@@ -212,11 +255,11 @@ class Base(object):
# 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.
# @since 0.9.2
def negotiate(self,opts): def negotiate(self,opts):
try: try:
self._actions,p,f1,f2 = opts self._actions,p,f1,f2 = opts
opts[1] = self._protocol = \ opts[1] = self._protocol = p & ~self.protocol_mask()
p & ~self.protocol_mask() & ~P_RCPT_REJ & ~P_HDR_LEADSPC
opts[2] = 0 opts[2] = 0
opts[3] = 0 opts[3] = 0
#self.log("Negotiated:",opts) #self.log("Negotiated:",opts)
...@@ -241,9 +284,12 @@ class Base(object): ...@@ -241,9 +284,12 @@ class Base(object):
return self._ctx.setreply(rcode,xcode,msg,*ml) return self._ctx.setreply(rcode,xcode,msg,*ml)
## Tell the MTA which macro names will be used. ## Tell the MTA which macro names will be used.
# The <code>Milter.ADDHDRS</code> action flag must be set. # The <code>Milter.SETSMLIST</code> action flag must be set.
# #
# May only be called from negotiate callback. # May only be called from negotiate callback.
# @since 0.9.2
# @param stage the protocol stage to set to macro list for
# @param macros a string with a space delimited list of macros
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):
...@@ -319,6 +365,7 @@ class Base(object): ...@@ -319,6 +365,7 @@ class Base(object):
# The <code>Milter.CHGFROM</code> action flag must be set. # The <code>Milter.CHGFROM</code> action flag must be set.
# #
# May be called from eom callback only. # May be called from eom callback only.
# @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
def chgfrom(self,sender,params=None): def chgfrom(self,sender,params=None):
...@@ -452,7 +499,9 @@ def dictfromlist(args): ...@@ -452,7 +499,9 @@ def dictfromlist(args):
## Convert ESMTP parm list to keyword dictionary. ## Convert ESMTP parm list to keyword dictionary.
# Params with no value are set to None in the dictionary. # Params with no value are set to None in the dictionary.
# @since 0.9.3
# @param str list of param strings of the form "NAME" or "NAME=VALUE" # @param str list of param strings of the form "NAME" or "NAME=VALUE"
# @return a dictionary of ESMTP param names and values
def param2dict(str): def param2dict(str):
"Convert ESMTP parm list to keyword dictionary." "Convert ESMTP parm list to keyword dictionary."
pairs = [x.split('=',1) for x in str] pairs = [x.split('=',1) for x in str]
...@@ -475,19 +524,8 @@ def envcallback(c,args): ...@@ -475,19 +524,8 @@ def envcallback(c,args):
return c(*pargs,**kw) return c(*pargs,**kw)
## Run the milter. ## Run the milter.
# The MTA can communicate with the milter by means of a
# unix, inet, or inet6 socket. By default, a unix domain socket
# is used. It must not exist,
# and sendmail will throw warnings if, eg, the file is under a
# group or world writable directory.
# <pre>
# setconn('unix:/var/run/pythonfilter')
# setconn('inet:8800') # listen on ANY interface
# setconn('inet:7871@@publichost') # listen on a specific interface
# setconn('inet6:8020')
# </pre>
# @param name the name of the milter known by the MTA # @param name the name of the milter known by the MTA
# @param socketname the descriptor of the unix socket # @param socketname the socket to be passed to <code>milter.setconn</code>
# @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):
......
...@@ -40,5 +40,20 @@ def main(): pass ...@@ -40,5 +40,20 @@ def main(): pass
def setdbg(lev): pass def setdbg(lev): pass
def settimeout(secs): pass def settimeout(secs): pass
def setbacklog(n): pass def setbacklog(n): pass
## Set the socket used to communicate with the MTA.
# The MTA can communicate with the milter by means of a
# unix, inet, or inet6 socket. By default, a unix domain socket
# is used. It must not exist,
# and sendmail will throw warnings if, eg, the file is under a
# group or world writable directory.
# <pre>
# setconn('unix:/var/run/pythonfilter')
# setconn('inet:8800') # listen on ANY interface
# setconn('inet:7871@@publichost') # listen on a specific interface
# setconn('inet6:8020')
# </pre>
def setconn(s): pass def setconn(s): pass
## Stop the milter gracefully.
def stop(): pass def stop(): pass
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment