Support new callbacks, including negotiate

This commit is contained in:
Stuart Gathman
2009-05-28 18:36:43 +00:00
parent ed17f9cecf
commit cb31963492
2 changed files with 173 additions and 41 deletions
+120 -41
View File
@@ -29,14 +29,125 @@ def uniqueID():
seqno = _seq = _seq + 1 seqno = _seq = _seq + 1
_seq_lock.release() _seq_lock.release()
return seqno return seqno
class Milter: def nocallback(func):
"""A simple class interface to the milter module. func.milter_protocol = 'NO'
""" return func
def noreply(func):
func.milter_protocol = 'NR'
return func
class DisabledAction(RuntimeError):
pass
class Base(object):
def __init__(self):
self.__actions = CURR_ACTS # all actions enabled
def _setctx(self,ctx): def _setctx(self,ctx):
self.__ctx = ctx self.__ctx = ctx
if ctx: if ctx:
ctx.setpriv(self) ctx.setpriv(self)
@nocallback
def connect(self,hostname,family,hostaddr): return CONTINUE
@nocallback
def hello(self,hostname): return CONTINUE
@nocallback
def envfrom(self,f,*str): return CONTINUE
@nocallback
def envrcpt(self,to,*str): return CONTINUE
@nocallback
def data(self): return CONTINUE
@nocallback
def header(self,field,value): return CONTINUE
@nocallback
def eoh(self): return CONTINUE
@nocallback
def body(self,unused): return CONTINUE
@nocallback
def eom(self): return CONTINUE
@nocallback
def abort(self): return CONTINUE
@nocallback
def unknown(self,cmd): return CONTINUE
@nocallback
def close(self): return CONTINUE
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: print func.__name__,'NOREPLY'
if ca != 'NO': p &= ~nc
elif p & nc: print func.__name__,'NOCALLBACK'
p[1] = p & ~P_RCPT_REJ & ~P_HDR_LEADSPC
except:
# don't change anything if something went wrong
return ALL_OPTS
return CONTINUE
# Milter methods which can be invoked from callbacks
def getsymval(self,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)
def setsmlist(self,stage,macros):
if not self.__actions & SETSMLIST: raise DisabledAction("SETSMLIST")
if type(macros) in (list,tuple):
macros = ' '.join(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)
def chgheader(self,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)
def delrcpt(self,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)
def chgfrom(self,sender,params=None):
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)
def progress(self):
return self.__ctx.progress()
class Milter(Base):
"""A simple class interface to the milter module.
"""
# user replaceable callbacks # user replaceable callbacks
def log(self,*msg): def log(self,*msg):
@@ -96,42 +207,6 @@ class Milter:
self.log("close") self.log("close")
return CONTINUE return CONTINUE
# Milter methods which can be invoked from callbacks
def getsymval(self,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)
# Milter methods which can only be called from eom callback.
def addheader(self,field,value,idx=-1):
return self.__ctx.addheader(field,value,idx)
def chgheader(self,field,idx,value):
return self.__ctx.chgheader(field,idx,value)
def addrcpt(self,rcpt,params=None):
return self.__ctx.addrcpt(rcpt,params)
def delrcpt(self,rcpt):
return self.__ctx.delrcpt(rcpt)
def replacebody(self,body):
return self.__ctx.replacebody(body)
def chgfrom(self,sender,params=None):
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):
return self.__ctx.quarantine(reason)
def progress(self):
return self.__ctx.progress()
factory = Milter factory = Milter
def connectcallback(ctx,hostname,family,hostaddr): def connectcallback(ctx,hostname,family,hostaddr):
@@ -214,7 +289,11 @@ def runmilter(name,socketname,timeout = 0):
milter.setconn(socketname) milter.setconn(socketname)
if timeout > 0: milter.settimeout(timeout) if timeout > 0: milter.settimeout(timeout)
# The name *must* match the X line in sendmail.cf (supposedly) # The name *must* match the X line in sendmail.cf (supposedly)
milter.register(name) milter.register(name,
data=lambda ctx: ctx.getpriv().data(),
unknown=lambda ctx,cmd: ctx.getpriv().unknown(cmd),
negotiate=lambda ctx,opt: ctx.getpriv().negotiate(opt)
)
start_seq = _seq start_seq = _seq
try: try:
milter.main() milter.main()
+53
View File
@@ -35,6 +35,9 @@ $ python setup.py help
libraries=["milter","smutil","resolv"] libraries=["milter","smutil","resolv"]
* $Log$ * $Log$
* Revision 1.18 2009/05/21 21:53:05 customdesigned
* First cut at support unknown, data, negotiate callbacks.
*
* Revision 1.17 2009/02/06 04:28:08 customdesigned * Revision 1.17 2009/02/06 04:28:08 customdesigned
* Oops! Missing options argument pointer for addrcpt. * Oops! Missing options argument pointer for addrcpt.
* *
@@ -1367,6 +1370,27 @@ milter_progress(PyObject *self, PyObject *args) {
} }
#endif #endif
#ifdef SMFIF_SETSMLIST
static char milter_setsmlist__doc__[] =
"setsmlist(stage,macrolist) -> None\n\
Tell the MTA which macro values we are interested in for a given stage";
static PyObject *
milter_setsmlist(PyObject *self, PyObject *args) {
SMFICTX *ctx;
PyThreadState *t;
int stage = 0;
char *smlist = 0;
if (!PyArg_ParseTuple(args, "is:setsmlist",&stage, &smlist)) return NULL;
ctx = _find_context(self);
if (ctx == NULL) return NULL;
t = PyEval_SaveThread();
return _thread_return(t,smfi_setsmlist(ctx,stage,smlist),
"cannot set macro list");
}
#endif
static PyMethodDef context_methods[] = { static PyMethodDef context_methods[] = {
{ "getsymval", milter_getsymval, METH_VARARGS, milter_getsymval__doc__}, { "getsymval", milter_getsymval, METH_VARARGS, milter_getsymval__doc__},
{ "setreply", milter_setreply, METH_VARARGS, milter_setreply__doc__}, { "setreply", milter_setreply, METH_VARARGS, milter_setreply__doc__},
@@ -1385,6 +1409,9 @@ static PyMethodDef context_methods[] = {
#endif #endif
#ifdef SMFIF_CHGFROM #ifdef SMFIF_CHGFROM
{ "chgfrom", milter_chgfrom, METH_VARARGS, milter_chgfrom__doc__}, { "chgfrom", milter_chgfrom, METH_VARARGS, milter_chgfrom__doc__},
#endif
#ifdef SMFIF_SETSMLIST
{ "setsmlist", milter_setsmlist, METH_VARARGS, milter_setsmlist__doc__},
#endif #endif
{ NULL, NULL } { NULL, NULL }
}; };
@@ -1505,8 +1532,34 @@ initmilter(void) {
#ifdef SMFIF_CHGFROM #ifdef SMFIF_CHGFROM
setitem(d,"CHGFROM",SMFIF_CHGFROM); setitem(d,"CHGFROM",SMFIF_CHGFROM);
#endif #endif
#ifdef SMFIF_SETSMLIST
setitem(d,"SETSMLIST",SMFIF_SETSMLIST);
#endif
#ifdef SMFIF_ALL_OPTS #ifdef SMFIF_ALL_OPTS
setitem(d,"P_RCPT_REJ",SMFIP_RCPT_REJ);
setitem(d,"P_NR_CONN",SMFIP_NR_CONN);
setitem(d,"P_NR_HELO",SMFIP_NR_HELO);
setitem(d,"P_NR_MAIL",SMFIP_NR_MAIL);
setitem(d,"P_NR_RCPT",SMFIP_NR_RCPT);
setitem(d,"P_NR_DATA",SMFIP_NR_DATA);
setitem(d,"P_NR_UNKN",SMFIP_NR_UNKN);
setitem(d,"P_NR_EOH",SMFIP_NR_EOH);
setitem(d,"P_NR_BODY",SMFIP_NR_BODY);
setitem(d,"P_NR_HDR",SMFIP_NR_HDR);
setitem(d,"P_NOCONNECT",SMFIP_NR_CONN);
setitem(d,"P_NOHELO",SMFIP_NR_HELO);
setitem(d,"P_NOMAIL",SMFIP_NR_MAIL);
setitem(d,"P_NORCPT",SMFIP_NR_RCPT);
setitem(d,"P_NODATA",SMFIP_NR_DATA);
setitem(d,"P_NOUNKNOWN",SMFIP_NR_UNKN);
setitem(d,"P_NOEOH",SMFIP_NR_EOH);
setitem(d,"P_NOBODY",SMFIP_NR_BODY);
setitem(d,"P_NOHDRS",SMFIP_NR_HDR);
setitem(d,"P_HDR_LEADSPC",SMFIP_HDR_LEADSPC);
setitem(d,"P_SKIP",SMFIP_SKIP);
setitem(d,"ALL_OPTS",SMFIF_ALL_OPTS); setitem(d,"ALL_OPTS",SMFIF_ALL_OPTS);
setitem(d,"SKIP",SMFIS_SKIP);
setitem(d,"NOREPLY",SMFIS_NOREPLY);
#endif #endif
setitem(d,"CONTINUE", SMFIS_CONTINUE); setitem(d,"CONTINUE", SMFIS_CONTINUE);
setitem(d,"REJECT", SMFIS_REJECT); setitem(d,"REJECT", SMFIS_REJECT);