diff --git a/Milter/__init__.py b/Milter/__init__.py
index d0212de4728a87accde204f9d463c039b46251af..58ebdf03616900034c9d80a6347512073083b97a 100755
--- a/Milter/__init__.py
+++ b/Milter/__init__.py
@@ -4,14 +4,14 @@
# A thin OO wrapper for the milter module
+__version__ = '0.9.2'
+
import os
import milter
import thread
from milter import *
-__version__ = '0.9.2'
-
_seq_lock = thread.allocate_lock()
_seq = 0
@@ -24,19 +24,32 @@ def uniqueID():
_seq_lock.release()
return seqno
-OPTIONAL_CALLBACKS = frozenset((
- 'connect','hello','envfrom','envrcpt','data','header','eoh','body','unknown'))
+OPTIONAL_CALLBACKS = {
+ 'connect':(P_NR_CONN,P_NOCONNECT),
+ 'hello':(P_NR_HELO,P_NOHELO),
+ 'envfrom':(P_NR_MAIL,P_NOMAIL),
+ 'envrcpt':(P_NR_RCPT,P_NORCPT),
+ 'data':(P_NR_DATA,P_NODATA),
+ 'unknown':(P_NR_UNKN,P_NOUNKNOWN),
+ 'eoh':(P_NR_EOH,P_NOEOH),
+ 'body':(P_NR_BODY,P_NOBODY),
+ 'header':(P_NR_HDR,P_NOHDRS)
+}
+
def nocallback(func):
- if not func.__name__ in OPTIONAL_CALLBACKS:
+ try:
+ func.milter_protocol = OPTIONAL_CALLBACKS[func.__name__][1]
+ except KeyError:
raise ValueError(
'@nocallback applied to non-optional method: '+func.__name__)
- func.milter_protocol = 'NO'
return func
+
def noreply(func):
- if not func.__name__ in OPTIONAL_CALLBACKS:
+ try:
+ func.milter_protocol = OPTIONAL_CALLBACKS[func.__name__][0]
+ except KeyErro:
raise ValueError(
'@noreply applied to non-optional method: '+func.__name__)
- func.milter_protocol = 'NR'
return func
class DisabledAction(RuntimeError):
@@ -49,8 +62,8 @@ class Base(object):
"The core class interface to the milter module."
def _setctx(self,ctx):
- self.__ctx = ctx
- self.__actions = CURR_ACTS # all actions enabled by default
+ self._ctx = ctx
+ self._actions = CURR_ACTS # all actions enabled by default
if ctx:
ctx.setpriv(self)
def log(self,*msg): pass
@@ -76,32 +89,27 @@ class Base(object):
def abort(self): return CONTINUE
def close(self): return CONTINUE
+ # Return mask of SMFIP_N.. protocol bits to clear for this class
+ @classmethod
+ def protocol_mask(klass):
+ try:
+ return klass._protocol_mask
+ except AttributeError:
+ p = 0
+ for func,(nr,nc) in OPTIONAL_CALLBACKS.items():
+ func = getattr(klass,func)
+ ca = getattr(func,'milter_protocol',0)
+ #print func,hex(nr),hex(nc),hex(ca)
+ p |= (nr|nc) & ~ca
+ klass._protocol_mask = p
+ return p
+
# Default negotiation sets P_NO* and P_NR* for callbacks
# marked @nocallback and @noreply respectively
def negotiate(self,opts):
try:
- self.__actions,p,f1,f2 = opts
- for func,nr,nc in (
- (self.connect,P_NR_CONN,P_NOCONNECT),
- (self.hello,P_NR_HELO,P_NOHELO),
- (self.envfrom,P_NR_MAIL,P_NOMAIL),
- (self.envrcpt,P_NR_RCPT,P_NORCPT),
- (self.data,P_NR_DATA,P_NODATA),
- (self.unknown,P_NR_UNKN,P_NOUNKNOWN),
- (self.eoh,P_NR_EOH,P_NOEOH),
- (self.body,P_NR_BODY,P_NOBODY),
- (self.header,P_NR_HDR,P_NOHDRS)
- ):
- ca = getattr(func,'milter_protocol',None)
- if ca != 'NR':
- p &= ~nr
- #elif p & nr:
- # self.log(func.__name__,'NOREPLY')
- if ca != 'NO':
- p &= ~nc
- #elif p & nc:
- # self.log(func.__name__,'NOCALLBACK')
- opts[1] = p & ~P_RCPT_REJ & ~P_HDR_LEADSPC
+ self._actions,p,f1,f2 = opts
+ opts[1] = p & ~self.protocol_mask() & ~P_RCPT_REJ & ~P_HDR_LEADSPC
opts[2] = 0
opts[3] = 0
self.log("Negotiated:",opts)
@@ -112,53 +120,53 @@ class Base(object):
# Milter methods which can be invoked from most callbacks
def getsymval(self,sym):
- return self.__ctx.getsymval(sym)
+ return self._ctx.getsymval(sym)
# If sendmail does not support setmlreply, then only the
# first msg line is used.
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
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):
macros = ' '.join(macros)
- return self.__ctx.setsmlist(stage,macros)
+ return self._ctx.setsmlist(stage,macros)
# Milter methods which can only be called from eom callback.
def addheader(self,field,value,idx=-1):
- if not self.__actions & ADDHDRS: raise DisabledAction("ADDHDRS")
- return self.__ctx.addheader(field,value,idx)
+ if not self._actions & ADDHDRS: raise DisabledAction("ADDHDRS")
+ return self._ctx.addheader(field,value,idx)
def chgheader(self,field,idx,value):
- if not self.__actions & CHGHDRS: raise DisabledAction("CHGHDRS")
- return self.__ctx.chgheader(field,idx,value)
+ if not self._actions & CHGHDRS: raise DisabledAction("CHGHDRS")
+ return self._ctx.chgheader(field,idx,value)
def addrcpt(self,rcpt,params=None):
- if not self.__actions & ADDRCPT: raise DisabledAction("ADDRCPT")
- return self.__ctx.addrcpt(rcpt,params)
+ if not self._actions & ADDRCPT: raise DisabledAction("ADDRCPT")
+ return self._ctx.addrcpt(rcpt,params)
def delrcpt(self,rcpt):
- if not self.__actions & DELRCPT: raise DisabledAction("DELRCPT")
- return self.__ctx.delrcpt(rcpt)
+ if not self._actions & DELRCPT: raise DisabledAction("DELRCPT")
+ return self._ctx.delrcpt(rcpt)
def replacebody(self,body):
- if not self.__actions & MODBODY: raise DisabledAction("MODBODY")
- return self.__ctx.replacebody(body)
+ if not self._actions & MODBODY: raise DisabledAction("MODBODY")
+ return self._ctx.replacebody(body)
def chgfrom(self,sender,params=None):
- if not self.__actions & CHGFROM: raise DisabledAction("CHGFROM")
- return self.__ctx.chgfrom(sender,params)
+ if not self._actions & CHGFROM: raise DisabledAction("CHGFROM")
+ return self._ctx.chgfrom(sender,params)
# When quarantined, a message goes into the mailq as if to be delivered,
# but delivery is deferred until the message is unquarantined.
def quarantine(self,reason):
- if not self.__actions & QUARANTINE: raise DisabledAction("QUARANTINE")
- return self.__ctx.quarantine(reason)
+ if not self._actions & QUARANTINE: raise DisabledAction("QUARANTINE")
+ return self._ctx.quarantine(reason)
def progress(self):
- return self.__ctx.progress()
+ return self._ctx.progress()
# A logging but otherwise do nothing Milter base class included
# for compatibility with previous versions of pymilter.
diff --git a/pymilter.spec b/pymilter.spec
index 22955a9768b171241c7f47ec38bc7d68315f57ec..e0d2dac4f50ab678ea2c3e02cf3546dceca8f3a9 100644
--- a/pymilter.spec
+++ b/pymilter.spec
@@ -12,7 +12,7 @@
Summary: Python interface to sendmail milter API
Name: pymilter
Version: 0.9.2
-Release: 1%{dist}
+Release: 2%{dist}
Source: http://downloads.sourceforge.net/pymilter/%{name}-%{version}.tar.gz
License: GPLv2+
Group: Development/Libraries
@@ -85,6 +85,7 @@ rm -rf $RPM_BUILD_ROOT
%changelog
* Thu May 28 2009 Stuart Gathman <stuart@bmsi.com> 0.9.2-1
- Add new callback support: data,negotiate,unknown
+- Auto-negotiate protocol steps
* Thu Feb 05 2009 Stuart Gathman <stuart@bmsi.com> 0.9.1-1
- Fix missing address of optional param to addrcpt