Support new callbacks, including negotiate
This commit is contained in:
+119
-40
@@ -30,13 +30,124 @@ def uniqueID():
|
|||||||
_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()
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user