Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 07503a8dea | |||
| 6a1996117c | |||
| 77c0ce6b2e | |||
| 7311f65150 | |||
| 84bd61aac1 | |||
| 372fad6ac9 | |||
| 60963b3c37 | |||
| 6221f8b753 | |||
| 344ecc7c07 | |||
| ee14614c3e | |||
| 4bb2403223 | |||
| d58546930a | |||
| f8efbb23df | |||
| 26b006455e | |||
| 9b7ca633f3 | |||
| 5928e99520 | |||
| 6d3833da72 | |||
| 2937935fea | |||
| 31aa39034b | |||
| cb31963492 | |||
| ed17f9cecf | |||
| 0e1a2de41f | |||
| 9f419e3fc8 | |||
| 6913fd3e66 | |||
| 780ac63ebe | |||
| b51c08ba3a | |||
| 2e7805e531 | |||
| b1eae98453 | |||
| 9118364164 |
@@ -7,6 +7,10 @@ real, usable Python extension.
|
||||
|
||||
Other contributors (in random order):
|
||||
|
||||
arkanes@irc.freenode.net
|
||||
for suggesting a class method to compute and cache protocol masks
|
||||
habnabit@habnabit.org
|
||||
for suggesting function attributes and decorators for protocol negotiation
|
||||
Dwayne Litzenberger, B.A.Sc.
|
||||
for library_dirs patch to compile on Debian
|
||||
Dave MacQuigg
|
||||
|
||||
+184
-61
@@ -4,19 +4,13 @@
|
||||
|
||||
# A thin OO wrapper for the milter module
|
||||
|
||||
__version__ = '0.9.2'
|
||||
|
||||
import os
|
||||
import milter
|
||||
import thread
|
||||
|
||||
from milter import ACCEPT,CONTINUE,REJECT,DISCARD,TEMPFAIL, \
|
||||
set_flags, setdbg, setbacklog, settimeout, error, \
|
||||
ADDHDRS, CHGBODY, ADDRCPT, DELRCPT, CHGHDRS, \
|
||||
V1_ACTS, V2_ACTS, CURR_ACTS
|
||||
|
||||
try: from milter import QUARANTINE
|
||||
except: pass
|
||||
|
||||
__version__ = '0.8.5'
|
||||
from milter import *
|
||||
|
||||
_seq_lock = thread.allocate_lock()
|
||||
_seq = 0
|
||||
@@ -30,30 +24,181 @@ def uniqueID():
|
||||
_seq_lock.release()
|
||||
return seqno
|
||||
|
||||
class Milter:
|
||||
"""A simple class interface to the milter module.
|
||||
"""
|
||||
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):
|
||||
try:
|
||||
func.milter_protocol = OPTIONAL_CALLBACKS[func.__name__][1]
|
||||
except KeyError:
|
||||
raise ValueError(
|
||||
'@nocallback applied to non-optional method: '+func.__name__)
|
||||
return func
|
||||
|
||||
def noreply(func):
|
||||
try:
|
||||
nr_mask = OPTIONAL_CALLBACKS[func.__name__][0]
|
||||
except KeyErro:
|
||||
raise ValueError(
|
||||
'@noreply applied to non-optional method: '+func.__name__)
|
||||
def wrapper(self,*args):
|
||||
rc = func(self,*args)
|
||||
if self._protocol & nr_mask: return NOREPLY
|
||||
return rc
|
||||
wrapper.milter_protocol = nr_mask
|
||||
return wrapper
|
||||
|
||||
class DisabledAction(RuntimeError):
|
||||
pass
|
||||
|
||||
# A do nothing Milter base class from which python milters should derive
|
||||
# unless they are using the milter C module directly.
|
||||
|
||||
class Base(object):
|
||||
"The core class interface to the milter module."
|
||||
|
||||
def _setctx(self,ctx):
|
||||
self.__ctx = ctx
|
||||
self._ctx = ctx
|
||||
self._actions = CURR_ACTS # all actions enabled by default
|
||||
self._protocol = 0 # no protocol options by default
|
||||
if ctx:
|
||||
ctx.setpriv(self)
|
||||
def log(self,*msg): pass
|
||||
@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 unknown(self,cmd): return CONTINUE
|
||||
def eom(self): return CONTINUE
|
||||
def abort(self): return CONTINUE
|
||||
def close(self): return CONTINUE
|
||||
|
||||
# Return mask of SMFIP_N.. protocol option 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
|
||||
opts[1] = self._protocol = \
|
||||
p & ~self.protocol_mask() & ~P_RCPT_REJ & ~P_HDR_LEADSPC
|
||||
opts[2] = 0
|
||||
opts[3] = 0
|
||||
#self.log("Negotiated:",opts)
|
||||
except:
|
||||
# don't change anything if something went wrong
|
||||
return ALL_OPTS
|
||||
return CONTINUE
|
||||
|
||||
# Milter methods which can be invoked from most 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)
|
||||
|
||||
# may only be called from negotiate callback
|
||||
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()
|
||||
|
||||
# A logging but otherwise do nothing Milter base class included
|
||||
# for compatibility with previous versions of pymilter.
|
||||
|
||||
class Milter(Base):
|
||||
"A simple class interface to the milter module."
|
||||
|
||||
# user replaceable callbacks
|
||||
def log(self,*msg):
|
||||
print 'Milter:',
|
||||
for i in msg: print i,
|
||||
print
|
||||
|
||||
@noreply
|
||||
def connect(self,hostname,family,hostaddr):
|
||||
"Called for each connection to sendmail."
|
||||
self.log("connect from %s at %s" % (hostname,hostaddr))
|
||||
return CONTINUE
|
||||
|
||||
@noreply
|
||||
def hello(self,hostname):
|
||||
"Called after the HELO command."
|
||||
self.log("hello from %s" % hostname)
|
||||
return CONTINUE
|
||||
|
||||
@noreply
|
||||
def envfrom(self,f,*str):
|
||||
"""Called to begin each message.
|
||||
f -> string message sender
|
||||
@@ -62,25 +207,24 @@ class Milter:
|
||||
self.log("mail from",f,str)
|
||||
return CONTINUE
|
||||
|
||||
@noreply
|
||||
def envrcpt(self,to,*str):
|
||||
"Called for each message recipient."
|
||||
self.log("rcpt to",to,str)
|
||||
return CONTINUE
|
||||
|
||||
@noreply
|
||||
def header(self,field,value):
|
||||
"Called for each message header."
|
||||
self.log("%s: %s" % (field,value))
|
||||
return CONTINUE
|
||||
|
||||
@noreply
|
||||
def eoh(self):
|
||||
"Called after all headers are processed."
|
||||
self.log("eoh")
|
||||
return CONTINUE
|
||||
|
||||
def body(self,unused):
|
||||
"Called to transfer the message body."
|
||||
return CONTINUE
|
||||
|
||||
def eom(self):
|
||||
"Called at the end of message."
|
||||
self.log("eom")
|
||||
@@ -96,54 +240,29 @@ 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):
|
||||
def negotiate_callback(ctx,opts):
|
||||
m = factory()
|
||||
m._setctx(ctx)
|
||||
return m.negotiate(opts)
|
||||
|
||||
def connect_callback(ctx,hostname,family,hostaddr,nr_mask=P_NR_CONN):
|
||||
m = ctx.getpriv()
|
||||
if not m:
|
||||
# If not already created (because the current MTA doesn't support
|
||||
# xmfi_negotiate), create the connection object.
|
||||
m = factory()
|
||||
m._setctx(ctx)
|
||||
return m.connect(hostname,family,hostaddr)
|
||||
|
||||
def closecallback(ctx):
|
||||
def close_callback(ctx):
|
||||
m = ctx.getpriv()
|
||||
if not m: return CONTINUE
|
||||
rc = m.close()
|
||||
m._setctx(None) # release milterContext
|
||||
try:
|
||||
rc = m.close()
|
||||
finally:
|
||||
m._setctx(None) # release milterContext
|
||||
return rc
|
||||
|
||||
def dictfromlist(args):
|
||||
@@ -194,7 +313,7 @@ def runmilter(name,socketname,timeout = 0):
|
||||
|
||||
# The default flags set include everything
|
||||
# milter.set_flags(milter.ADDHDRS)
|
||||
milter.set_connect_callback(connectcallback)
|
||||
milter.set_connect_callback(connect_callback)
|
||||
milter.set_helo_callback(lambda ctx, host: ctx.getpriv().hello(host))
|
||||
# For envfrom and envrcpt, we would like to convert ESMTP parms to keyword
|
||||
# parms, but then all existing users would have to include **kw to accept
|
||||
@@ -207,12 +326,16 @@ def runmilter(name,socketname,timeout = 0):
|
||||
milter.set_body_callback(lambda ctx,chunk: ctx.getpriv().body(chunk))
|
||||
milter.set_eom_callback(lambda ctx: ctx.getpriv().eom())
|
||||
milter.set_abort_callback(lambda ctx: ctx.getpriv().abort())
|
||||
milter.set_close_callback(closecallback)
|
||||
milter.set_close_callback(close_callback)
|
||||
|
||||
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=negotiate_callback
|
||||
)
|
||||
start_seq = _seq
|
||||
try:
|
||||
milter.main()
|
||||
|
||||
+7
-4
@@ -5,6 +5,9 @@
|
||||
# Send DSNs, do call back verification,
|
||||
# and generate DSN messages from a template
|
||||
# $Log$
|
||||
# Revision 1.16 2007/09/25 01:24:59 customdesigned
|
||||
# Allow arbitrary object, not just spf.query like, to provide data for create_msg
|
||||
#
|
||||
# Revision 1.15 2007/09/24 20:13:26 customdesigned
|
||||
# Remove explicit spf dependency.
|
||||
#
|
||||
@@ -31,7 +34,7 @@ import Milter
|
||||
import time
|
||||
import dns
|
||||
|
||||
def send_dsn(mailfrom,receiver,msg=None,timeout=600,session=None):
|
||||
def send_dsn(mailfrom,receiver,msg=None,timeout=600,session=None,ourfrom=''):
|
||||
"""Send DSN. If msg is None, do callback verification.
|
||||
Mailfrom is original sender we are sending DSN or CBV to.
|
||||
Receiver is the MTA sending the DSN.
|
||||
@@ -62,14 +65,14 @@ def send_dsn(mailfrom,receiver,msg=None,timeout=600,session=None):
|
||||
raise smtplib.SMTPHeloError(code, resp)
|
||||
if msg:
|
||||
try:
|
||||
smtp.sendmail('<>',mailfrom,msg)
|
||||
smtp.sendmail('<%s>'%ourfrom,mailfrom,msg)
|
||||
except smtplib.SMTPSenderRefused:
|
||||
# does not accept DSN, try postmaster (at the risk of mail loops)
|
||||
smtp.sendmail('<postmaster@%s>'%receiver,mailfrom,msg)
|
||||
else: # CBV
|
||||
code,resp = smtp.docmd('MAIL FROM: <>')
|
||||
code,resp = smtp.docmd('MAIL FROM: <%s>'%ourfrom)
|
||||
if code != 250:
|
||||
raise smtplib.SMTPSenderRefused(code, resp, '<>')
|
||||
raise smtplib.SMTPSenderRefused(code, resp, '<%s>'%ourfrom)
|
||||
code,resp = smtp.rcpt(mailfrom)
|
||||
if code not in (250,251):
|
||||
return (code,resp) # permanent error
|
||||
|
||||
+222
-14
@@ -35,6 +35,30 @@ $ python setup.py help
|
||||
libraries=["milter","smutil","resolv"]
|
||||
|
||||
* $Log$
|
||||
* Revision 1.22 2009/05/29 19:53:36 customdesigned
|
||||
* Typo SMFIS_ALL_OPTS
|
||||
*
|
||||
* Revision 1.21 2009/05/29 19:49:40 customdesigned
|
||||
* Typo calling helo instead of negotiate.
|
||||
*
|
||||
* Revision 1.20 2009/05/29 18:25:59 customdesigned
|
||||
* Null terminate keyword list.
|
||||
*
|
||||
* Revision 1.19 2009/05/28 18:36:42 customdesigned
|
||||
* Support new callbacks, including negotiate
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Revision 1.16 2008/12/16 04:21:05 customdesigned
|
||||
* Fedora release
|
||||
*
|
||||
* Revision 1.15 2008/12/13 20:29:56 customdesigned
|
||||
* Split off milter applications.
|
||||
*
|
||||
* Revision 1.14 2008/12/04 19:43:00 customdesigned
|
||||
* Doc updates.
|
||||
*
|
||||
@@ -375,7 +399,7 @@ generic_set_callback(PyObject *args,char *t,PyObject **cb) {
|
||||
callback = 0;
|
||||
else {
|
||||
if (!PyCallable_Check(callback)) {
|
||||
PyErr_SetString(PyExc_TypeError, "parameter must be callable");
|
||||
PyErr_SetString(PyExc_TypeError, "callback parameter must be callable");
|
||||
return NULL;
|
||||
}
|
||||
Py_INCREF(callback);
|
||||
@@ -777,6 +801,87 @@ milter_wrap_abort(SMFICTX *ctx) {
|
||||
return generic_noarg_wrapper(ctx,abort_callback);
|
||||
}
|
||||
|
||||
#ifdef SMFIS_ALL_OPTS
|
||||
static PyObject *unknown_callback = NULL;
|
||||
static PyObject *data_callback = NULL;
|
||||
static PyObject *negotiate_callback = NULL;
|
||||
|
||||
static int
|
||||
milter_wrap_unknown(SMFICTX *ctx, const char *cmd) {
|
||||
PyObject *arglist;
|
||||
milter_ContextObject *c;
|
||||
|
||||
if (unknown_callback == NULL) return SMFIS_CONTINUE;
|
||||
c = _get_context(ctx);
|
||||
if (!c) return SMFIS_TEMPFAIL;
|
||||
arglist = Py_BuildValue("(Os)", c, cmd);
|
||||
return _generic_wrapper(c, unknown_callback, arglist);
|
||||
}
|
||||
|
||||
static int
|
||||
milter_wrap_data(SMFICTX *ctx) {
|
||||
return generic_noarg_wrapper(ctx,data_callback);
|
||||
}
|
||||
|
||||
static int
|
||||
milter_wrap_negotiate(SMFICTX *ctx,
|
||||
unsigned long f0,
|
||||
unsigned long f1,
|
||||
unsigned long f2,
|
||||
unsigned long f3,
|
||||
unsigned long *pf0,
|
||||
unsigned long *pf1,
|
||||
unsigned long *pf2,
|
||||
unsigned long *pf3) {
|
||||
PyObject *arglist, *optlist;
|
||||
milter_ContextObject *c;
|
||||
int rc;
|
||||
|
||||
if (negotiate_callback == NULL) return SMFIS_ALL_OPTS;
|
||||
c = _get_context(ctx);
|
||||
if (!c)
|
||||
return SMFIS_REJECT; // do not contact us again for current connection
|
||||
optlist = Py_BuildValue("[kkkk]",f0,f1,f2,f3);
|
||||
if (optlist == NULL)
|
||||
arglist = NULL;
|
||||
else
|
||||
arglist = Py_BuildValue("(OO)", c, optlist);
|
||||
PyThreadState *t = c->t;
|
||||
c->t = 0; // do not release thread in _generic_wrapper
|
||||
rc = _generic_wrapper(c, negotiate_callback, arglist);
|
||||
c->t = t;
|
||||
if (rc == SMFIS_CONTINUE) {
|
||||
#if 0 // PyArgs_Parse deprecated and going away
|
||||
if (!PyArgs_Parse(optlist,"[kkkk]",pf0,pf1,pf2,pf3)) {
|
||||
PyErr_Print();
|
||||
PyErr_Clear(); /* must clear since not returning to python */
|
||||
rc = SMFIS_REJECT;
|
||||
}
|
||||
#else
|
||||
unsigned long *pa[4] = { pf0,pf1,pf2,pf3 };
|
||||
unsigned long fa[4] = { f0,f1,f2,f3 };
|
||||
int len = PyList_Size(optlist);
|
||||
int i;
|
||||
for (i = 0; i < 4; ++i) {
|
||||
*pa[i] = (i <= len)
|
||||
? PyInt_AsUnsignedLongMask(PyList_GET_ITEM(optlist,i))
|
||||
: fa[i];
|
||||
}
|
||||
if (PyErr_Occurred()) {
|
||||
PyErr_Print();
|
||||
PyErr_Clear();
|
||||
rc = SMFIS_REJECT;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
else if (rc != SMFIS_ALL_OPTS)
|
||||
rc = SMFIS_REJECT;
|
||||
Py_DECREF(optlist);
|
||||
_release_thread(t);
|
||||
return rc;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int
|
||||
milter_wrap_close(SMFICTX *ctx) {
|
||||
/* xxfi_close can be called out of order - even before connect.
|
||||
@@ -808,15 +913,61 @@ milter_wrap_close(SMFICTX *ctx) {
|
||||
}
|
||||
|
||||
static char milter_register__doc__[] =
|
||||
"register(name) -> None\n\
|
||||
"register(name,unknown=,data=,negotiate=) -> None\n\
|
||||
Registers the milter name with current callbacks, and flags.\n\
|
||||
Required before main() is called.";
|
||||
|
||||
static PyObject *
|
||||
milter_register(PyObject *self, PyObject *args) {
|
||||
if (!PyArg_ParseTuple(args, "s:register", &description.xxfi_name))
|
||||
milter_register(PyObject *self, PyObject *args, PyObject *kwds) {
|
||||
static char *kwlist[] = { "name","unknown","data","negotiate", NULL };
|
||||
static PyObject** const cbp[3] =
|
||||
{ &unknown_callback, &data_callback, &negotiate_callback };
|
||||
PyObject *cb[3] = { NULL, NULL, NULL };
|
||||
int i;
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|OOO:register", kwlist,
|
||||
&description.xxfi_name, &cb[0],&cb[1],&cb[2]))
|
||||
return NULL;
|
||||
for (i = 0; i < 3; ++i) {
|
||||
PyObject *callback = cb[i];
|
||||
if (callback != NULL && callback != Py_None) {
|
||||
if (!PyCallable_Check(callback)) {
|
||||
char err[80];
|
||||
sprintf(err,"%s parameter must be callable",kwlist[i]);
|
||||
PyErr_SetString(PyExc_TypeError, err);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (i = 0; i < 3; ++i) {
|
||||
PyObject *callback = cb[i];
|
||||
if (callback != NULL) { // if keyword specified
|
||||
if (callback == Py_None) {
|
||||
callback = NULL;
|
||||
}
|
||||
else {
|
||||
Py_INCREF(callback);
|
||||
}
|
||||
PyObject *oldval = *cbp[i];
|
||||
*cbp[i] = callback;
|
||||
if (oldval) {
|
||||
Py_DECREF(oldval);
|
||||
}
|
||||
}
|
||||
}
|
||||
return _generic_return(smfi_register(description), "cannot register");
|
||||
}
|
||||
|
||||
static char milter_opensocket__doc__[] =
|
||||
"opensocket(rmsock) -> None\n\
|
||||
Attempts to create and open the socket provided with setconn.\n\
|
||||
Removes the socket first if rmsock is True.";
|
||||
|
||||
static PyObject *
|
||||
milter_opensocket(PyObject *self, PyObject *args) {
|
||||
char rmsock = 0;
|
||||
if (!PyArg_ParseTuple(args, "b:opensocket", &rmsock))
|
||||
return NULL;
|
||||
return _generic_return(smfi_register(description), "cannot register");
|
||||
return _generic_return(smfi_opensocket(rmsock), "cannot opensocket");
|
||||
}
|
||||
|
||||
static char milter_main__doc__[] =
|
||||
@@ -1094,7 +1245,7 @@ milter_addrcpt(PyObject *self, PyObject *args) {
|
||||
PyThreadState *t;
|
||||
int rc;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "s|z:addrcpt", &rcpt)) return NULL;
|
||||
if (!PyArg_ParseTuple(args, "s|z:addrcpt", &rcpt, ¶ms)) return NULL;
|
||||
ctx = _find_context(self);
|
||||
if (ctx == NULL) return NULL;
|
||||
t = PyEval_SaveThread();
|
||||
@@ -1124,8 +1275,7 @@ milter_delrcpt(PyObject *self, PyObject *args) {
|
||||
ctx = _find_context(self);
|
||||
if (ctx == NULL) return NULL;
|
||||
t = PyEval_SaveThread();
|
||||
return _thread_return(t,smfi_delrcpt(ctx, rcpt),
|
||||
"cannot delete recipient");
|
||||
return _thread_return(t,smfi_delrcpt(ctx, rcpt), "cannot delete recipient");
|
||||
}
|
||||
|
||||
static char milter_replacebody__doc__[] =
|
||||
@@ -1146,8 +1296,8 @@ milter_replacebody(PyObject *self, PyObject *args) {
|
||||
ctx = _find_context(self);
|
||||
if (ctx == NULL) return NULL;
|
||||
t = PyEval_SaveThread();
|
||||
return _thread_return(t,smfi_replacebody(ctx, bodyp, bodylen),
|
||||
"cannot replace message body");
|
||||
return _thread_return(t,smfi_replacebody(ctx,
|
||||
(unsigned char *)bodyp, bodylen), "cannot replace message body");
|
||||
}
|
||||
|
||||
static char milter_setpriv__doc__[] =
|
||||
@@ -1232,6 +1382,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__},
|
||||
@@ -1250,6 +1421,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 }
|
||||
};
|
||||
@@ -1272,7 +1446,12 @@ static struct smfiDesc description = { /* Set some reasonable defaults */
|
||||
milter_wrap_body,
|
||||
milter_wrap_eom,
|
||||
milter_wrap_abort,
|
||||
milter_wrap_close
|
||||
milter_wrap_close,
|
||||
#ifdef SMFIS_ALL_OPTS
|
||||
milter_wrap_unknown,
|
||||
milter_wrap_data,
|
||||
milter_wrap_negotiate
|
||||
#endif
|
||||
};
|
||||
|
||||
static PyMethodDef milter_methods[] = {
|
||||
@@ -1287,9 +1466,9 @@ static PyMethodDef milter_methods[] = {
|
||||
{ "set_eom_callback", milter_set_eom_callback, METH_VARARGS, milter_set_eom_callback__doc__},
|
||||
{ "set_abort_callback", milter_set_abort_callback, METH_VARARGS, milter_set_abort_callback__doc__},
|
||||
{ "set_close_callback", milter_set_close_callback, METH_VARARGS, milter_set_close_callback__doc__},
|
||||
{ "set_exception_policy", milter_set_exception_policy,METH_VARARGS, milter_set_exception_policy__doc__},
|
||||
{ "register", milter_register, METH_VARARGS, milter_register__doc__},
|
||||
{ "register", milter_register, METH_VARARGS, milter_register__doc__},
|
||||
{ "set_exception_policy", milter_set_exception_policy, METH_VARARGS, milter_set_exception_policy__doc__},
|
||||
{ "register", (PyCFunction)milter_register,METH_VARARGS|METH_KEYWORDS, milter_register__doc__},
|
||||
{ "opensocket", milter_opensocket, METH_VARARGS, milter_opensocket__doc__},
|
||||
{ "main", milter_main, METH_VARARGS, milter_main__doc__},
|
||||
{ "setdbg", milter_setdbg, METH_VARARGS, milter_setdbg__doc__},
|
||||
{ "settimeout", milter_settimeout, METH_VARARGS, milter_settimeout__doc__},
|
||||
@@ -1364,6 +1543,35 @@ initmilter(void) {
|
||||
#endif
|
||||
#ifdef SMFIF_CHGFROM
|
||||
setitem(d,"CHGFROM",SMFIF_CHGFROM);
|
||||
#endif
|
||||
#ifdef SMFIF_SETSMLIST
|
||||
setitem(d,"SETSMLIST",SMFIF_SETSMLIST);
|
||||
#endif
|
||||
#ifdef SMFIS_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_NOCONNECT);
|
||||
setitem(d,"P_NOHELO",SMFIP_NOHELO);
|
||||
setitem(d,"P_NOMAIL",SMFIP_NOMAIL);
|
||||
setitem(d,"P_NORCPT",SMFIP_NORCPT);
|
||||
setitem(d,"P_NODATA",SMFIP_NODATA);
|
||||
setitem(d,"P_NOUNKNOWN",SMFIP_NOUNKNOWN);
|
||||
setitem(d,"P_NOEOH",SMFIP_NOEOH);
|
||||
setitem(d,"P_NOBODY",SMFIP_NOBODY);
|
||||
setitem(d,"P_NOHDRS",SMFIP_NOHDRS);
|
||||
setitem(d,"P_HDR_LEADSPC",SMFIP_HDR_LEADSPC);
|
||||
setitem(d,"P_SKIP",SMFIP_SKIP);
|
||||
setitem(d,"ALL_OPTS",SMFIS_ALL_OPTS);
|
||||
setitem(d,"SKIP",SMFIS_SKIP);
|
||||
setitem(d,"NOREPLY",SMFIS_NOREPLY);
|
||||
#endif
|
||||
setitem(d,"CONTINUE", SMFIS_CONTINUE);
|
||||
setitem(d,"REJECT", SMFIS_REJECT);
|
||||
|
||||
+74
-39
@@ -1,23 +1,32 @@
|
||||
# EL 3,4,5 supported, set to 0 for Fedora
|
||||
%define el4 1
|
||||
%define dist .el4
|
||||
%if 0%{?el3} || 0%{?el4}
|
||||
%define __python python2.4
|
||||
%define version 0.9.0
|
||||
%define release 1.el4
|
||||
%endif
|
||||
|
||||
%define libdir %{_libdir}/pymilter
|
||||
%define name pymilter
|
||||
%define redhat7 0
|
||||
%{!?python_sitearch: %define python_sitearch %(%{__python} -c "from distutils.sysconfig import get_python_lib; print get_python_lib(1)")}
|
||||
%define pythonbase %(basename %{__python})
|
||||
|
||||
Summary: Python interface to sendmail milter API
|
||||
Name: %{name}
|
||||
Version: %{version}
|
||||
Release: %{release}
|
||||
Source: %{name}-%{version}.tar.gz
|
||||
#Patch: %{name}-%{version}.patch
|
||||
Name: pymilter
|
||||
Version: 0.9.2
|
||||
Release: 3%{dist}
|
||||
Source: http://downloads.sourceforge.net/pymilter/%{name}-%{version}.tar.gz
|
||||
License: GPLv2+
|
||||
Group: Development/Libraries
|
||||
BuildRoot: %{_tmppath}/%{name}-buildroot
|
||||
Vendor: Stuart D. Gathman <stuart@bmsi.com>
|
||||
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root
|
||||
Url: http://www.bmsi.com/python/milter.html
|
||||
Requires: %{__python} >= 2.4, sendmail >= 8.13
|
||||
BuildRequires: %{__python}-devel >= 2.4, sendmail-devel >= 8.13
|
||||
Requires: %{pythonbase} >= 2.4, sendmail >= 8.13
|
||||
%if 0%{?el3} || 0%{?el4}
|
||||
# Need python2.4 specific pydns, not the version for system python
|
||||
Requires: pydns
|
||||
%else
|
||||
# Needed for callbacks, not a core function but highly useful for milters
|
||||
Requires: python-pydns
|
||||
%endif
|
||||
BuildRequires: ed, %{pythonbase}-devel >= 2.4, sendmail-devel >= 8.13
|
||||
|
||||
%description
|
||||
This is a python extension module to enable python scripts to
|
||||
@@ -27,30 +36,30 @@ DSNs, and doing CBV.
|
||||
|
||||
%prep
|
||||
%setup -q
|
||||
#patch -p0 -b .bms
|
||||
|
||||
%build
|
||||
%if %{redhat7}
|
||||
LDFLAGS="-s"
|
||||
%else # Redhat builds debug packages after 7.3
|
||||
LDFLAGS="-g"
|
||||
%endif
|
||||
env CFLAGS="$RPM_OPT_FLAGS" LDFLAGS="$LDFLAGS" %{__python} setup.py build
|
||||
env CFLAGS="$RPM_OPT_FLAGS" %{__python} setup.py build
|
||||
|
||||
%install
|
||||
rm -rf $RPM_BUILD_ROOT
|
||||
%{__python} setup.py install --root=$RPM_BUILD_ROOT --record=INSTALLED_FILES
|
||||
mkdir -p $RPM_BUILD_ROOT/var/run/milter
|
||||
%{__python} setup.py install --root=$RPM_BUILD_ROOT
|
||||
mkdir -p $RPM_BUILD_ROOT%{_localstatedir}/run/milter
|
||||
mkdir -p $RPM_BUILD_ROOT%{_localstatedir}/log/milter
|
||||
mkdir -p $RPM_BUILD_ROOT%{libdir}
|
||||
%ifos aix4.1
|
||||
cat >$RPM_BUILD_ROOT%{libdir}/start.sh <<'EOF'
|
||||
#!/bin/sh
|
||||
cd /var/log/milter
|
||||
exec /usr/local/bin/python bms.py >>milter.log 2>&1
|
||||
EOF
|
||||
%else # not aix4.1
|
||||
cp start.sh $RPM_BUILD_ROOT%{libdir}
|
||||
ed $RPM_BUILD_ROOT%{libdir}/start.sh <<'EOF'
|
||||
/^datadir=/
|
||||
c
|
||||
datadir="%{_localstatedir}/log/milter"
|
||||
.
|
||||
/^piddir=/
|
||||
c
|
||||
piddir="%{_localstatedir}/run/milter"
|
||||
.
|
||||
/^libdir=/
|
||||
c
|
||||
libdir="%{libdir}"
|
||||
.
|
||||
/^python=/
|
||||
c
|
||||
python="%{__python}"
|
||||
@@ -58,43 +67,69 @@ python="%{__python}"
|
||||
w
|
||||
q
|
||||
EOF
|
||||
%endif
|
||||
chmod a+x $RPM_BUILD_ROOT%{libdir}/start.sh
|
||||
%if !%{redhat7}
|
||||
#grep '.pyc$' INSTALLED_FILES | sed -e 's/c$/o/' >>INSTALLED_FILES
|
||||
%endif
|
||||
|
||||
# start.sh is used by spfmilter and milter, and could be used by
|
||||
# other milters running on redhat
|
||||
%files -f INSTALLED_FILES
|
||||
%defattr(-,root,root)
|
||||
# start.sh is used by spfmilter, srsmilter, and milter, and could be used by
|
||||
# other milters using pymilter.
|
||||
%files
|
||||
%defattr(-,root,root,-)
|
||||
%doc README ChangeLog NEWS TODO CREDITS sample.py milter-template.py
|
||||
%config %{libdir}/start.sh
|
||||
%dir %attr(0755,mail,mail) /var/run/milter
|
||||
%{python_sitearch}/*
|
||||
%{libdir}
|
||||
%dir %attr(0755,mail,mail) %{_localstatedir}/run/milter
|
||||
%dir %attr(0755,mail,mail) %{_localstatedir}/log/milter
|
||||
|
||||
%clean
|
||||
rm -rf $RPM_BUILD_ROOT
|
||||
|
||||
%changelog
|
||||
* Tue Jun 02 2009 Stuart Gathman <stuart@bmsi.com> 0.9.2-3
|
||||
- Change result of @noreply callbacks to NOREPLY when so negotiated.
|
||||
|
||||
* Tue Jun 02 2009 Stuart Gathman <stuart@bmsi.com> 0.9.2-2
|
||||
- Cache callback negotiation
|
||||
|
||||
* 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
|
||||
|
||||
* Wed Jan 07 2009 Stuart Gathman <stuart@bmsi.com> 0.9.0-4
|
||||
- Stop using INSTALLED_FILES to make Fedora happy
|
||||
- Remove config flag from start.sh glue
|
||||
- Own /var/log/milter
|
||||
- Use _localstatedir
|
||||
|
||||
* Wed Jan 07 2009 Stuart Gathman <stuart@bmsi.com> 0.9.0-2
|
||||
- Changes to meet Fedora standards
|
||||
|
||||
* Mon Nov 24 2008 Stuart Gathman <stuart@bmsi.com> 0.9.0-1
|
||||
- Split pymilter into its own CVS module
|
||||
- Support chgfrom and addrcpt_par
|
||||
- Support NS records in Milter.dns
|
||||
|
||||
* Mon Aug 25 2008 Stuart Gathman <stuart@bmsi.com> 0.8.10-2
|
||||
- /var/run/milter directory must be owned by mail
|
||||
|
||||
* Mon Aug 25 2008 Stuart Gathman <stuart@bmsi.com> 0.8.10-1
|
||||
- improved parsing into email and fullname (still 2 self test failures)
|
||||
- implement no-DSN CBV, reduce full DSNs
|
||||
|
||||
* Mon Sep 24 2007 Stuart Gathman <stuart@bmsi.com> 0.8.9-1
|
||||
- Use ifarch hack to build milter and milter-spf packages as noarch
|
||||
- Remove spf dependency from dsn.py, add dns.py
|
||||
|
||||
* Fri Jan 05 2007 Stuart Gathman <stuart@bmsi.com> 0.8.8-1
|
||||
- move AddrCache, parse_addr, iniplist to Milter package
|
||||
- move parse_header to Milter.utils
|
||||
- fix plock for missing source and can't change owner/group
|
||||
- split out pymilter and pymilter-spf packages
|
||||
- move milter apps to /usr/lib/pymilter
|
||||
|
||||
* Sat Nov 04 2006 Stuart Gathman <stuart@bmsi.com> 0.8.7-1
|
||||
- SPF moved to pyspf RPM
|
||||
|
||||
* Tue May 23 2006 Stuart Gathman <stuart@bmsi.com> 0.8.6-2
|
||||
- Support CBV timeout
|
||||
|
||||
@@ -2,10 +2,11 @@ import os
|
||||
import sys
|
||||
from distutils.core import setup, Extension
|
||||
|
||||
# FIXME: on some versions of sendmail, smutil is renamed to sm
|
||||
# on slackware and debian, leave it out entirely. It depends
|
||||
# FIXME: on some versions of sendmail, smutil is renamed to sm.
|
||||
# On slackware and debian, leave it out entirely. It depends
|
||||
# on how libmilter was built by the sendmail package.
|
||||
libs = ["milter", "smutil"]
|
||||
#libs = ["milter", "smutil"]
|
||||
libs = ["milter"]
|
||||
libdirs = ["/usr/lib/libmilter"] # needed for Debian
|
||||
|
||||
# patch distutils if it can't cope with the "classifiers" or
|
||||
@@ -16,7 +17,7 @@ if sys.version < '2.2.3':
|
||||
DistributionMetadata.download_url = None
|
||||
|
||||
# NOTE: importing Milter to obtain version fails when milter.so not built
|
||||
setup(name = "pymilter", version = '0.9.0',
|
||||
setup(name = "pymilter", version = '0.9.2',
|
||||
description="Python interface to sendmail milter API",
|
||||
long_description="""\
|
||||
This is a python extension module to enable python scripts to
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
#!/bin/sh
|
||||
appname="$1"
|
||||
script="${2:-${appname}}"
|
||||
datadir=/var/log/milter
|
||||
datadir="/var/log/milter"
|
||||
piddir="/var/run/milter"
|
||||
libdir="/usr/lib/pymilter"
|
||||
python="python2.4"
|
||||
exec >>${datadir}/${appname}.log 2>&1
|
||||
if test -s ${datadir}/${script}.py; then
|
||||
cd ${datadir} # use version in log dir if it exists for debugging
|
||||
else
|
||||
cd /usr/lib/pymilter
|
||||
cd ${libdir}
|
||||
fi
|
||||
|
||||
${python} ${script}.py &
|
||||
echo $! >/var/run/milter/${appname}.pid
|
||||
echo $! >${piddir}/${appname}.pid
|
||||
|
||||
Reference in New Issue
Block a user