From cb3196349267b898f80ad7f20e60510d4e948539 Mon Sep 17 00:00:00 2001 From: Stuart Gathman Date: Thu, 28 May 2009 18:36:43 +0000 Subject: [PATCH] Support new callbacks, including negotiate --- Milter/__init__.py | 161 +++++++++++++++++++++++++++++++++------------ miltermodule.c | 53 +++++++++++++++ 2 files changed, 173 insertions(+), 41 deletions(-) diff --git a/Milter/__init__.py b/Milter/__init__.py index 7293bf1..4d85717 100755 --- a/Milter/__init__.py +++ b/Milter/__init__.py @@ -29,14 +29,125 @@ def uniqueID(): seqno = _seq = _seq + 1 _seq_lock.release() return seqno - -class Milter: - """A simple class interface to the milter module. - """ + +def nocallback(func): + 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): self.__ctx = ctx if ctx: 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 def log(self,*msg): @@ -96,42 +207,6 @@ class Milter: self.log("close") 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 def connectcallback(ctx,hostname,family,hostaddr): @@ -214,7 +289,11 @@ def runmilter(name,socketname,timeout = 0): milter.setconn(socketname) if timeout > 0: milter.settimeout(timeout) # 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 try: milter.main() diff --git a/miltermodule.c b/miltermodule.c index 27f8b88..9b7b101 100644 --- a/miltermodule.c +++ b/miltermodule.c @@ -35,6 +35,9 @@ $ python setup.py help libraries=["milter","smutil","resolv"] * $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 * Oops! Missing options argument pointer for addrcpt. * @@ -1367,6 +1370,27 @@ milter_progress(PyObject *self, PyObject *args) { } #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[] = { { "getsymval", milter_getsymval, METH_VARARGS, milter_getsymval__doc__}, { "setreply", milter_setreply, METH_VARARGS, milter_setreply__doc__}, @@ -1385,6 +1409,9 @@ static PyMethodDef context_methods[] = { #endif #ifdef SMFIF_CHGFROM { "chgfrom", milter_chgfrom, METH_VARARGS, milter_chgfrom__doc__}, +#endif +#ifdef SMFIF_SETSMLIST + { "setsmlist", milter_setsmlist, METH_VARARGS, milter_setsmlist__doc__}, #endif { NULL, NULL } }; @@ -1505,8 +1532,34 @@ initmilter(void) { #ifdef SMFIF_CHGFROM setitem(d,"CHGFROM",SMFIF_CHGFROM); #endif +#ifdef SMFIF_SETSMLIST + setitem(d,"SETSMLIST",SMFIF_SETSMLIST); +#endif #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,"SKIP",SMFIS_SKIP); + setitem(d,"NOREPLY",SMFIS_NOREPLY); #endif setitem(d,"CONTINUE", SMFIS_CONTINUE); setitem(d,"REJECT", SMFIS_REJECT);