Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 42e7a02638 | |||
| bc9d8c622b | |||
| 2fa952e108 | |||
| 381e906b6a | |||
| 207278479f | |||
| a0bd76cded | |||
| 8e96c23ddc | |||
| 5ec4e2b34d | |||
| 28c3a6afd6 | |||
| 36df47f019 | |||
| e5c03665e9 | |||
| ea9ca0c12a | |||
| fb1da3b12b | |||
| 74d33126b5 |
@@ -2,3 +2,7 @@
|
|||||||
build/
|
build/
|
||||||
test/*.out
|
test/*.out
|
||||||
test/*.tstout
|
test/*.tstout
|
||||||
|
test/*.log
|
||||||
|
test.db
|
||||||
|
dist
|
||||||
|
MANIFEST
|
||||||
|
|||||||
+36
-5
@@ -9,7 +9,7 @@
|
|||||||
# This code is under the GNU General Public License. See COPYING for details.
|
# This code is under the GNU General Public License. See COPYING for details.
|
||||||
|
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
__version__ = '1.0.1'
|
__version__ = '1.0.2'
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
@@ -48,6 +48,12 @@ OPTIONAL_CALLBACKS = {
|
|||||||
'header':(P_NR_HDR,P_NOHDRS)
|
'header':(P_NR_HDR,P_NOHDRS)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MACRO_CALLBACKS = {
|
||||||
|
'connect': M_CONNECT,
|
||||||
|
'hello': M_HELO, 'envfrom': M_ENVFROM, 'envrcpt': M_ENVRCPT,
|
||||||
|
'data': M_DATA, 'eom': M_EOM, 'eoh': M_EOH
|
||||||
|
}
|
||||||
|
|
||||||
## @private
|
## @private
|
||||||
R = re.compile(r'%+')
|
R = re.compile(r'%+')
|
||||||
|
|
||||||
@@ -141,6 +147,7 @@ def nocallback(func):
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
'@nocallback applied to non-optional method: '+func.__name__)
|
'@nocallback applied to non-optional method: '+func.__name__)
|
||||||
|
@wraps(func)
|
||||||
def wrapper(self,*args):
|
def wrapper(self,*args):
|
||||||
if func(self,*args) != CONTINUE:
|
if func(self,*args) != CONTINUE:
|
||||||
raise RuntimeError('%s return code must be CONTINUE with @nocallback'
|
raise RuntimeError('%s return code must be CONTINUE with @nocallback'
|
||||||
@@ -173,6 +180,21 @@ def noreply(func):
|
|||||||
wrapper.milter_protocol = nr_mask
|
wrapper.milter_protocol = nr_mask
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
|
## Function decorator to set macros used in a callback.
|
||||||
|
# By default, the MTA sends all macros defined for a callback.
|
||||||
|
# If some or all of these are unused, the bandwidth can be saved
|
||||||
|
# by listing the ones that are used.
|
||||||
|
# @since 1.0.2
|
||||||
|
def symlist(*syms):
|
||||||
|
if len(syms) > 5:
|
||||||
|
raise ValueError('@symlist limited to 5 macros by MTA: '+func.__name__)
|
||||||
|
def setsyms(func):
|
||||||
|
if func.__name__ not in MACRO_CALLBACKS:
|
||||||
|
raise ValueError('@symlist applied to non-symlist method: '+func.__name__)
|
||||||
|
func._symlist = syms
|
||||||
|
return func
|
||||||
|
return setsyms
|
||||||
|
|
||||||
## Disabled action exception.
|
## Disabled action exception.
|
||||||
# set_flags() can tell the MTA that this application will not use certain
|
# set_flags() can tell the MTA that this application will not use certain
|
||||||
# features (such as CHGFROM). This can also be negotiated for each
|
# features (such as CHGFROM). This can also be negotiated for each
|
||||||
@@ -393,6 +415,11 @@ class Base(object):
|
|||||||
def negotiate(self,opts):
|
def negotiate(self,opts):
|
||||||
try:
|
try:
|
||||||
self._actions,p,f1,f2 = opts
|
self._actions,p,f1,f2 = opts
|
||||||
|
for func,stage in MACRO_CALLBACKS.items():
|
||||||
|
func = getattr(self,func)
|
||||||
|
syms = getattr(func,'_symlist',None)
|
||||||
|
if syms is not None:
|
||||||
|
self.setsymlist(stage,syms)
|
||||||
opts[1] = self._protocol = p & ~self.protocol_mask()
|
opts[1] = self._protocol = p & ~self.protocol_mask()
|
||||||
opts[2] = 0
|
opts[2] = 0
|
||||||
opts[3] = 0
|
opts[3] = 0
|
||||||
@@ -443,23 +470,27 @@ class Base(object):
|
|||||||
# set. The protocol stages are M_CONNECT, M_HELO, M_ENVFROM, M_ENVRCPT,
|
# set. The protocol stages are M_CONNECT, M_HELO, M_ENVFROM, M_ENVRCPT,
|
||||||
# M_DATA, M_EOM, M_EOH.
|
# M_DATA, M_EOM, M_EOH.
|
||||||
#
|
#
|
||||||
# May only be called from negotiate callback.
|
# May only be called from negotiate callback. Hence, this is an advanced
|
||||||
|
# feature. Use the @@symlist function decorator to conviently set
|
||||||
|
# the macros used by a callback.
|
||||||
# @since 0.9.8, previous version was misspelled!
|
# @since 0.9.8, previous version was misspelled!
|
||||||
# @param stage the protocol stage to set to macro list for,
|
# @param stage the protocol stage to set to macro list for,
|
||||||
# one of the M_* constants defined in Milter
|
# one of the M_* constants defined in Milter
|
||||||
# @param macros space separated and/or lists of strings
|
# @param macros space separated and/or lists of strings
|
||||||
def setsymlist(self,stage,*macros):
|
def setsymlist(self,stage,*macros):
|
||||||
if not self._actions & SETSYMLIST: raise DisabledAction("SETSYMLIST")
|
if not self._actions & SETSYMLIST: raise DisabledAction("SETSYMLIST")
|
||||||
|
if len(macros) > 5:
|
||||||
|
raise ValueError('setsymlist limited to 5 macros by MTA')
|
||||||
a = []
|
a = []
|
||||||
for m in macros:
|
for m in macros:
|
||||||
try:
|
try:
|
||||||
m = m.encode('utf8')
|
m = m.encode('utf8')
|
||||||
except: pass
|
except: pass
|
||||||
try:
|
try:
|
||||||
m = m.split(' ')
|
m = m.split(b' ')
|
||||||
except: pass
|
|
||||||
a += m
|
a += m
|
||||||
return self._ctx.setsmlist(stage,' '.join(a))
|
except: pass
|
||||||
|
return self._ctx.setsymlist(stage,b' '.join(a))
|
||||||
|
|
||||||
# Milter methods which can only be called from eom callback.
|
# Milter methods which can only be called from eom callback.
|
||||||
|
|
||||||
|
|||||||
+42
-4
@@ -40,6 +40,9 @@ class TestBase(object):
|
|||||||
self._reply = None
|
self._reply = None
|
||||||
## The rfc822 message object for the current email being fed to the %milter.
|
## The rfc822 message object for the current email being fed to the %milter.
|
||||||
self._msg = None
|
self._msg = None
|
||||||
|
## The protocol stage for macros returned
|
||||||
|
self._stage = None
|
||||||
|
## The macros returned by protocol stage
|
||||||
self._symlist = [ None, None, None, None, None, None, None ]
|
self._symlist = [ None, None, None, None, None, None, None ]
|
||||||
|
|
||||||
def log(self,*msg):
|
def log(self,*msg):
|
||||||
@@ -54,8 +57,12 @@ class TestBase(object):
|
|||||||
self._macros[name] = val
|
self._macros[name] = val
|
||||||
|
|
||||||
def getsymval(self,name):
|
def getsymval(self,name):
|
||||||
# FIXME: track stage, and use _symlist
|
stage = self._stage
|
||||||
return self._macros.get(name,'')
|
if stage >= 0:
|
||||||
|
syms = self._symlist[stage]
|
||||||
|
if syms is not None and name not in syms:
|
||||||
|
return None
|
||||||
|
return self._macros.get(name,None)
|
||||||
|
|
||||||
def replacebody(self,chunk):
|
def replacebody(self,chunk):
|
||||||
if self._body:
|
if self._body:
|
||||||
@@ -113,7 +120,10 @@ class TestBase(object):
|
|||||||
self._reply = (rcode,xcode) + msg
|
self._reply = (rcode,xcode) + msg
|
||||||
|
|
||||||
def setsymlist(self,stage,macros):
|
def setsymlist(self,stage,macros):
|
||||||
if not self._actions & SETSYMLIST: raise DisabledAction("SETSYMLIST")
|
if not self._actions & Milter.SETSYMLIST:
|
||||||
|
raise DisabledAction("SETSYMLIST")
|
||||||
|
if self._stage != -1:
|
||||||
|
raise RuntimeError("setsymlist may only be called from negotiate")
|
||||||
# not used yet, but just for grins we save the data
|
# not used yet, but just for grins we save the data
|
||||||
a = []
|
a = []
|
||||||
for m in macros:
|
for m in macros:
|
||||||
@@ -121,9 +131,13 @@ class TestBase(object):
|
|||||||
m = m.encode('utf8')
|
m = m.encode('utf8')
|
||||||
except: pass
|
except: pass
|
||||||
try:
|
try:
|
||||||
m = m.split(' ')
|
m = m.split(b' ')
|
||||||
except: pass
|
except: pass
|
||||||
a += m
|
a += m
|
||||||
|
if len(a) > 5:
|
||||||
|
raise ValueError('setsymlist limited to 5 macros by MTA')
|
||||||
|
if self._symlist[stage] is not None:
|
||||||
|
raise ValueError('setsymlist already called for stage:'+stage)
|
||||||
self._symlist[stage] = set(a)
|
self._symlist[stage] = set(a)
|
||||||
|
|
||||||
## Feed a file like object to the %milter. Calls envfrom, envrcpt for
|
## Feed a file like object to the %milter. Calls envfrom, envrcpt for
|
||||||
@@ -144,16 +158,32 @@ class TestBase(object):
|
|||||||
self._reply = None
|
self._reply = None
|
||||||
self._sender = '<%s>'%sender
|
self._sender = '<%s>'%sender
|
||||||
msg = mime.message_from_file(fp)
|
msg = mime.message_from_file(fp)
|
||||||
|
# envfrom
|
||||||
|
self._stage = Milter.M_ENVFROM
|
||||||
rc = self.envfrom(self._sender)
|
rc = self.envfrom(self._sender)
|
||||||
|
self._stage = None
|
||||||
if rc != Milter.CONTINUE: return rc
|
if rc != Milter.CONTINUE: return rc
|
||||||
|
# envrcpt
|
||||||
for rcpt in (rcpt,) + rcpts:
|
for rcpt in (rcpt,) + rcpts:
|
||||||
|
self._stage = Milter.M_ENVRCPT
|
||||||
rc = self.envrcpt('<%s>'%rcpt)
|
rc = self.envrcpt('<%s>'%rcpt)
|
||||||
|
self._stage = None
|
||||||
if rc != Milter.CONTINUE: return rc
|
if rc != Milter.CONTINUE: return rc
|
||||||
|
# data
|
||||||
|
self._stage = Milter.M_DATA
|
||||||
|
rc = self.data()
|
||||||
|
self._stage = None
|
||||||
|
if rc != Milter.CONTINUE: return rc
|
||||||
|
# header
|
||||||
for h,val in msg.items():
|
for h,val in msg.items():
|
||||||
rc = self.header(h,val)
|
rc = self.header(h,val)
|
||||||
if rc != Milter.CONTINUE: return rc
|
if rc != Milter.CONTINUE: return rc
|
||||||
|
# eoh
|
||||||
|
self._stage = Milter.M_EOH
|
||||||
rc = self.eoh()
|
rc = self.eoh()
|
||||||
|
self._stage = None
|
||||||
if rc != Milter.CONTINUE: return rc
|
if rc != Milter.CONTINUE: return rc
|
||||||
|
# body
|
||||||
header,body = msg.as_bytes().split(b'\n\n',1)
|
header,body = msg.as_bytes().split(b'\n\n',1)
|
||||||
bfp = BytesIO(body)
|
bfp = BytesIO(body)
|
||||||
while 1:
|
while 1:
|
||||||
@@ -163,7 +193,9 @@ class TestBase(object):
|
|||||||
if rc != Milter.CONTINUE: return rc
|
if rc != Milter.CONTINUE: return rc
|
||||||
self._msg = msg
|
self._msg = msg
|
||||||
self._body = BytesIO()
|
self._body = BytesIO()
|
||||||
|
self._stage = Milter.M_EOM
|
||||||
rc = self.eom()
|
rc = self.eom()
|
||||||
|
self._stage = None
|
||||||
if self._bodyreplaced:
|
if self._bodyreplaced:
|
||||||
body = self._body.getvalue()
|
body = self._body.getvalue()
|
||||||
self._body = BytesIO()
|
self._body = BytesIO()
|
||||||
@@ -188,13 +220,19 @@ class TestBase(object):
|
|||||||
def connect(self,host='localhost',helo='spamrelay',ip='1.2.3.4'):
|
def connect(self,host='localhost',helo='spamrelay',ip='1.2.3.4'):
|
||||||
self._body = None
|
self._body = None
|
||||||
self._bodyreplaced = False
|
self._bodyreplaced = False
|
||||||
|
self._setctx(None)
|
||||||
opts = [ Milter.CURR_ACTS,~0,0,0 ]
|
opts = [ Milter.CURR_ACTS,~0,0,0 ]
|
||||||
|
self._stage = -1
|
||||||
rc = self.negotiate(opts)
|
rc = self.negotiate(opts)
|
||||||
|
self._stage = Milter.M_CONNECT
|
||||||
rc = super(TestBase,self).connect(host,1,(ip,1234))
|
rc = super(TestBase,self).connect(host,1,(ip,1234))
|
||||||
if rc != Milter.CONTINUE:
|
if rc != Milter.CONTINUE:
|
||||||
|
self._stage = None
|
||||||
self.close()
|
self.close()
|
||||||
return rc
|
return rc
|
||||||
|
self._stage = Milter.M_HELO
|
||||||
rc = self.hello(helo)
|
rc = self.hello(helo)
|
||||||
|
self._stage = None
|
||||||
if rc != Milter.CONTINUE:
|
if rc != Milter.CONTINUE:
|
||||||
self.close()
|
self.close()
|
||||||
return rc
|
return rc
|
||||||
|
|||||||
+1
-3
@@ -70,7 +70,7 @@ def iniplist(ipaddr,iplist):
|
|||||||
True
|
True
|
||||||
>>> iniplist('4.2.2.2',['b.resolvers.Level3.net'])
|
>>> iniplist('4.2.2.2',['b.resolvers.Level3.net'])
|
||||||
True
|
True
|
||||||
>>> iniplist('2607:f8b0:4004:801::',['google.com/40'])
|
>>> iniplist('2606:2800:220:1::',['example.com/40'])
|
||||||
True
|
True
|
||||||
>>> iniplist('4.2.2.2',['nothing.example.com'])
|
>>> iniplist('4.2.2.2',['nothing.example.com'])
|
||||||
False
|
False
|
||||||
@@ -134,8 +134,6 @@ def parseaddr(t):
|
|||||||
('God@heaven', 'jeff@spec.org')
|
('God@heaven', 'jeff@spec.org')
|
||||||
>>> parseaddr('Real Name ((comment)) <addr...@example.com>')
|
>>> parseaddr('Real Name ((comment)) <addr...@example.com>')
|
||||||
('Real Name (comment)', 'addr...@example.com')
|
('Real Name (comment)', 'addr...@example.com')
|
||||||
>>> parseaddr('a(WRONG)@b')
|
|
||||||
('WRONG', 'a@b')
|
|
||||||
"""
|
"""
|
||||||
#return email.utils.parseaddr(t)
|
#return email.utils.parseaddr(t)
|
||||||
res = email.utils.parseaddr(t)
|
res = email.utils.parseaddr(t)
|
||||||
|
|||||||
@@ -4,14 +4,11 @@ web:
|
|||||||
rsync -ravKk doc/html/ spidey2.bmsi.com:/Public/pymilter
|
rsync -ravKk doc/html/ spidey2.bmsi.com:/Public/pymilter
|
||||||
cd doc/html; zip -r ../../doc .
|
cd doc/html; zip -r ../../doc .
|
||||||
|
|
||||||
VERSION=1.0
|
VERSION=1.0.2
|
||||||
CVSTAG=pymilter-1_0
|
|
||||||
PKG=pymilter-$(VERSION)
|
PKG=pymilter-$(VERSION)
|
||||||
SRCTAR=$(PKG).tar.gz
|
SRCTAR=$(PKG).tar.gz
|
||||||
|
|
||||||
$(SRCTAR):
|
$(SRCTAR):
|
||||||
cvs export -r$(CVSTAG) -d $(PKG) pymilter
|
git archive --format=tar.gz --prefix=$(PKG)/ -o $(SRCTAR) $(PKG)
|
||||||
tar cvfz $(PKG).tar.gz $(PKG)
|
|
||||||
rm -r $(PKG)
|
|
||||||
|
|
||||||
cvstar: $(SRCTAR)
|
gittar: $(SRCTAR)
|
||||||
|
|||||||
+1
-1
@@ -14,7 +14,7 @@ internal_tlds = ["corp", "personal"]
|
|||||||
# True if internal, False otherwise
|
# True if internal, False otherwise
|
||||||
def is_internal(hostname):
|
def is_internal(hostname):
|
||||||
components = hostname.split(".")
|
components = hostname.split(".")
|
||||||
return components.pop() in internal_tlds:
|
return components.pop() in internal_tlds
|
||||||
|
|
||||||
# Determine if internal and external hosts are mixed based on a list
|
# Determine if internal and external hosts are mixed based on a list
|
||||||
# of hostnames
|
# of hostnames
|
||||||
|
|||||||
+18
-4
@@ -1,5 +1,5 @@
|
|||||||
diff --git a/miltermodule.c b/miltermodule.c
|
diff --git a/miltermodule.c b/miltermodule.c
|
||||||
index aa10a08..af9a144 100644
|
index aa10a08..4d5a93d 100644
|
||||||
--- a/miltermodule.c
|
--- a/miltermodule.c
|
||||||
+++ b/miltermodule.c
|
+++ b/miltermodule.c
|
||||||
@@ -343,7 +343,7 @@ static struct MilterCallback {
|
@@ -343,7 +343,7 @@ static struct MilterCallback {
|
||||||
@@ -67,6 +67,15 @@ index aa10a08..af9a144 100644
|
|||||||
if (o == NULL) { /* out of memory */
|
if (o == NULL) { /* out of memory */
|
||||||
Py_DECREF(arglist);
|
Py_DECREF(arglist);
|
||||||
return _report_exception(self);
|
return _report_exception(self);
|
||||||
|
@@ -889,7 +889,7 @@ milter_wrap_body(SMFICTX *ctx, u_char *bodyp, size_t bodylen) {
|
||||||
|
c = _get_context(ctx);
|
||||||
|
if (!c) return SMFIS_TEMPFAIL;
|
||||||
|
/* Unclear whether this should be s#, z#, or t# */
|
||||||
|
- arglist = Py_BuildValue("(Os#)", c, bodyp, bodylen);
|
||||||
|
+ arglist = Py_BuildValue("(Oy#)", c, bodyp, bodylen);
|
||||||
|
return _generic_wrapper(c, body_callback, arglist);
|
||||||
|
}
|
||||||
|
|
||||||
@@ -963,7 +963,7 @@ milter_wrap_negotiate(SMFICTX *ctx,
|
@@ -963,7 +963,7 @@ milter_wrap_negotiate(SMFICTX *ctx,
|
||||||
int i;
|
int i;
|
||||||
for (i = 0; i < 4; ++i) {
|
for (i = 0; i < 4; ++i) {
|
||||||
@@ -94,8 +103,9 @@ index aa10a08..af9a144 100644
|
|||||||
static PyTypeObject milter_ContextType = {
|
static PyTypeObject milter_ContextType = {
|
||||||
- PyObject_HEAD_INIT(&PyType_Type)
|
- PyObject_HEAD_INIT(&PyType_Type)
|
||||||
- 0,
|
- 0,
|
||||||
|
- "milterContext",
|
||||||
+ PyVarObject_HEAD_INIT(&PyType_Type,0)
|
+ PyVarObject_HEAD_INIT(&PyType_Type,0)
|
||||||
"milterContext",
|
+ "milter.Context",
|
||||||
sizeof(milter_ContextObject),
|
sizeof(milter_ContextObject),
|
||||||
0,
|
0,
|
||||||
milter_Context_dealloc, /* tp_dealloc */
|
milter_Context_dealloc, /* tp_dealloc */
|
||||||
@@ -119,7 +129,7 @@ index aa10a08..af9a144 100644
|
|||||||
};
|
};
|
||||||
|
|
||||||
static const char milter_documentation[] =
|
static const char milter_documentation[] =
|
||||||
@@ -1634,17 +1635,27 @@ Libmilter is currently marked FFR, and needs to be explicitly installed.\n\
|
@@ -1634,17 +1635,31 @@ Libmilter is currently marked FFR, and needs to be explicitly installed.\n\
|
||||||
See <sendmailsource>/libmilter/README for details on setting it up.\n";
|
See <sendmailsource>/libmilter/README for details on setting it up.\n";
|
||||||
|
|
||||||
static void setitem(PyObject *d,const char *name,long val) {
|
static void setitem(PyObject *d,const char *name,long val) {
|
||||||
@@ -148,11 +158,15 @@ index aa10a08..af9a144 100644
|
|||||||
|
|
||||||
- m = Py_InitModule4("milter", milter_methods, milter_documentation,
|
- m = Py_InitModule4("milter", milter_methods, milter_documentation,
|
||||||
- (PyObject*)NULL, PYTHON_API_VERSION);
|
- (PyObject*)NULL, PYTHON_API_VERSION);
|
||||||
|
+ if (PyType_Ready(&milter_ContextType) < 0)
|
||||||
|
+ return NULL;
|
||||||
|
+
|
||||||
+ m = PyModule_Create(&moduledef);
|
+ m = PyModule_Create(&moduledef);
|
||||||
|
+ if (m == NULL) return NULL;
|
||||||
d = PyModule_GetDict(m);
|
d = PyModule_GetDict(m);
|
||||||
MilterError = PyErr_NewException("milter.error", NULL, NULL);
|
MilterError = PyErr_NewException("milter.error", NULL, NULL);
|
||||||
PyDict_SetItemString(d,"error", MilterError);
|
PyDict_SetItemString(d,"error", MilterError);
|
||||||
@@ -1710,4 +1721,5 @@ initmilter(void) {
|
@@ -1710,4 +1725,5 @@ initmilter(void) {
|
||||||
setitem(d,"DISCARD", SMFIS_DISCARD);
|
setitem(d,"DISCARD", SMFIS_DISCARD);
|
||||||
setitem(d,"ACCEPT", SMFIS_ACCEPT);
|
setitem(d,"ACCEPT", SMFIS_ACCEPT);
|
||||||
setitem(d,"TEMPFAIL", SMFIS_TEMPFAIL);
|
setitem(d,"TEMPFAIL", SMFIS_TEMPFAIL);
|
||||||
|
|||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
/* Copyright (C) 2001 James Niemira (niemira@colltech.com, urmane@urmane.org)
|
/* Copyright (C) 2001 James Niemira (niemira@colltech.com, urmane@urmane.org)
|
||||||
* Portions Copyright (C) 2001,2002,2003,2004,2005,2006,2007
|
* Portions Copyright (C) 2001,2002,2003,2004,2005,2006,2007
|
||||||
* Stuart Gathman (stuart@bmsi.com)
|
* Stuart Gathman (stuart@gathman.org)
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify it
|
* This program is free software: you can redistribute it and/or modify it
|
||||||
* under the terms of the GNU General Public License as published by the
|
* under the terms of the GNU General Public License as published by the
|
||||||
@@ -282,7 +282,7 @@ $ python setup.py help
|
|||||||
* published. Unfortunately I know of no good way to do this
|
* published. Unfortunately I know of no good way to do this
|
||||||
* other than with OS-specific tests.
|
* other than with OS-specific tests.
|
||||||
*/
|
*/
|
||||||
#if defined(__FreeBSD_kernel__) || defined(__linux__)
|
#if defined(__FreeBSD__) || defined(__linux__)
|
||||||
#define HAVE_IPV6_RFC2553
|
#define HAVE_IPV6_RFC2553
|
||||||
#include <arpa/inet.h>
|
#include <arpa/inet.h>
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
+6
-1
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
Summary: Python interface to sendmail milter API
|
Summary: Python interface to sendmail milter API
|
||||||
Name: %{pythonbase}-pymilter
|
Name: %{pythonbase}-pymilter
|
||||||
Version: 1.0.1
|
Version: 1.0.2
|
||||||
Release: 1%{dist}
|
Release: 1%{dist}
|
||||||
Source: https://github.com/sdgathman/pymilter/archive/pymilter-%{version}.tar.gz
|
Source: https://github.com/sdgathman/pymilter/archive/pymilter-%{version}.tar.gz
|
||||||
Source1: pymilter.te
|
Source1: pymilter.te
|
||||||
@@ -96,6 +96,11 @@ if [ $1 -eq 0 ] ; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
%changelog
|
%changelog
|
||||||
|
* Tue Dec 13 2016 Stuart Gathman <stuart@gathman.org> 1.0.2-1
|
||||||
|
- Fix the last setsymlist misspelling. Support in test framework and tests.
|
||||||
|
- Add @symlist decorator.
|
||||||
|
- Change body callback and a few other APIs to use bytes instead of str.
|
||||||
|
|
||||||
* Tue Sep 20 2016 Stuart Gathman <stuart@gathman.org> 1.0.1-1
|
* Tue Sep 20 2016 Stuart Gathman <stuart@gathman.org> 1.0.1-1
|
||||||
- Support python3
|
- Support python3
|
||||||
|
|
||||||
|
|||||||
+6
-1
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
Summary: Python interface to sendmail milter API
|
Summary: Python interface to sendmail milter API
|
||||||
Name: %{pythonbase}-pymilter
|
Name: %{pythonbase}-pymilter
|
||||||
Version: 1.0.1
|
Version: 1.0.2
|
||||||
Release: 1%{dist}
|
Release: 1%{dist}
|
||||||
Source: https://github.com/sdgathman/pymilter/archive/pymilter-%{version}.tar.gz
|
Source: https://github.com/sdgathman/pymilter/archive/pymilter-%{version}.tar.gz
|
||||||
Source1: pymilter.te
|
Source1: pymilter.te
|
||||||
@@ -95,6 +95,11 @@ if [ $1 -eq 0 ] ; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
%changelog
|
%changelog
|
||||||
|
* Tue Dec 13 2016 Stuart Gathman <stuart@gathman.org> 1.0.2-1
|
||||||
|
- Fix the last setsymlist misspelling. Support in test framework and tests.
|
||||||
|
- Add @symlist decorator.
|
||||||
|
- Change body callback and a few other APIs to use bytes instead of str.
|
||||||
|
|
||||||
* Tue Sep 20 2016 Stuart Gathman <stuart@gathman.org> 1.0.1-1
|
* Tue Sep 20 2016 Stuart Gathman <stuart@gathman.org> 1.0.1-1
|
||||||
- Support python3
|
- Support python3
|
||||||
|
|
||||||
|
|||||||
@@ -33,18 +33,25 @@ class sampleMilter(Milter.Milter):
|
|||||||
self.fp = None
|
self.fp = None
|
||||||
self.bodysize = 0
|
self.bodysize = 0
|
||||||
self.id = Milter.uniqueID()
|
self.id = Milter.uniqueID()
|
||||||
|
self.user = None
|
||||||
|
|
||||||
# multiple messages can be received on a single connection
|
# multiple messages can be received on a single connection
|
||||||
# envfrom (MAIL FROM in the SMTP protocol) seems to mark the start
|
# envfrom (MAIL FROM in the SMTP protocol) seems to mark the start
|
||||||
# of each message.
|
# of each message.
|
||||||
|
@Milter.symlist('{auth_authen}')
|
||||||
@Milter.noreply
|
@Milter.noreply
|
||||||
def envfrom(self,f,*str):
|
def envfrom(self,f,*str):
|
||||||
"start of MAIL transaction"
|
"start of MAIL transaction"
|
||||||
self.log("mail from",f,str)
|
|
||||||
self.fp = BytesIO()
|
self.fp = BytesIO()
|
||||||
self.tempname = None
|
self.tempname = None
|
||||||
self.mailfrom = f
|
self.mailfrom = f
|
||||||
self.bodysize = 0
|
self.bodysize = 0
|
||||||
|
self.user = self.getsymval('{auth_authen}')
|
||||||
|
self.auth_type = self.getsymval('{auth_type}')
|
||||||
|
if self.user:
|
||||||
|
self.log("user",self.user,"sent mail from",f,str)
|
||||||
|
else:
|
||||||
|
self.log("mail from",f,str)
|
||||||
return Milter.CONTINUE
|
return Milter.CONTINUE
|
||||||
|
|
||||||
def envrcpt(self,to,*str):
|
def envrcpt(self,to,*str):
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ if sys.version >= '3':
|
|||||||
print("modules=",modules)
|
print("modules=",modules)
|
||||||
|
|
||||||
# NOTE: importing Milter to obtain version fails when milter.so not built
|
# NOTE: importing Milter to obtain version fails when milter.so not built
|
||||||
setup(name = "pymilter", version = '1.0.1',
|
setup(name = "pymilter", version = '1.0.2',
|
||||||
description="Python interface to sendmail milter API",
|
description="Python interface to sendmail milter API",
|
||||||
long_description="""\
|
long_description="""\
|
||||||
This is a python extension module to enable python scripts to
|
This is a python extension module to enable python scripts to
|
||||||
@@ -28,9 +28,9 @@ sending DSNs or doing CBVs.
|
|||||||
author="Jim Niemira",
|
author="Jim Niemira",
|
||||||
author_email="urmane@urmane.org",
|
author_email="urmane@urmane.org",
|
||||||
maintainer="Stuart D. Gathman",
|
maintainer="Stuart D. Gathman",
|
||||||
maintainer_email="stuart@bmsi.com",
|
maintainer_email="stuart@gathman.org",
|
||||||
license="GPL",
|
license="GPL",
|
||||||
url="http://www.bmsi.com/python/milter.html",
|
url="https://pythonhosted.org/milter/",
|
||||||
py_modules=modules,
|
py_modules=modules,
|
||||||
packages = ['Milter'],
|
packages = ['Milter'],
|
||||||
ext_modules=[
|
ext_modules=[
|
||||||
|
|||||||
@@ -13,9 +13,14 @@ class BMSMilterTestCase(unittest.TestCase):
|
|||||||
|
|
||||||
def testDefang(self,fname='virus1'):
|
def testDefang(self,fname='virus1'):
|
||||||
milter = TestMilter()
|
milter = TestMilter()
|
||||||
|
milter.setsymval('{auth_authen}','batman')
|
||||||
|
milter.setsymval('{auth_type}','batcomputer')
|
||||||
|
milter.setsymval('j','mailhost')
|
||||||
rc = milter.connect()
|
rc = milter.connect()
|
||||||
self.failUnless(rc == Milter.CONTINUE)
|
self.failUnless(rc == Milter.CONTINUE)
|
||||||
rc = milter.feedMsg(fname)
|
rc = milter.feedMsg(fname)
|
||||||
|
self.failUnless(milter.user == 'batman',"getsymval failed")
|
||||||
|
self.failUnless(milter.auth_type != 'batcomputer',"setsymlist failed")
|
||||||
self.failUnless(rc == Milter.ACCEPT)
|
self.failUnless(rc == Milter.ACCEPT)
|
||||||
self.failUnless(milter._bodyreplaced,"Message body not replaced")
|
self.failUnless(milter._bodyreplaced,"Message body not replaced")
|
||||||
fp = milter._body
|
fp = milter._body
|
||||||
|
|||||||
@@ -45,6 +45,11 @@ class AddrCacheTestCase(unittest.TestCase):
|
|||||||
h = Milter.utils.parse_header(s)
|
h = Milter.utils.parse_header(s)
|
||||||
self.assertEqual(h,b'Last Few Coldplay Album Artworks Available\x00')
|
self.assertEqual(h,b'Last Few Coldplay Album Artworks Available\x00')
|
||||||
|
|
||||||
|
@unittest.expectedFailure
|
||||||
|
def testParseAddress(self):
|
||||||
|
s = Milter.utils.parseaddr('a(WRONG)@b')
|
||||||
|
self.assertEqual(s,('WRONG', 'a@b'))
|
||||||
|
|
||||||
def suite():
|
def suite():
|
||||||
s = unittest.makeSuite(AddrCacheTestCase,'test')
|
s = unittest.makeSuite(AddrCacheTestCase,'test')
|
||||||
s.addTest(doctest.DocTestSuite(Milter.utils))
|
s.addTest(doctest.DocTestSuite(Milter.utils))
|
||||||
|
|||||||
Reference in New Issue
Block a user