diff --git a/Milter/__init__.py b/Milter/__init__.py
index fe796c9..217514e 100755
--- a/Milter/__init__.py
+++ b/Milter/__init__.py
@@ -40,6 +40,41 @@ OPTIONAL_CALLBACKS = {
'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 _protocol
+# member is a bitmask of protocol options negotiated. So,
+# for instance, if self._protocol & Milter.P_RCPT_REJ
+# is true, then that feature was successfully negotiated with the MTA.
+#
+# Sample use:
+#
+# class myMilter(Milter.Base): +# def envrcpt(self,to,*params): +# return Milter.CONTINUE +# myMilter = Milter.enable_protocols(myMilter,Milter.P_RCPT_REJ) +#+# @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. # If the MTA supports it, tells the MTA not to call this callback, # increasing efficiency. All the callbacks (except negotiate) @@ -48,6 +83,7 @@ OPTIONAL_CALLBACKS = { # 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. +# @since 0.9.2 def nocallback(func): try: func.milter_protocol = OPTIONAL_CALLBACKS[func.__name__][1] @@ -62,6 +98,7 @@ def nocallback(func): # 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. +# @since 0.9.2 def noreply(func): try: nr_mask = OPTIONAL_CALLBACKS[func.__name__][0] @@ -81,6 +118,7 @@ def noreply(func): # connection in the negotiate callback. If the application then calls # the feature anyway via an instance method, this exception is # thrown. +# @since 0.9.2 class DisabledAction(RuntimeError): pass @@ -89,7 +127,7 @@ class DisabledAction(RuntimeError): # unless they are using the low lever milter module directly. # All optional callbacks are disabled, and automatically # reenabled when overridden. -# +# @since 0.9.2 class Base(object): "The core class interface to the milter module." @@ -109,6 +147,7 @@ class Base(object): # CHGHDRS,QUARANTINE,CHGFROM,SETSMLIST. # The
Milter.CURR_ACTS bitmask is all actions
# known when the milter module was compiled.
+ # @since 0.9.2
#
## @var _protocol
@@ -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_NODATA P_NOUNKNOWN P_NOEOH P_NOBODY P_NOHDRS P_HDR_LEADSPC P_SKIP
# (all under the Milter namespace).
+ # @since 0.9.2
## Defined by subclasses to write log messages.
def log(self,*msg): pass
@@ -159,6 +199,7 @@ class Base(object):
## Called when the SMTP client says DATA.
# Returning REJECT rejects the message without wasting bandwidth
# on the unwanted message.
+ # @since 0.9.2
@nocallback
def data(self): return CONTINUE
## Called for each header field in the message body.
@@ -173,6 +214,7 @@ class Base(object):
def body(self,blk): return CONTINUE
## Called when the SMTP client issues an unknown command.
# @param cmd the unknown command
+ # @since 0.9.2
@nocallback
def unknown(self,cmd): return CONTINUE
## Called at the end of the message body.
@@ -194,12 +236,13 @@ class Base(object):
# Libmilter passes the protocol bits that the current MTA knows
# how to skip. We clear the ones we don't want to skip.
# The negation is somewhat mind bending, but it is simple.
+ # @since 0.9.2
@classmethod
def protocol_mask(klass):
try:
return klass._protocol_mask
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():
func = getattr(klass,func)
ca = getattr(func,'milter_protocol',0)
@@ -212,11 +255,11 @@ class Base(object):
# Default negotiation sets P_NO* and P_NR* for callbacks
# marked @@nocallback and @@noreply respectively, leaves all
# actions enabled, and enables Milter.SKIP.
+ # @since 0.9.2
def negotiate(self,opts):
try:
self._actions,p,f1,f2 = opts
- opts[1] = self._protocol = \
- p & ~self.protocol_mask() & ~P_RCPT_REJ & ~P_HDR_LEADSPC
+ opts[1] = self._protocol = p & ~self.protocol_mask()
opts[2] = 0
opts[3] = 0
#self.log("Negotiated:",opts)
@@ -241,9 +284,12 @@ class Base(object):
return self._ctx.setreply(rcode,xcode,msg,*ml)
## Tell the MTA which macro names will be used.
- # The Milter.ADDHDRS action flag must be set.
+ # The Milter.SETSMLIST action flag must be set.
#
# 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):
if not self._actions & SETSMLIST: raise DisabledAction("SETSMLIST")
if type(macros) in (list,tuple):
@@ -319,6 +365,7 @@ class Base(object):
# The Milter.CHGFROM action flag must be set.
#
# May be called from eom callback only.
+ # @since 0.9.1
# @param sender the new sender address
# @param params an optional list of ESMTP parameters
def chgfrom(self,sender,params=None):
@@ -452,7 +499,9 @@ def dictfromlist(args):
## Convert ESMTP parm list to keyword 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"
+# @return a dictionary of ESMTP param names and values
def param2dict(str):
"Convert ESMTP parm list to keyword dictionary."
pairs = [x.split('=',1) for x in str]
@@ -475,19 +524,8 @@ def envcallback(c,args):
return c(*pargs,**kw)
## 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.
-#
-# setconn('unix:/var/run/pythonfilter')
-# setconn('inet:8800') # listen on ANY interface
-# setconn('inet:7871@@publichost') # listen on a specific interface
-# setconn('inet6:8020')
-#
# @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 milter.setconn
# @param timeout the time in secs the MTA should wait for a response before
# considering this milter dead
def runmilter(name,socketname,timeout = 0):
diff --git a/doc/milter.py b/doc/milter.py
index bcd8ee6..547dd8f 100644
--- a/doc/milter.py
+++ b/doc/milter.py
@@ -40,5 +40,20 @@ def main(): pass
def setdbg(lev): pass
def settimeout(secs): 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.
+#
+# setconn('unix:/var/run/pythonfilter')
+# setconn('inet:8800') # listen on ANY interface
+# setconn('inet:7871@@publichost') # listen on a specific interface
+# setconn('inet6:8020')
+#
def setconn(s): pass
+
+## Stop the milter gracefully.
def stop(): pass