Compare commits

...

36 Commits

Author SHA1 Message Date
Stuart D. Gathman 10fdccf366 Release 1.0.4 2019-04-17 19:07:50 -04:00
Stuart D. Gathman 7d097fa4a0 start.sh superceded by daemonize on EL6 and systemd elsewhere. 2019-04-17 17:25:06 -04:00
Stuart D. Gathman ca81502c85 Remove milter.path no longer needed. Unified rpm spec. 2019-04-17 17:16:39 -04:00
Ralph Seichter 50356d4710 Fix for compilation error on macOS 10.14 (Mojave) (#31)
This change ensures that arpa/inet.h is included when building
miltermodule.c on macOS 10.14. See
https://github.com/sdgathman/pymilter/issues/30

Signed-off-by: Ralph Seichter <github@seichter.de>
2019-04-17 11:53:56 -04:00
dkg ec3fa46799 Correctly document that body callback chunks are in bytes (#28)
https://github.com/sdgathman/pymilter/issues/12 says "Obviously, body
and replacebody are bytes" and milter_wrap_body in miltermodule.c
says:

   arglist = Py_BuildValue("(Oy#)", c, bodyp, bodylen);
…

So pymilter should sport the correct documentation.
2019-02-22 15:54:01 -05:00
Stuart D. Gathman 04e0b15640 Import full path on py3 2019-02-22 15:54:01 -05:00
Stuart D. Gathman ff6a07ef10 Misspelling - Scott Kitterman 2018-12-26 10:31:20 -05:00
Stuart D. Gathman 7dfda0a3bd More #ifdef consolidation. 2018-12-24 18:20:26 -05:00
Pino Toscano 183ce91a61 Include arpa/inet.h on any GNU libc platform (#24)
This header is provided by GNU libc on any platform, so include it
unconditionally if __GLIBC__ is defined.

Fixes #23.
2018-12-24 17:42:13 -05:00
Stuart D. Gathman d30918aca7 Fix python3 unit tests 2018-12-23 23:38:15 -05:00
Stuart D. Gathman 4e8fda517c Fix getsymval for python3 2018-12-23 23:16:42 -05:00
Stuart D. Gathman 2194d8fd96 setsymlist not called yet in TestMilter 2018-12-23 23:03:46 -05:00
Stuart D. Gathman 52e1b4ae32 Move sys to top of mime.py 2018-12-23 22:31:20 -05:00
Stuart D. Gathman 55e5378659 Move py3 sgmllib to an internal module - Scott Kitterman 2018-12-23 20:29:01 -05:00
Stuart D. Gathman f9b2241ec6 Improve readability of module init, setitem. Move old changes to ChangeLog 2018-12-23 20:00:24 -05:00
Stuart D. Gathman 62783fbbfd Convert python3 patch for miltermodule.c to #ifdef - Scott Kitterman 2018-12-23 19:46:59 -05:00
Stuart D. Gathman d521665f75 Working testctx, still need to redirect logging in testsample.py 2018-08-18 17:49:37 -04:00
Stuart D. Gathman 55eb05e526 Change deprecated failUnless to assertTrue 2018-08-05 00:34:06 -04:00
Stuart D. Gathman fc008f6db0 Update documentation 2018-08-04 14:09:23 -04:00
Stuart D. Gathman 96cd9ac263 Request compile error on implicit function declarations to save lots
of debugging time.
2018-01-09 21:52:39 -05:00
jcea 10471faa7d Correctly support IPv6 in Solaris (#17)
I can't see how adding one more OS to the manual list can hurt for now, so in goes the PR.  Thanks.
2018-01-09 14:46:11 -05:00
Stuart D. Gathman 36750bac78 Fix some python3 syntax errors 2017-04-20 17:00:14 -04:00
Stuart D. Gathman 42e7a02638 Missed version 2016-12-13 14:17:34 -05:00
Stuart D. Gathman bc9d8c622b Release 1.0.2 2016-12-13 14:15:56 -05:00
Stuart D. Gathman 2fa952e108 Make test cases work. (Previous commit incomplete) 2016-12-02 00:19:27 -05:00
Stuart D. Gathman 381e906b6a Implement setsymlist decorator and test framework 2016-12-01 23:59:31 -05:00
Stuart D. Gathman 207278479f Another setsymlist misspelling 2016-11-17 09:12:16 -05:00
Stuart D. Gathman a0bd76cded Gerhard Schmidt corrected the FreeBSD test via pymilter list. 2016-11-08 08:12:52 -05:00
Stuart D. Gathman 8e96c23ddc Update email and url in setup.py 2016-11-07 20:10:54 -05:00
Stuart D. Gathman 5ec4e2b34d Update email 2016-10-29 09:51:13 -04:00
Stuart D. Gathman 28c3a6afd6 update .gitignore 2016-10-17 23:48:09 -04:00
Stuart D. Gathman 36df47f019 Move expected failure to testutils.py where it can be tagged.
Change to example.com/40 for iniplist with leftover bits to ignore.
2016-10-17 23:38:49 -04:00
Tom Hendrikx e5c03665e9 Fixes (#8)
* Fix tests that fail

Solves #5

* FIx syntax errors
2016-10-17 23:23:55 -04:00
Stuart D. Gathman ea9ca0c12a Pass bytes to body callback. 2016-10-17 20:42:46 -04:00
Stuart D. Gathman fb1da3b12b Call PyType_Ready 2016-10-17 19:15:07 -04:00
Stuart D. Gathman 74d33126b5 Create src.tar from git-archive 2016-09-30 20:12:41 -04:00
24 changed files with 967 additions and 730 deletions
+4
View File
@@ -2,3 +2,7 @@
build/ build/
test/*.out test/*.out
test/*.tstout test/*.tstout
test/*.log
test.db
dist
MANIFEST
+210
View File
@@ -1,3 +1,213 @@
# Revision 1.35 2013/03/14 22:11:25 customdesigned
# Release 0.9.8
#
# Revision 1.34 2013/03/09 05:42:14 customdesigned
# Make TestBase members private, fix getsymlist misspelling.
#
# Revision 1.33 2013/03/09 00:25:23 customdesigned
# Better untrapped exception message. const char for doc comments.
#
# Revision 1.32 2013/01/13 01:46:16 customdesigned
# Doc updates.
#
# Revision 1.31 2012/04/12 23:32:50 customdesigned
# Replace redundant callback array with macros. If this doesn't break anything,
# macros can be eliminated with code changes.
#
# Revision 1.30 2012/04/12 23:08:06 customdesigned
# Support RFC2553 on BSD
#
# Revision 1.29 2011/06/09 15:45:27 customdesigned
# Print callback name for non-int return error.
#
# Revision 1.28 2011/06/08 23:13:48 customdesigned
# Generate special exception when callback return not int.
#
# Revision 1.27 2009/07/28 21:45:54 customdesigned
# Add getversion() to return runtime version.
#
# Revision 1.26 2009/07/28 21:08:20 customdesigned
# Increment del count.
#
# Revision 1.25 2009/07/28 20:58:55 customdesigned
# getdiag method
#
# Revision 1.24 2009/06/09 01:54:44 customdesigned
# Forgot to initialize optional parameter.
#
# Revision 1.23 2009/05/29 20:44:58 customdesigned
# Typo SMFIP_NO constants.
#
# 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.
#
# Revision 1.13 2008/11/23 03:06:47 customdesigned
# Milter support for chgfrom.
#
# Revision 1.12 2008/11/21 20:42:52 customdesigned
# Support smfi_chgfrom and smfi_addrcpt_par.
#
# Revision 1.11 2007/09/25 02:26:29 customdesigned
# Update license.
#
# Revision 1.10 2006/02/12 02:00:42 customdesigned
# Resolve FIXME for wrap_close.
#
# Revision 1.9 2005/12/23 21:46:36 customdesigned
# Compile on sendmail-8.12 (ifdef SMFIR_INSHEADER)
#
# Revision 1.8 2005/10/20 23:23:36 customdesigned
# Include smfi_progress is SMFIR_PROGRESS defined
#
# Revision 1.7 2005/10/20 23:04:46 customdesigned
# Add optional idx for position of added header.
#
# Revision 1.6 2005/07/15 22:18:17 customdesigned
# Support callback exception policy
#
# Revision 1.5 2005/06/24 04:20:07 customdesigned
# Report context allocation error.
#
# Revision 1.4 2005/06/24 04:12:43 customdesigned
# Remove unused name argument to generic wrappers.
#
# Revision 1.3 2005/06/24 03:57:35 customdesigned
# Handle close called before connect.
#
# Revision 1.2 2005/06/02 04:18:55 customdesigned
# Update copyright notices after reading article on /.
#
# Revision 1.1.1.2 2005/05/31 18:09:06 customdesigned
# Release 0.7.1
#
# Revision 2.31 2004/08/23 02:24:36 stuart
# Support setbacklog
#
# Revision 2.30 2004/08/21 20:29:53 stuart
# Support option of 11 lines max for mlreply.
#
# Revision 2.29 2004/08/21 04:14:29 stuart
# mlreply support
#
# Revision 2.28 2004/08/21 02:45:21 stuart
# Don't leak int constants if module unloaded.
#
# Revision 2.27 2004/04/06 03:19:59 stuart
# Release 0.6.8
#
# Revision 2.26 2004/03/04 21:43:06 stuart
# Fix memory leak by removing unused dynamic template buffer,
# thanks again to Alexander Kourakos.
#
# Revision 2.25 2004/03/01 19:45:03 stuart
# Release 0.6.5
#
# Revision 2.24 2004/03/01 18:56:50 stuart
# Support progress reporting.
#
# Revision 2.23 2004/03/01 18:36:09 stuart
# Plug memory leak. Thanks to Alexander Kourakos.
#
# Revision 2.22 2003/11/02 03:01:46 stuart
# Adjust SMTP error codes after careful reading of standard.
#
# Revision 2.21 2003/06/24 19:57:04 stuart
# Allow removing a python milter callback by setting to None.
#
# Revision 2.20 2003/02/13 17:08:57 stuart
# IPV6 support
#
# Revision 2.19 2003/02/13 16:58:29 stuart
# Support passing None to setreply and chgheader.
#
# Revision 2.18 2002/12/11 16:44:06 stuart
# Support QUARANTINE if supported by libmilter.
#
# Revision 2.17 2002/04/18 20:20:35 stuart
# Fix for NULL hostaddr in connect callback from Jason Erickson.
#
# Revision 2.16 2001/09/26 13:29:09 stuart
# sa_len not supported by linux.
#
# Revision 2.15 2001/09/25 17:28:40 stuart
# Copyrights, documentation, release 0.3.1
#
# Revision 2.14 2001/09/25 00:36:57 stuart
# Pass hostaddr to python code in format used by standard socket module.
#
# Revision 2.13 2001/09/24 23:44:55 stuart
# Return old callback from setcallback functions.
#
# Revision 2.12 2001/09/24 20:02:30 stuart
# Remove redundant setpriv
#
# Revision 2.11 2001/09/23 22:26:35 stuart
# Update docs. Streamline Milter.py
# update testbms.py to reflect actual sendmail behaviour with multiple
# messages per connection.
#
# Revision 2.10 2001/09/22 15:33:42 stuart
# More doc comment updates.
#
# Revision 2.9 2001/09/22 14:52:27 stuart
# Actually return retval in _generic_return.
# Go over doc comments.
#
# Revision 2.8 2001/09/22 01:59:32 stuart
# Prevent reentrant call of milter_main, which libmilter doesn't support.
#
# Revision 2.7 2001/09/22 01:47:37 stuart
# Forgot to set milter interp.
#
# Revision 2.6 2001/09/22 01:23:53 stuart
# Added proper threading after research in python docs.
#
# Revision 2.5 2001/09/21 20:08:51 stuart
# Release 0.2.3
#
# Revision 2.4 2001/09/20 16:18:16 stuart
# libmilter checks in_eom state, so we don't have to.
#
# Revision 2.3 2001/09/19 06:02:33 stuart
# Make more stuff static.
#
# Revision 2.1 2001/09/19 04:24:13 stuart
# Use extension type to track context in python.
#
# Revision 1.4 2001/09/18 18:48:28 stuart
# clear private data reference in _clear_context
#
# Revision 1.3 2001/09/15 04:19:37 stuart
# nasty off by 1 mem overwrite bugs in wrap_env
# generic_set_callback
#
# Revision 1.2 2001/09/15 03:15:39 stuart
# several bugs fixed, works smoothly
#
# Revision 1.69 2006/11/04 22:09:39 customdesigned # Revision 1.69 2006/11/04 22:09:39 customdesigned
# Another lame DSN heuristic. Block PTR cache poisoning attack. # Another lame DSN heuristic. Block PTR cache poisoning attack.
# #
+1 -1
View File
@@ -31,7 +31,7 @@ PROJECT_NAME = pymilter
# This could be handy for archiving the generated documentation or # This could be handy for archiving the generated documentation or
# if some version control system is used. # if some version control system is used.
PROJECT_NUMBER = 1.0 PROJECT_NUMBER = 1.0.2
# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute)
# base path where the generated documentation will be put. # base path where the generated documentation will be put.
-1
View File
@@ -10,7 +10,6 @@ include testmime.py
include testutils.py include testutils.py
include test.py include test.py
include sample.py include sample.py
include sgmllib.py
include milter-template.py include milter-template.py
include test/* include test/*
include Milter/*.py include Milter/*.py
+37 -6
View File
@@ -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.4'
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,11 +415,16 @@ 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
#self.log("Negotiated:",opts) #self.log("Negotiated:",opts)
except: except Exception as x:
# don't change anything if something went wrong # don't change anything if something went wrong
return ALL_OPTS return ALL_OPTS
return CONTINUE return CONTINUE
@@ -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' ')
a += m
except: pass except: pass
a += m return self._ctx.setsymlist(stage,b' '.join(a))
return self._ctx.setsmlist(stage,' '.join(a))
# Milter methods which can only be called from eom callback. # Milter methods which can only be called from eom callback.
+2 -2
View File
@@ -26,8 +26,8 @@ def DNSLookup(name, qtype):
# A RR as dotted quad. For consistency, this driver should # A RR as dotted quad. For consistency, this driver should
# return both as binary string. # return both as binary string.
return [((a['name'], a['typename']), a['data']) for a in resp.answers] return [((a['name'], a['typename']), a['data']) for a in resp.answers]
except IOError, x: except IOError as x:
raise DNSError, str(x) raise DNSError(str(x))
class Session(object): class Session(object):
"""A Session object has a simple cache with no TTL that is valid """A Session object has a simple cache with no TTL that is valid
+3 -3
View File
@@ -142,13 +142,13 @@ def send_dsn(mailfrom,receiver,msg=None,timeout=600,session=None,ourfrom=''):
if badrcpts: if badrcpts:
return badrcpts return badrcpts
return None # success return None # success
except smtplib.SMTPRecipientsRefused,x: except smtplib.SMTPRecipientsRefused as x:
if len(x.recipients) == 1: if len(x.recipients) == 1:
return x.recipients.values()[0] # permanent error return x.recipients.values()[0] # permanent error
return x.recipients return x.recipients
except smtplib.SMTPSenderRefused,x: except smtplib.SMTPSenderRefused as x:
return x.args[:2] # does not accept DSN return x.args[:2] # does not accept DSN
except smtplib.SMTPDataError,x: except smtplib.SMTPDataError as x:
return x.args # permanent error return x.args # permanent error
except smtplib.SMTPException: except smtplib.SMTPException:
pass # any other error, try next MX pass # any other error, try next MX
View File
+44 -4
View File
@@ -14,6 +14,7 @@ Milter.NOREPLY = Milter.CONTINUE
## Test mixin for unit testing %milter applications. ## Test mixin for unit testing %milter applications.
# This mixin overrides many Milter.MilterBase methods # This mixin overrides many Milter.MilterBase methods
# with stub versions that simply record what was done. # with stub versions that simply record what was done.
# @deprecated Use Milter.test.TestCtx
# @since 0.9.8 # @since 0.9.8
class TestBase(object): class TestBase(object):
@@ -40,6 +41,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 +58,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 +121,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 +132,14 @@ 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)
print('setsymlist',stage,a)
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 +160,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 +195,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 +222,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
+297
View File
@@ -0,0 +1,297 @@
## @package Milter.testctx
# A test framework for milters that replaces milterContext rather
# than Milter.Base. Since miltermodule.c doesn't currently export
# a way to query callbacks set (and we might want to run without
# loading milter), we assume the callbacks set by Milter.runmilter().
from __future__ import print_function
from socket import AF_INET,AF_INET6
import time
import mime
try:
from io import BytesIO
except:
from StringIO import StringIO as BytesIO
import Milter
from Milter import utils
import mime
## Milter context for unit testing %milter applications.
# A substitute for milter.milterContext that can be passed to
# Milter.Base._setctx().
# @since 1.0.3
class TestCtx(object):
default_opts = [Milter.CURR_ACTS,0x1fffff,0,0]
def __init__(self,logfile='test/milter.log'):
## Usually the Milter application derived from Milter.Base
self._priv = None
## List of recipients deleted
self._delrcpt = []
## List of recipients added
self._addrcpt = []
## Macros defined
self._macros = { }
## Reply codes and messages set by the %milter
self._reply = None
## The macros returned by protocol stage
self._symlist = [ None, None, None, None, None, None, None ]
## The message body.
self._body = None
## True if the %milter replaced the message body.
self._bodyreplaced = False
## True if the %milter changed any headers.
self._headerschanged = False
## The rfc822 message object for the current email being fed to the %milter.
self._msg = None
## The MAIL FROM for the current email being fed to the %milter
self._sender = None
## True if the %milter changed the envelope from.
self._envfromchanged = False
## List of recipients added
self._addrcpt = []
## Negotiated options
self._opts = TestCtx.default_opts
## Last activity
self._activity = time.time()
def getpriv(self):
return self._priv
def setpriv(self,priv):
self._priv = priv
def getsymval(self,name):
stage = self._stage
if stage >= 0:
try:
s = name.encode('utf8')
except: pass
syms = self._symlist[stage]
if syms is not None and s not in syms:
return None
return self._macros.get(name,None)
def _setsymval(self,name,val):
self._macros[name] = val
def setreply(self,rcode,xcode,*msg):
self._reply = (rcode,xcode) + msg
def setsymlist(self,stage,macros):
if self._stage != -1:
raise RuntimeError("setsymlist may only be called from negotiate")
# Records which macros are available to getsymval()
m = macros
try:
m = m.encode('utf8')
except: pass
try:
m = m.split(b' ')
except: pass
if len(m) > 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)
if not m:
raise ValueError('setsymlist with empty list for stage:'+stage)
self._symlist[stage] = set(m)
def addheader(self,field,value,idx):
if not self._body:
raise IOError("addheader not called from eom()")
self._msg[field] = value
self._headerschanged = True
def chgheader(self,field,idx,value):
if not self._body:
raise IOError("chgheader not called from eom()")
if value == '':
del self._msg[field]
else:
self._msg[field] = value
self._headerschanged = True
def addrcpt(self,rcpt,params):
if not self._body:
raise IOError("addrcpt not called from eom()")
self._addrcpt.append((rcpt,params))
def delrcpt(self,rcpt):
if not self._body:
raise IOError("delrcpt not called from eom()")
self._delrcpt.append(rcpt)
def replacebody(self,chunk):
if self._body:
self._body.write(chunk)
self._bodyreplaced = True
else:
raise IOError("replacebody not called from eom()")
def chgfrom(self,sender,params=None):
if not self._body:
raise IOError("chgfrom not called from eom()")
self._envfromchanged = True
self._sender = sender
def quarantine(self,reason):
raise NotImplemented
## Reset activity timer.
def progress(self):
self._activity = time.time()
def _abort(self):
"What Milter sets for abort_callback"
self._priv.abort()
self._close()
def _close(self):
Milter.close_callback(self)
def _negotiate(self):
self._body = None
self._bodyreplaced = False
self._priv = None
self._opts = TestCtx.default_opts
self._stage = -1
rc = Milter.negotiate_callback(self,self._opts)
if rc == Milter.ALL_OPTS:
self._opts = TestCtx.default_opts
elif rc != Milter.CONTINUE:
self._abort()
self._close()
self._protocol = self._opts[1]
return rc
def _connect(self,host='localhost',helo='spamrelay',ip='1.2.3.4'):
rc = self._negotiate()
# FIXME: what if not CONTINUE or ALL_OPTS?
if self._protocol & Milter.P_NOCONNECT:
return Milter.CONTINUE
if utils.ip4re.match(ip):
af = AF_INET
elif utils.ip6re.match(ip):
af = AF_INET6
else:
raise ValueError('TestCtx.connect: invalid ip address: '+ip)
self._stage = Milter.M_CONNECT
rc = Milter.connect_callback(self,host,af,ip)
self._stage = None
if rc != Milter.CONTINUE:
self._close()
return rc
return self._helo(helo)
def _helo(self,helo):
if self._protocol & Milter.P_NOHELO:
return Milter.CONTINUE
self._stage = Milter.M_HELO
rc = self._priv.hello(helo)
self._stage = None
if rc != Milter.CONTINUE:
self._close()
return rc
def _envfrom(self,*s):
self._sender = s[0]
if self._protocol & Milter.P_NOMAIL:
return Milter.CONTINUE
self._stage = Milter.M_ENVFROM
rc = self._priv.envfrom(*s)
self._stage = None
return rc
def _envrcpt(self,s):
if self._protocol & Milter.P_NORCPT:
return Milter.CONTINUE
self._stage = Milter.M_ENVRCPT
rc = self._priv.envrcpt(s)
self._stage = None
return rc
def _data(self):
if self._protocol & Milter.P_NODATA:
return Milter.CONTINUE
self._stage = Milter.M_DATA
rc = self._priv.data()
self._stage = None
return rc
def _header(self,fld,val):
return self._priv.header(fld,val)
def _eoh(self):
if self._protocol & Milter.P_NOEOH:
return Milter.CONTINUE
self._stage = Milter.M_EOH
rc = self._priv.eoh()
self._stage = None
return rc
def _feed_body(self,bfp):
if self._protocol & Milter.P_NOBODY:
return Milter.CONTINUE
while True:
buf = bfp.read(8192)
if len(buf) == 0: break
rc = self._priv.body(buf)
if rc != Milter.CONTINUE: return rc
return Milter.CONTINUE
def _eom(self):
self._body = BytesIO()
self._stage = Milter.M_EOM
rc = self._priv.eom()
self._stage = None
return rc
## Feed a file like object to the ctx. Calls the callbacks in
# the same sequence as libmilter.
# @param fp the file with rfc2822 message stream
# @param sender the MAIL FROM
# @param rcpt RCPT TO - additional recipients may follow
def _feedFile(self,fp,sender="spam@adv.com",rcpt="victim@lamb.com",*rcpts):
self._body = None
self._bodyreplaced = False
self._headerschanged = False
self._reply = None
msg = mime.message_from_file(fp)
self._msg = msg
# envfrom
rc = self._envfrom('<%s>'%sender)
if rc != Milter.CONTINUE: return rc
# envrcpt
for rcpt in (rcpt,) + rcpts:
rc = self._envrcpt('<%s>'%rcpt)
if rc != Milter.CONTINUE: return rc
# data
rc = self._data()
if rc != Milter.CONTINUE: return rc
# header
for h,val in msg.items():
rc = self._header(h,val)
if rc != Milter.CONTINUE: return rc
# eoh
rc = self._eoh()
if rc != Milter.CONTINUE: return rc
# body
header,body = msg.as_bytes().split(b'\n\n',1)
rc = self._feed_body(BytesIO(body))
if rc != Milter.CONTINUE: return rc
rc = self._eom()
if self._bodyreplaced:
body = self._body.getvalue()
self._body = BytesIO()
self._body.write(header)
self._body.write(b'\n\n')
self._body.write(body)
return rc
## Feed an email contained in a file to the %milter.
# This is a convenience method that invokes @link #feedFile feedFile @endlink.
# @param sender MAIL FROM
# @param rcpts RCPT TO, multiple recipients may be supplied
def _feedMsg(self,fname,sender="spam@adv.com",*rcpts):
with open('test/'+fname,'rb') as fp:
return self._feedFile(fp,sender,*rcpts)
+2 -3
View File
@@ -8,6 +8,7 @@ import socket
import email.errors import email.errors
from email.header import decode_header from email.header import decode_header
import email.base64mime import email.base64mime
import email.utils
from fnmatch import fnmatchcase from fnmatch import fnmatchcase
from binascii import a2b_base64 from binascii import a2b_base64
@@ -70,7 +71,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 +135,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)
+14 -4
View File
@@ -57,7 +57,9 @@
# #
# @section Useful python packages for milters # @section Useful python packages for milters
# #
# <a href="https://pypi.python.org/pypi/pyspf">pyspf</a> checks the # <a href="https://github.com/sdgathman/pymilter">pymilter</a> - this package.
#
# <a href="https://github.com/sdgathman/pyspf">pyspf</a> checks the
# SMTP envelope sender (MAIL FROM, passed to the Milter.Base.envfrom callback) # SMTP envelope sender (MAIL FROM, passed to the Milter.Base.envfrom callback)
# against a Sender Policy published in DNS by the sending domain. This # against a Sender Policy published in DNS by the sending domain. This
# can prevent forgery of the MAIL FROM. SPF is Sender Policy Framework. # can prevent forgery of the MAIL FROM. SPF is Sender Policy Framework.
@@ -71,18 +73,26 @@
# a standard place to summarize the results from DKIM, SPF, rDNS, SMTP AUTH, # a standard place to summarize the results from DKIM, SPF, rDNS, SMTP AUTH,
# and other email authentication methods. # and other email authentication methods.
# #
# <a href="https://pypi.python.org/pypi/pydspam/">pydspam</a> wraps # <a href="https://github.com/sdgathman/pydspam/">pydspam</a> wraps
# the libdspam API of the <a href="http://dspam.sourceforge.net/">DSPAM</a> # the libdspam API of the <a href="http://dspam.sourceforge.net/">DSPAM</a>
# project. # project.
# #
# <a href="https://github.com/sdgathman/pysrs/">pysrs</a> rewrites
# MAIL FROM to include a timestamped signature so that "bounce spam"
# can be immediately rejected.
#
# <a href="https://github.com/sdgathman/pygossip/">pygossip</a> is a
# system to track reputation by domain and authentication level and type,
# and a simple protocol to gossip about reputations with other mail servers.
#
# @section Milters written with pymilter # @section Milters written with pymilter
# #
# <a href="https://github.com/croessner/vrfydmn">Verify Domain</a> is a # <a href="https://github.com/croessner/vrfydmn">Verify Domain</a> is a
# Postfix milter that rejects/fixes manipulated From: header # Postfix milter that rejects/fixes manipulated From: header
# on a mail host with multiple virtual domains. # on a mail host with multiple virtual domains.
# #
# <a href="https://pypi.python.org/pypi/milter/">BMS Milter</a> has several # <a href="https://github.com/sdgathman/milter/">BMS Milter</a> has several
# milters, a big complicated spam filter that integrates multiple # milters, a big complicated spam filter that integrates multiple
# authentication protocols with pydpsm, and two simple ones: spfmilter.py and # authentication protocols with pydspam, and two simple ones: spfmilter.py and
# dkim-milter.py. # dkim-milter.py.
# #
+5 -8
View File
@@ -1,17 +1,14 @@
web: web:
doxygen doxygen
test -L doc/html/milter_api || ln -sf /usr/share/doc/sendmail-devel-* doc/html/milter_api test -L doc/html/milter_api || ln -sf /usr/share/doc/sendmail-milter-devel doc/html/milter_api
rsync -ravKk doc/html/ spidey2.bmsi.com:/Public/pymilter rsync -ravKk doc/html/ bmsi.com:/var/www/html/pymilter
cd doc/html; zip -r ../../doc . cd doc/html; zip -r ../../doc .
VERSION=1.0 VERSION=1.0.4
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)
+2 -2
View File
@@ -1,4 +1,4 @@
## A very simple milter to prevent mixing of internal and external mail. ## A very simple sample milter to prevent mixing of internal and external mail.
# Internal is defined as using one of a list of internal top level domains. # Internal is defined as using one of a list of internal top level domains.
# This code is open-source on the same terms as Python. # This code is open-source on the same terms as Python.
@@ -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
-160
View File
@@ -1,160 +0,0 @@
diff --git a/miltermodule.c b/miltermodule.c
index aa10a08..af9a144 100644
--- a/miltermodule.c
+++ b/miltermodule.c
@@ -343,7 +343,7 @@ static struct MilterCallback {
{ NULL , NULL }
};
-staticforward struct smfiDesc description; /* forward declaration */
+static struct smfiDesc description; /* forward declaration */
static PyObject *MilterError;
/* The interpreter instance that called milter.main */
@@ -355,7 +355,7 @@ typedef struct {
static milter_Diag diag;
-staticforward PyTypeObject milter_ContextType;
+static PyTypeObject milter_ContextType;
typedef struct {
PyObject_HEAD
@@ -700,7 +700,7 @@ _generic_wrapper(milter_ContextObject *self, PyObject *cb, PyObject *arglist) {
result = PyEval_CallObject(cb, arglist);
Py_DECREF(arglist);
if (result == NULL) return _report_exception(self);
- if (!PyInt_Check(result)) {
+ if (!PyLong_Check(result)) {
const struct MilterCallback *p;
const char *cbname = "milter";
char buf[40];
@@ -715,7 +715,7 @@ _generic_wrapper(milter_ContextObject *self, PyObject *cb, PyObject *arglist) {
PyErr_SetString(MilterError,buf);
return _report_exception(self);
}
- retval = PyInt_AS_LONG(result);
+ retval = PyLong_AS_LONG(result);
Py_DECREF(result);
_release_thread(self->t);
return retval;
@@ -732,7 +732,7 @@ makeipaddr(struct sockaddr_in *addr) {
sprintf(buf, "%d.%d.%d.%d",
(int) (x>>24) & 0xff, (int) (x>>16) & 0xff,
(int) (x>> 8) & 0xff, (int) (x>> 0) & 0xff);
- return PyString_FromString(buf);
+ return PyUnicode_FromString(buf);
}
#ifdef HAVE_IPV6_SUPPORT
@@ -740,8 +740,8 @@ static PyObject *
makeip6addr(struct sockaddr_in6 *addr) {
char buf[100]; /* must be at least INET6_ADDRSTRLEN + 1 */
const char *s = inet_ntop(AF_INET6, &addr->sin6_addr, buf, sizeof buf);
- if (s) return PyString_FromString(s);
- return PyString_FromString("inet6:unknown");
+ if (s) return PyUnicode_FromString(s);
+ return PyUnicode_FromString("inet6:unknown");
}
#endif
@@ -832,7 +832,7 @@ generic_env_wrapper(SMFICTX *ctx, PyObject*cb, char **argv) {
for (i=0;i<count;i++) {
/* There's some error checking performed in do_mkvalue() for a string */
/* that's not currently done here - it probably should be */
- PyObject *o = PyString_FromStringAndSize(argv[i], strlen(argv[i]));
+ PyObject *o = PyUnicode_FromStringAndSize(argv[i], strlen(argv[i]));
if (o == NULL) { /* out of memory */
Py_DECREF(arglist);
return _report_exception(self);
@@ -963,7 +963,7 @@ milter_wrap_negotiate(SMFICTX *ctx,
int i;
for (i = 0; i < 4; ++i) {
*pa[i] = (i <= len)
- ? PyInt_AsUnsignedLongMask(PyList_GET_ITEM(optlist,i))
+ ? PyLong_AsUnsignedLongMask(PyList_GET_ITEM(optlist,i))
: fa[i];
}
if (PyErr_Occurred()) {
@@ -1551,11 +1551,6 @@ static PyMethodDef context_methods[] = {
{ NULL, NULL }
};
-static PyObject *
-milter_Context_getattr(PyObject *self, char *name) {
- return Py_FindMethod(context_methods, self, name);
-}
-
static struct smfiDesc description = { /* Set some reasonable defaults */
"pythonfilter",
SMFI_VERSION,
@@ -1604,14 +1599,13 @@ static PyMethodDef milter_methods[] = {
};
static PyTypeObject milter_ContextType = {
- PyObject_HEAD_INIT(&PyType_Type)
- 0,
+ PyVarObject_HEAD_INIT(&PyType_Type,0)
"milterContext",
sizeof(milter_ContextObject),
0,
milter_Context_dealloc, /* tp_dealloc */
0, /* tp_print */
- milter_Context_getattr, /* tp_getattr */
+ 0, /* tp_getattr */
0, /* tp_setattr */
0, /* tp_compare */
0, /* tp_repr */
@@ -1625,6 +1619,13 @@ static PyTypeObject milter_ContextType = {
0, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT, /* tp_flags */
+ NULL, /* Documentation string */
+ 0, /* call function for all accessible objects */
+ 0, /* delete references to contained objects */
+ 0, /* rich comparisons */
+ 0, /* weak reference enabler */
+ 0, 0, /* Iterators */
+ context_methods, /* Attribute descriptor and subclassing stuff */
};
static const char milter_documentation[] =
@@ -1634,17 +1635,27 @@ Libmilter is currently marked FFR, and needs to be explicitly installed.\n\
See <sendmailsource>/libmilter/README for details on setting it up.\n";
static void setitem(PyObject *d,const char *name,long val) {
- PyObject *v = PyInt_FromLong(val);
+ PyObject *v = PyLong_FromLong(val);
PyDict_SetItemString(d,name,v);
Py_DECREF(v);
}
-void
-initmilter(void) {
+static struct PyModuleDef moduledef = {
+ PyModuleDef_HEAD_INIT,
+ "milter", /* m_name */
+ milter_documentation,/* m_doc */
+ -1, /* m_size */
+ milter_methods, /* m_methods */
+ NULL, /* m_reload */
+ NULL, /* m_traverse */
+ NULL, /* m_clear */
+ NULL, /* m_free */
+};
+
+PyMODINIT_FUNC PyInit_milter(void) {
PyObject *m, *d;
- m = Py_InitModule4("milter", milter_methods, milter_documentation,
- (PyObject*)NULL, PYTHON_API_VERSION);
+ m = PyModule_Create(&moduledef);
d = PyModule_GetDict(m);
MilterError = PyErr_NewException("milter.error", NULL, NULL);
PyDict_SetItemString(d,"error", MilterError);
@@ -1710,4 +1721,5 @@ initmilter(void) {
setitem(d,"DISCARD", SMFIS_DISCARD);
setitem(d,"ACCEPT", SMFIS_ACCEPT);
setitem(d,"TEMPFAIL", SMFIS_TEMPFAIL);
+ return m;
}
+97 -232
View File
@@ -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
@@ -34,217 +34,6 @@ $ python setup.py help
libraries=["milter","smutil","resolv"] libraries=["milter","smutil","resolv"]
* $Log$
* Revision 1.35 2013/03/14 22:11:25 customdesigned
* Release 0.9.8
*
* Revision 1.34 2013/03/09 05:42:14 customdesigned
* Make TestBase members private, fix getsymlist misspelling.
*
* Revision 1.33 2013/03/09 00:25:23 customdesigned
* Better untrapped exception message. const char for doc comments.
*
* Revision 1.32 2013/01/13 01:46:16 customdesigned
* Doc updates.
*
* Revision 1.31 2012/04/12 23:32:50 customdesigned
* Replace redundant callback array with macros. If this doesn't break anything,
* macros can be eliminated with code changes.
*
* Revision 1.30 2012/04/12 23:08:06 customdesigned
* Support RFC2553 on BSD
*
* Revision 1.29 2011/06/09 15:45:27 customdesigned
* Print callback name for non-int return error.
*
* Revision 1.28 2011/06/08 23:13:48 customdesigned
* Generate special exception when callback return not int.
*
* Revision 1.27 2009/07/28 21:45:54 customdesigned
* Add getversion() to return runtime version.
*
* Revision 1.26 2009/07/28 21:08:20 customdesigned
* Increment del count.
*
* Revision 1.25 2009/07/28 20:58:55 customdesigned
* getdiag method
*
* Revision 1.24 2009/06/09 01:54:44 customdesigned
* Forgot to initialize optional parameter.
*
* Revision 1.23 2009/05/29 20:44:58 customdesigned
* Typo SMFIP_NO constants.
*
* 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.
*
* Revision 1.13 2008/11/23 03:06:47 customdesigned
* Milter support for chgfrom.
*
* Revision 1.12 2008/11/21 20:42:52 customdesigned
* Support smfi_chgfrom and smfi_addrcpt_par.
*
* Revision 1.11 2007/09/25 02:26:29 customdesigned
* Update license.
*
* Revision 1.10 2006/02/12 02:00:42 customdesigned
* Resolve FIXME for wrap_close.
*
* Revision 1.9 2005/12/23 21:46:36 customdesigned
* Compile on sendmail-8.12 (ifdef SMFIR_INSHEADER)
*
* Revision 1.8 2005/10/20 23:23:36 customdesigned
* Include smfi_progress is SMFIR_PROGRESS defined
*
* Revision 1.7 2005/10/20 23:04:46 customdesigned
* Add optional idx for position of added header.
*
* Revision 1.6 2005/07/15 22:18:17 customdesigned
* Support callback exception policy
*
* Revision 1.5 2005/06/24 04:20:07 customdesigned
* Report context allocation error.
*
* Revision 1.4 2005/06/24 04:12:43 customdesigned
* Remove unused name argument to generic wrappers.
*
* Revision 1.3 2005/06/24 03:57:35 customdesigned
* Handle close called before connect.
*
* Revision 1.2 2005/06/02 04:18:55 customdesigned
* Update copyright notices after reading article on /.
*
* Revision 1.1.1.2 2005/05/31 18:09:06 customdesigned
* Release 0.7.1
*
* Revision 2.31 2004/08/23 02:24:36 stuart
* Support setbacklog
*
* Revision 2.30 2004/08/21 20:29:53 stuart
* Support option of 11 lines max for mlreply.
*
* Revision 2.29 2004/08/21 04:14:29 stuart
* mlreply support
*
* Revision 2.28 2004/08/21 02:45:21 stuart
* Don't leak int constants if module unloaded.
*
* Revision 2.27 2004/04/06 03:19:59 stuart
* Release 0.6.8
*
* Revision 2.26 2004/03/04 21:43:06 stuart
* Fix memory leak by removing unused dynamic template buffer,
* thanks again to Alexander Kourakos.
*
* Revision 2.25 2004/03/01 19:45:03 stuart
* Release 0.6.5
*
* Revision 2.24 2004/03/01 18:56:50 stuart
* Support progress reporting.
*
* Revision 2.23 2004/03/01 18:36:09 stuart
* Plug memory leak. Thanks to Alexander Kourakos.
*
* Revision 2.22 2003/11/02 03:01:46 stuart
* Adjust SMTP error codes after careful reading of standard.
*
* Revision 2.21 2003/06/24 19:57:04 stuart
* Allow removing a python milter callback by setting to None.
*
* Revision 2.20 2003/02/13 17:08:57 stuart
* IPV6 support
*
* Revision 2.19 2003/02/13 16:58:29 stuart
* Support passing None to setreply and chgheader.
*
* Revision 2.18 2002/12/11 16:44:06 stuart
* Support QUARANTINE if supported by libmilter.
*
* Revision 2.17 2002/04/18 20:20:35 stuart
* Fix for NULL hostaddr in connect callback from Jason Erickson.
*
* Revision 2.16 2001/09/26 13:29:09 stuart
* sa_len not supported by linux.
*
* Revision 2.15 2001/09/25 17:28:40 stuart
* Copyrights, documentation, release 0.3.1
*
* Revision 2.14 2001/09/25 00:36:57 stuart
* Pass hostaddr to python code in format used by standard socket module.
*
* Revision 2.13 2001/09/24 23:44:55 stuart
* Return old callback from setcallback functions.
*
* Revision 2.12 2001/09/24 20:02:30 stuart
* Remove redundant setpriv
*
* Revision 2.11 2001/09/23 22:26:35 stuart
* Update docs. Streamline Milter.py
* update testbms.py to reflect actual sendmail behaviour with multiple
* messages per connection.
*
* Revision 2.10 2001/09/22 15:33:42 stuart
* More doc comment updates.
*
* Revision 2.9 2001/09/22 14:52:27 stuart
* Actually return retval in _generic_return.
* Go over doc comments.
*
* Revision 2.8 2001/09/22 01:59:32 stuart
* Prevent reentrant call of milter_main, which libmilter doesn't support.
*
* Revision 2.7 2001/09/22 01:47:37 stuart
* Forgot to set milter interp.
*
* Revision 2.6 2001/09/22 01:23:53 stuart
* Added proper threading after research in python docs.
*
* Revision 2.5 2001/09/21 20:08:51 stuart
* Release 0.2.3
*
* Revision 2.4 2001/09/20 16:18:16 stuart
* libmilter checks in_eom state, so we don't have to.
*
* Revision 2.3 2001/09/19 06:02:33 stuart
* Make more stuff static.
*
* Revision 2.1 2001/09/19 04:24:13 stuart
* Use extension type to track context in python.
*
* Revision 1.4 2001/09/18 18:48:28 stuart
* clear private data reference in _clear_context
*
* Revision 1.3 2001/09/15 04:19:37 stuart
* nasty off by 1 mem overwrite bugs in wrap_env
* generic_set_callback
*
* Revision 1.2 2001/09/15 03:15:39 stuart
* several bugs fixed, works smoothly
*
*/ */
#ifndef MAX_ML_REPLY #ifndef MAX_ML_REPLY
@@ -282,7 +71,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__) || defined(__sun__) || defined(__GLIBC__) || (defined(__APPLE__) && defined(__MACH__))
#define HAVE_IPV6_RFC2553 #define HAVE_IPV6_RFC2553
#include <arpa/inet.h> #include <arpa/inet.h>
#endif #endif
@@ -343,7 +132,13 @@ static struct MilterCallback {
{ NULL , NULL } { NULL , NULL }
}; };
staticforward struct smfiDesc description; /* forward declaration */ #if PY_MAJOR_VERSION >= 3
static struct smfiDesc description; /* forward declaration */
static PyTypeObject milter_ContextType;
#else
staticforward struct smfiDesc description; /* forward declaration */
staticforward PyTypeObject milter_ContextType;
#endif
static PyObject *MilterError; static PyObject *MilterError;
/* The interpreter instance that called milter.main */ /* The interpreter instance that called milter.main */
@@ -355,8 +150,6 @@ typedef struct {
static milter_Diag diag; static milter_Diag diag;
staticforward PyTypeObject milter_ContextType;
typedef struct { typedef struct {
PyObject_HEAD PyObject_HEAD
SMFICTX *ctx; /* libmilter thread state */ SMFICTX *ctx; /* libmilter thread state */
@@ -586,7 +379,7 @@ Sets the Python function invoked for each body chunk. There may\n\
be multiple body chunks passed to the filter. End-of-lines are\n\ be multiple body chunks passed to the filter. End-of-lines are\n\
represented as received from SMTP (normally Carriage-Return/Line-Feed).\n\ represented as received from SMTP (normally Carriage-Return/Line-Feed).\n\
Function takes args (ctx, chunk) -> int\n\ Function takes args (ctx, chunk) -> int\n\
chunk -> String - body data"; chunk -> bytes - body data";
static PyObject * static PyObject *
milter_set_body_callback(PyObject *self, PyObject *args) { milter_set_body_callback(PyObject *self, PyObject *args) {
@@ -700,7 +493,11 @@ _generic_wrapper(milter_ContextObject *self, PyObject *cb, PyObject *arglist) {
result = PyEval_CallObject(cb, arglist); result = PyEval_CallObject(cb, arglist);
Py_DECREF(arglist); Py_DECREF(arglist);
if (result == NULL) return _report_exception(self); if (result == NULL) return _report_exception(self);
#if PY_MAJOR_VERSION >= 3
if (!PyLong_Check(result)) {
#else
if (!PyInt_Check(result)) { if (!PyInt_Check(result)) {
#endif
const struct MilterCallback *p; const struct MilterCallback *p;
const char *cbname = "milter"; const char *cbname = "milter";
char buf[40]; char buf[40];
@@ -715,7 +512,11 @@ _generic_wrapper(milter_ContextObject *self, PyObject *cb, PyObject *arglist) {
PyErr_SetString(MilterError,buf); PyErr_SetString(MilterError,buf);
return _report_exception(self); return _report_exception(self);
} }
#if PY_MAJOR_VERSION >= 3
retval = PyLong_AS_LONG(result);
#else
retval = PyInt_AS_LONG(result); retval = PyInt_AS_LONG(result);
#endif
Py_DECREF(result); Py_DECREF(result);
_release_thread(self->t); _release_thread(self->t);
return retval; return retval;
@@ -732,7 +533,11 @@ makeipaddr(struct sockaddr_in *addr) {
sprintf(buf, "%d.%d.%d.%d", sprintf(buf, "%d.%d.%d.%d",
(int) (x>>24) & 0xff, (int) (x>>16) & 0xff, (int) (x>>24) & 0xff, (int) (x>>16) & 0xff,
(int) (x>> 8) & 0xff, (int) (x>> 0) & 0xff); (int) (x>> 8) & 0xff, (int) (x>> 0) & 0xff);
#if PY_MAJOR_VERSION >= 3
return PyUnicode_FromString(buf);
#else
return PyString_FromString(buf); return PyString_FromString(buf);
#endif
} }
#ifdef HAVE_IPV6_SUPPORT #ifdef HAVE_IPV6_SUPPORT
@@ -740,8 +545,13 @@ static PyObject *
makeip6addr(struct sockaddr_in6 *addr) { makeip6addr(struct sockaddr_in6 *addr) {
char buf[100]; /* must be at least INET6_ADDRSTRLEN + 1 */ char buf[100]; /* must be at least INET6_ADDRSTRLEN + 1 */
const char *s = inet_ntop(AF_INET6, &addr->sin6_addr, buf, sizeof buf); const char *s = inet_ntop(AF_INET6, &addr->sin6_addr, buf, sizeof buf);
if (s) return PyString_FromString(s); #if PY_MAJOR_VERSION >= 3
return PyString_FromString("inet6:unknown"); if (s) return PyUnicode_FromString(s);
return PyUnicode_FromString("inet6:unknown");
#else
if (s) return PyString_FromString(s);
return PyString_FromString("inet6:unknown");
#endif
} }
#endif #endif
@@ -832,7 +642,11 @@ generic_env_wrapper(SMFICTX *ctx, PyObject*cb, char **argv) {
for (i=0;i<count;i++) { for (i=0;i<count;i++) {
/* There's some error checking performed in do_mkvalue() for a string */ /* There's some error checking performed in do_mkvalue() for a string */
/* that's not currently done here - it probably should be */ /* that's not currently done here - it probably should be */
#if PY_MAJOR_VERSION >= 3
PyObject *o = PyUnicode_FromStringAndSize(argv[i], strlen(argv[i]));
#else
PyObject *o = PyString_FromStringAndSize(argv[i], strlen(argv[i])); PyObject *o = PyString_FromStringAndSize(argv[i], strlen(argv[i]));
#endif
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 +703,11 @@ milter_wrap_body(SMFICTX *ctx, u_char *bodyp, size_t bodylen) {
c = _get_context(ctx); c = _get_context(ctx);
if (!c) return SMFIS_TEMPFAIL; if (!c) return SMFIS_TEMPFAIL;
/* Unclear whether this should be s#, z#, or t# */ /* Unclear whether this should be s#, z#, or t# */
#if PY_MAJOR_VERSION >= 3
arglist = Py_BuildValue("(Oy#)", c, bodyp, bodylen);
#else
arglist = Py_BuildValue("(Os#)", c, bodyp, bodylen); arglist = Py_BuildValue("(Os#)", c, bodyp, bodylen);
#endif
return _generic_wrapper(c, body_callback, arglist); return _generic_wrapper(c, body_callback, arglist);
} }
@@ -950,20 +768,17 @@ milter_wrap_negotiate(SMFICTX *ctx,
rc = _generic_wrapper(c, negotiate_callback, arglist); rc = _generic_wrapper(c, negotiate_callback, arglist);
c->t = t; c->t = t;
if (rc == SMFIS_CONTINUE) { 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 *pa[4] = { pf0,pf1,pf2,pf3 };
unsigned long fa[4] = { f0,f1,f2,f3 }; unsigned long fa[4] = { f0,f1,f2,f3 };
int len = PyList_Size(optlist); int len = PyList_Size(optlist);
int i; int i;
for (i = 0; i < 4; ++i) { for (i = 0; i < 4; ++i) {
*pa[i] = (i <= len) *pa[i] = (i <= len)
? PyInt_AsUnsignedLongMask(PyList_GET_ITEM(optlist,i)) #if PY_MAJOR_VERSION >= 3
? PyLong_AsUnsignedLongMask(PyList_GET_ITEM(optlist,i))
#else
? PyInt_AsUnsignedLongMask(PyList_GET_ITEM(optlist,i))
#endif
: fa[i]; : fa[i];
} }
if (PyErr_Occurred()) { if (PyErr_Occurred()) {
@@ -971,7 +786,6 @@ milter_wrap_negotiate(SMFICTX *ctx,
PyErr_Clear(); PyErr_Clear();
rc = SMFIS_REJECT; rc = SMFIS_REJECT;
} }
#endif
} }
else if (rc != SMFIS_ALL_OPTS) else if (rc != SMFIS_ALL_OPTS)
rc = SMFIS_REJECT; rc = SMFIS_REJECT;
@@ -1331,7 +1145,7 @@ Change/delete a header in the message. \n\
It is not checked for standards compliance; the mail filter\n\ It is not checked for standards compliance; the mail filter\n\
must ensure that no protocols are violated as a result of adding this header.\n\ must ensure that no protocols are violated as a result of adding this header.\n\
field - header field name\n\ field - header field name\n\
int - the Nth occurence of this header\n\ int - the Nth occurrence of this header\n\
value - header field value\n\ value - header field value\n\
field and value are strings.\n\ field and value are strings.\n\
This function can only be called from the EOM callback."; This function can only be called from the EOM callback.";
@@ -1551,10 +1365,12 @@ static PyMethodDef context_methods[] = {
{ NULL, NULL } { NULL, NULL }
}; };
#if PY_MAJOR_VERSION < 3
static PyObject * static PyObject *
milter_Context_getattr(PyObject *self, char *name) { milter_Context_getattr(PyObject *self, char *name) {
return Py_FindMethod(context_methods, self, name); return Py_FindMethod(context_methods, self, name);
} }
#endif
static struct smfiDesc description = { /* Set some reasonable defaults */ static struct smfiDesc description = { /* Set some reasonable defaults */
"pythonfilter", "pythonfilter",
@@ -1604,14 +1420,23 @@ static PyMethodDef milter_methods[] = {
}; };
static PyTypeObject milter_ContextType = { static PyTypeObject milter_ContextType = {
#if PY_MAJOR_VERSION >= 3
PyVarObject_HEAD_INIT(&PyType_Type,0)
"milter.Context",
#else
PyObject_HEAD_INIT(&PyType_Type) PyObject_HEAD_INIT(&PyType_Type)
0, 0,
"milterContext", "milterContext",
#endif
sizeof(milter_ContextObject), sizeof(milter_ContextObject),
0, 0,
milter_Context_dealloc, /* tp_dealloc */ milter_Context_dealloc, /* tp_dealloc */
0, /* tp_print */ 0, /* tp_print */
#if PY_MAJOR_VERSION >= 3
0, /* tp_getattr */
#else
milter_Context_getattr, /* tp_getattr */ milter_Context_getattr, /* tp_getattr */
#endif
0, /* tp_setattr */ 0, /* tp_setattr */
0, /* tp_compare */ 0, /* tp_compare */
0, /* tp_repr */ 0, /* tp_repr */
@@ -1625,6 +1450,15 @@ static PyTypeObject milter_ContextType = {
0, /* tp_setattro */ 0, /* tp_setattro */
0, /* tp_as_buffer */ 0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT, /* tp_flags */ Py_TPFLAGS_DEFAULT, /* tp_flags */
#if PY_MAJOR_VERSION >= 3
NULL, /* Documentation string */
0, /* call function for all accessible objects */
0, /* delete references to contained objects */
0, /* rich comparisons */
0, /* weak reference enabler */
0, 0, /* Iterators */
context_methods, /* Attribute descriptor and subclassing stuff */
#endif
}; };
static const char milter_documentation[] = static const char milter_documentation[] =
@@ -1634,17 +1468,45 @@ 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) {
#if PY_MAJOR_VERSION >= 3
PyObject *v = PyLong_FromLong(val);
#else
PyObject *v = PyInt_FromLong(val); PyObject *v = PyInt_FromLong(val);
#endif
PyDict_SetItemString(d,name,v); PyDict_SetItemString(d,name,v);
Py_DECREF(v); Py_DECREF(v);
} }
#if PY_MAJOR_VERSION >= 3
void static struct PyModuleDef moduledef = {
initmilter(void) { PyModuleDef_HEAD_INIT,
"milter", /* m_name */
milter_documentation,/* m_doc */
-1, /* m_size */
milter_methods, /* m_methods */
NULL, /* m_reload */
NULL, /* m_traverse */
NULL, /* m_clear */
NULL, /* m_free */
};
PyMODINIT_FUNC PyInit_milter(void) {
PyObject *m, *d;
if (PyType_Ready(&milter_ContextType) < 0)
return NULL;
m = PyModule_Create(&moduledef);
if (m == NULL) return NULL;
#else
void initmilter(void) {
PyObject *m, *d; PyObject *m, *d;
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);
#endif
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 +1572,7 @@ 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);
#if PY_MAJOR_VERSION >= 3
return m;
#endif
} }
+5 -2
View File
@@ -102,6 +102,7 @@ except:
import socket import socket
import Milter import Milter
import zipfile import zipfile
import sys
import email import email
from email.message import Message from email.message import Message
@@ -401,7 +402,10 @@ class _defang:
# emulate old defang function # emulate old defang function
defang = _defang() defang = _defang()
from sgmllib import SGMLParser as HTMLParser if sys.version < '3.0.0':
from sgmllib import SGMLParser as HTMLParser
else:
from Milter.sgmllib import SGMLParser as HTMLParser
import re import re
declname = re.compile(r'[a-zA-Z][-_.a-zA-Z0-9]*\s*') declname = re.compile(r'[a-zA-Z][-_.a-zA-Z0-9]*\s*')
@@ -534,7 +538,6 @@ def check_html(msg,savname=None):
return Milter.CONTINUE return Milter.CONTINUE
if __name__ == '__main__': if __name__ == '__main__':
import sys
def _list_attach(msg): def _list_attach(msg):
t = msg.get_content_type() t = msg.get_content_type()
p = msg.get_payload(decode=True) p = msg.get_payload(decode=True)
-192
View File
@@ -1,192 +0,0 @@
%if 0%{?rhel} == 7
%define pythonbase python34
%else
%define pythonbase python3
%endif
%define __python python3
%define libdir %{_libdir}/pymilter
%{!?python_sitearch: %define python_sitearch %(%{__python} -c "from distutils.sysconfig import get_python_lib; print get_python_lib(1)")}
Summary: Python interface to sendmail milter API
Name: %{pythonbase}-pymilter
Version: 1.0.1
Release: 1%{dist}
Source: https://github.com/sdgathman/pymilter/archive/pymilter-%{version}.tar.gz
Source1: pymilter.te
# Patch miltermodule to python3
# FIXME: replace with reverse patch at some point (make py3 the default)
Patch: milter.patch
License: GPLv2+
Group: Development/Libraries
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root
Url: http://www.bmsi.com/python/milter.html
# python-2.6.4 gets RuntimeError: not holding the import lock
Requires: %{pythonbase} >= 2.6.5, sendmail-milter >= 8.13
%if 0%{?fedora} >= 23
# Need python2.6 specific pydns, not the version for system python
Recommends: %{pythonbase}-pydns
%endif
# Needed for callbacks, not a core function but highly useful for milters
BuildRequires: ed, %{pythonbase}-devel, sendmail-devel >= 8.13
%description
This is a python extension module to enable python scripts to
attach to sendmail's libmilter functionality. Additional python
modules provide for navigating and modifying MIME parts, sending
DSNs, and doing CBV.
%package selinux
Summary: SELinux policy module for pymilter
Group: System Environment/Base
Requires: policycoreutils, selinux-policy, %{name}
BuildRequires: policycoreutils, checkpolicy
%if 0%{?epel} >= 6
BuildRequires: policycoreutils-python
%else
BuildRequires: policycoreutils-python-utils
%endif
%description selinux
SELinux policy module for using pymilter with sendmail with selinux enforcing
%prep
%setup -q -n pymilter-%{version}
%patch -p1 -b .py3
cp %{SOURCE1} pymilter.te
%build
env CFLAGS="$RPM_OPT_FLAGS" %{__python} setup.py build
checkmodule -m -M -o pymilter.mod pymilter.te
semodule_package -o pymilter.pp -m pymilter.mod
%install
rm -rf $RPM_BUILD_ROOT
%{__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}
# install selinux modules
mkdir -p %{buildroot}%{_datadir}/selinux/targeted
cp -p pymilter.pp %{buildroot}%{_datadir}/selinux/targeted
%files
%defattr(-,root,root,-)
%doc README ChangeLog NEWS TODO CREDITS sample.py milter-template.py
%{python_sitearch}/*
%{libdir}
%dir %attr(0755,mail,mail) %{_localstatedir}/run/milter
%dir %attr(0755,mail,mail) %{_localstatedir}/log/milter
%files selinux
%doc pymilter.te
%{_datadir}/selinux/targeted/*
%clean
rm -rf $RPM_BUILD_ROOT
%post selinux
/usr/sbin/semodule -s targeted -i %{_datadir}/selinux/targeted/pymilter.pp \
&>/dev/null || :
%postun selinux
if [ $1 -eq 0 ] ; then
/usr/sbin/semodule -s targeted -r pymilter &> /dev/null || :
fi
%changelog
* Tue Sep 20 2016 Stuart Gathman <stuart@gathman.org> 1.0.1-1
- Support python3
* Sat Mar 1 2014 Stuart Gathman <stuart@gathman.org> 1.0-2
- Remove start.sh to track EPEL repository, suggest daemonize as replacement
- Selinux subpackage should not care about pymilter version
* Wed Jun 26 2013 Stuart Gathman <stuart@gathman.org> 1.0-1
- Allow ACCEPT as untrapped exception policy
- Optional dir for getaddrset and getaddrdict in Milter.config
- Show registered milter name in untrapped exception message.
- Include selinux subpackage
- Provide Milter.greylist export and Milter.greylist import to migrate data
* Sat Mar 9 2013 Stuart Gathman <stuart@bmsi.com> 0.9.8-1
- Add Milter.test module for unit testing milters.
- Fix typo that prevented setsymlist from being active.
- Change untrapped exception message to:
- "pymilter: untrapped exception in milter app"
* Thu Apr 12 2012 Stuart Gathman <stuart@bmsi.com> 0.9.7-1
- Raise RuntimeError when result != CONTINUE for @noreply and @nocallback
- Remove redundant table in miltermodule
- Fix CNAME chain duplicating TXT records in Milter.dns (from pyspf).
* Sat Feb 25 2012 Stuart Gathman <stuart@bmsi.com> 0.9.6-1
- Raise ValueError on unescaped '%' passed to setreply
- Grace time at end of Greylist window
* Fri Aug 19 2011 Stuart Gathman <stuart@bmsi.com> 0.9.5-1
- Print milter.error for invalid callback return type.
(Since stacktrace is empty, the TypeError exception is confusing.)
- Fix milter-template.py
- Tweak Milter.utils.addr2bin and Milter.dynip to handle IP6
* Tue Mar 02 2010 Stuart Gathman <stuart@bmsi.com> 0.9.4-1
- Handle IP6 in Milter.utils.iniplist()
- python-2.6
* Thu Jul 02 2009 Stuart Gathman <stuart@bmsi.com> 0.9.3-1
- Handle source route in Milter.utils.parse_addr()
- Fix default arg in chgfrom.
- Disable negotiate callback for libmilter < 8.14.3 (1,0,1)
* 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
+172 -62
View File
@@ -1,46 +1,72 @@
%define __python python2 # we don't want to provide private python extension libs
%if 0%{?rhel} == 6 %global sum Python interface to sendmail milter API
%define pythonbase python %global __provides_exclude_from ^(%{python2_sitearch})/.*\\.so$
%if 0%{?epel} == 7
%global python3 python34
%else %else
%define pythonbase python2 %global python3 python3
%endif %endif
%define libdir %{_libdir}/pymilter Summary: %{sum}
%{!?python_sitearch: %define python_sitearch %(%{__python} -c "from distutils.sysconfig import get_python_lib; print get_python_lib(1)")} Name: python-pymilter
Version: 1.0.4
Summary: Python interface to sendmail milter API Release: 1%{?dist}
Name: %{pythonbase}-pymilter Url: http://bmsi.com/pymilter
Version: 1.0.1
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: tmpfiles-python-pymilter.conf
# Patch miltermodule to python3 # remove unit tests that require network for check
# FIXME: replace with reverse patch at some point (make py3 the default) Patch: pymilter-check.patch
Patch: milter.patch
License: GPLv2+ License: GPLv2+
Group: Development/Libraries Group: Development/Libraries
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root BuildRequires: python2-devel, %{python3}-devel, sendmail-devel >= 8.13
Url: http://www.bmsi.com/python/milter.html
# python-2.6.4 gets RuntimeError: not holding the import lock # python-2.6.4 gets RuntimeError: not holding the import lock
Requires: %{pythonbase} >= 2.6.5, sendmail-milter >= 8.13
%if 0%{?fedora} >= 23
# Need python2.6 specific pydns, not the version for system python # Need python2.6 specific pydns, not the version for system python
Recommends: %{pythonbase}-pydns BuildRequires: gcc
%endif
# Needed for callbacks, not a core function but highly useful for milters
BuildRequires: ed, %{pythonbase}-devel, sendmail-devel >= 8.13
%description %global _description\
This is a python extension module to enable python scripts to This is a python extension module to enable python scripts to\
attach to sendmail's libmilter functionality. Additional python attach to sendmail's libmilter functionality. Additional python\
modules provide for navigating and modifying MIME parts, sending modules provide for navigating and modifying MIME parts, sending\
DSNs, and doing CBV. DSNs, and doing CBV.
%description %_description
%package -n python2-pymilter
Summary: %{sum}
%if 0%{?epel} >= 6
Requires: python-pydns
%else
Requires: python2-pydns
%endif
Requires: %{name}-common = %{version}-%{release}
%{?python_provide:%python_provide python2-pymilter}
%description -n python2-pymilter %_description
%package -n %{python3}-pymilter
Summary: %{sum}
%if 0%{?fedora} >= 26
Requires: %{python3}-py3dns
%endif
Requires: %{name}-common = %{version}-%{release}
%{?python_provide:%python_provide %{python3}-pymilter}
%description -n %{python3}-pymilter %_description
%package common
Summary: Common files and directories for python milters
BuildArch: noarch
%description common
Common files and directories used for python milters
%package selinux %package selinux
Summary: SELinux policy module for pymilter Summary: SELinux policy module for pymilter
Group: System Environment/Base Group: System Environment/Base
Requires: policycoreutils, selinux-policy, %{name} Requires: policycoreutils, selinux-policy-targeted
BuildRequires: policycoreutils, checkpolicy Requires: %{name} = %{version}-%{release}
BuildArch: noarch
BuildRequires: policycoreutils, checkpolicy, selinux-policy-devel
%if 0%{?epel} >= 6 %if 0%{?epel} >= 6
BuildRequires: policycoreutils-python BuildRequires: policycoreutils-python
%else %else
@@ -48,66 +74,150 @@ BuildRequires: policycoreutils-python-utils
%endif %endif
%description selinux %description selinux
SELinux policy module for using pymilter with sendmail with selinux enforcing Give sendmail_t additional access to stream sockets used to communicate
with milters.
%prep %prep
%setup -q -n pymilter-%{version} %setup -q -n pymilter-pymilter-%{version}
cp %{SOURCE1} pymilter.te %patch -p1 -b .check
%build %build
env CFLAGS="$RPM_OPT_FLAGS" %{__python} setup.py build %py2_build
#patch -p1 -b -z .py3 <milter.patch # not needed since 1.0.3
%py3_build
checkmodule -m -M -o pymilter.mod pymilter.te checkmodule -m -M -o pymilter.mod pymilter.te
semodule_package -o pymilter.pp -m pymilter.mod semodule_package -o pymilter.pp -m pymilter.mod
%install %install
rm -rf $RPM_BUILD_ROOT %py2_install
%{__python} setup.py install --root=$RPM_BUILD_ROOT %py3_install
mkdir -p $RPM_BUILD_ROOT%{_localstatedir}/run/milter
mkdir -p $RPM_BUILD_ROOT%{_localstatedir}/log/milter mkdir -p %{buildroot}/run/milter
mkdir -p $RPM_BUILD_ROOT%{libdir} mkdir -p %{buildroot}%{_localstatedir}/log/milter
mkdir -p %{buildroot}%{_libexecdir}/milter
mkdir -p %{buildroot}%{_prefix}/lib/tmpfiles.d
install -m 0644 %{SOURCE1} %{buildroot}%{_prefix}/lib/tmpfiles.d/%{name}.conf
# install selinux modules # install selinux modules
mkdir -p %{buildroot}%{_datadir}/selinux/targeted mkdir -p %{buildroot}%{_datadir}/selinux/targeted
cp -p pymilter.pp %{buildroot}%{_datadir}/selinux/targeted cp -p pymilter.pp %{buildroot}%{_datadir}/selinux/targeted
%files %check
%defattr(-,root,root,-) py2path=$(ls -d build/lib.linux-*-2.*)
py3path=$(ls -d build/lib.linux-*-3.*)
PYTHONPATH=${py2path}:. python2 test.py &&
PYTHONPATH=${py3path}:. python3 test.py
%files -n python2-pymilter
%license COPYING
%doc README ChangeLog NEWS TODO CREDITS sample.py milter-template.py %doc README ChangeLog NEWS TODO CREDITS sample.py milter-template.py
%{python_sitearch}/* %{python2_sitearch}/*
%{libdir}
%dir %attr(0755,mail,mail) %{_localstatedir}/run/milter %files -n %{python3}-pymilter
%license COPYING
%doc README ChangeLog NEWS TODO CREDITS sample.py milter-template.py
%{python3_sitearch}/*
%files common
%dir %{_libexecdir}/milter
%{_prefix}/lib/tmpfiles.d/%{name}.conf
%dir %attr(0755,mail,mail) %{_localstatedir}/log/milter %dir %attr(0755,mail,mail) %{_localstatedir}/log/milter
%dir %attr(0755,mail,mail) /run/milter
%files selinux %files selinux
%doc pymilter.te %doc pymilter.te
%{_datadir}/selinux/targeted/* %{_datadir}/selinux/targeted/*
%clean
rm -rf $RPM_BUILD_ROOT
%post selinux %post selinux
/usr/sbin/semodule -s targeted -i %{_datadir}/selinux/targeted/pymilter.pp \ %{_sbindir}/semodule -s targeted -i %{_datadir}/selinux/targeted/pymilter.pp \
&>/dev/null || : &>/dev/null || :
%postun selinux %postun selinux
if [ $1 -eq 0 ] ; then if [ $1 -eq 0 ] ; then
/usr/sbin/semodule -s targeted -r pymilter &> /dev/null || : %{_sbindir}/semodule -s targeted -r pymilter &> /dev/null || :
fi fi
%changelog %changelog
* Tue Sep 20 2016 Stuart Gathman <stuart@gathman.org> 1.0.1-1 * Wed Apr 17 2019 Stuart Gathman <stuart@gathman.org> - 1.0.4-1
- Support python3 - New upstream release: cleanup unused files, additional platform support
- Minor doc updates
* Sat Mar 1 2014 Stuart Gathman <stuart@gathman.org> 1.0-2 * Sun Dec 23 2018 Stuart Gathman <stuart@gathman.org> - 1.0.3-1
- Remove start.sh to track EPEL repository, suggest daemonize as replacement - New upstream release
- Selinux subpackage should not care about pymilter version - patch step for python3 no longer required in build
* Wed Jun 26 2013 Stuart Gathman <stuart@gathman.org> 1.0-1 * Sat Aug 4 2018 Stuart Gathman <stuart@gathman.org> - 1.0.2-4
- Allow ACCEPT as untrapped exception policy - Add unit tests to %%check
- Optional dir for getaddrset and getaddrdict in Milter.config
- Show registered milter name in untrapped exception message. * Sat Aug 4 2018 Stuart Gathman <stuart@gathman.org> - 1.0.2-3
- Include selinux subpackage - use libexec instead of libdir
- Provide Milter.greylist export and Milter.greylist import to migrate data
* Sat Aug 4 2018 Stuart Gathman <stuart@gathman.org> - 1.0.2-2
- add python34 subpackage on el7
* Sat Aug 4 2018 Stuart Gathman <stuart@gathman.org> - 1.0.2-1
- build for both python2 and python3
- add selinux policy allowing sendmail_t access to milters
* Tue Jul 17 2018 Miro Hrončok <mhroncok@redhat.com> - 1.0-13
- Update Python macros to new packaging standards
(See https://fedoraproject.org/wiki/Changes/Move_usr_bin_python_into_separate_package)
* Sat Jul 14 2018 Fedora Release Engineering <releng@fedoraproject.org> - 1.0-12
- Rebuilt for https://fedoraproject.org/wiki/Fedora_29_Mass_Rebuild
* Fri Feb 09 2018 Iryna Shcherbina <ishcherb@redhat.com> - 1.0-11
- Update Python 2 dependency declarations to new packaging standards
(See https://fedoraproject.org/wiki/FinalizingFedoraSwitchtoPython3)
* Fri Feb 09 2018 Fedora Release Engineering <releng@fedoraproject.org> - 1.0-10
- Rebuilt for https://fedoraproject.org/wiki/Fedora_28_Mass_Rebuild
* Fri Feb 09 2018 Igor Gnatenko <ignatenkobrain@fedoraproject.org> - 1.0-9
- Escape macros in %%changelog
* Sat Aug 19 2017 Zbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl> - 1.0-8
- Python 2 binary package renamed to python2-pymilter
See https://fedoraproject.org/wiki/FinalizingFedoraSwitchtoPython3
* Thu Aug 03 2017 Fedora Release Engineering <releng@fedoraproject.org> - 1.0-7
- Rebuilt for https://fedoraproject.org/wiki/Fedora_27_Binutils_Mass_Rebuild
* Thu Jul 27 2017 Fedora Release Engineering <releng@fedoraproject.org> - 1.0-6
- Rebuilt for https://fedoraproject.org/wiki/Fedora_27_Mass_Rebuild
>>>>>>> 021796e51e5919812f1c300d1830ef9ed378db2d
* Sat Feb 11 2017 Fedora Release Engineering <releng@fedoraproject.org> - 1.0-5
- Rebuilt for https://fedoraproject.org/wiki/Fedora_26_Mass_Rebuild
* Tue Jul 19 2016 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 1.0-4
- https://fedoraproject.org/wiki/Changes/Automatic_Provides_for_Python_RPM_Packages
* Thu Feb 04 2016 Fedora Release Engineering <releng@fedoraproject.org> - 1.0-3
- Rebuilt for https://fedoraproject.org/wiki/Fedora_24_Mass_Rebuild
* Thu Jun 18 2015 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 1.0-2
- Rebuilt for https://fedoraproject.org/wiki/Fedora_23_Mass_Rebuild
* Sat Sep 27 2014 Paul Wouters <pwouters@redhat.com> - 1.0-1
- Updated to 1.0
- Use tmpfiles and /run
* Sun Aug 17 2014 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 0.9.8-6
- Rebuilt for https://fedoraproject.org/wiki/Fedora_21_22_Mass_Rebuild
* Sat Jun 07 2014 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 0.9.8-5
- Rebuilt for https://fedoraproject.org/wiki/Fedora_21_Mass_Rebuild
* Fri Jan 10 2014 Paul Wouters <pwouters@redhat.com> - 0.9.8-4
- Add COPYING
- Fix buildroot macros and dist macro
* Fri Jan 10 2014 Paul Wouters <pwouters@redhat.com> - 0.9.8-3
- rebuilt with proper file permission
* Tue Jan 07 2014 Paul Wouters <pwouters@redhat.com> - 0.9.8-2
- Fixup for fedora release
* Sat Mar 9 2013 Stuart Gathman <stuart@bmsi.com> 0.9.8-1 * Sat Mar 9 2013 Stuart Gathman <stuart@bmsi.com> 0.9.8-1
- Add Milter.test module for unit testing milters. - Add Milter.test module for unit testing milters.
@@ -115,13 +225,13 @@ fi
- Change untrapped exception message to: - Change untrapped exception message to:
- "pymilter: untrapped exception in milter app" - "pymilter: untrapped exception in milter app"
* Thu Apr 12 2012 Stuart Gathman <stuart@bmsi.com> 0.9.7-1 * Sat Feb 25 2012 Stuart Gathman <stuart@bmsi.com> 0.9.7-1
- Raise RuntimeError when result != CONTINUE for @noreply and @nocallback - Raise RuntimeError when result != CONTINUE for @noreply and @nocallback
- Remove redundant table in miltermodule - Remove redundant table in miltermodule
- Fix CNAME chain duplicating TXT records in Milter.dns (from pyspf). - Fix CNAME chain duplicating TXT records in Milter.dns (from pyspf).
* Sat Feb 25 2012 Stuart Gathman <stuart@bmsi.com> 0.9.6-1 * Sat Feb 25 2012 Stuart Gathman <stuart@bmsi.com> 0.9.6-1
- Raise ValueError on unescaped '%' passed to setreply - Raise ValueError on unescaped '%%' passed to setreply
- Grace time at end of Greylist window - Grace time at end of Greylist window
* Fri Aug 19 2011 Stuart Gathman <stuart@bmsi.com> 0.9.5-1 * Fri Aug 19 2011 Stuart Gathman <stuart@bmsi.com> 0.9.5-1
+8 -1
View File
@@ -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):
+6 -7
View File
@@ -12,12 +12,9 @@ if sys.version < '2.6.5':
libs = ["milter"] libs = ["milter"]
libdirs = ["/usr/lib/libmilter"] # needed for Debian libdirs = ["/usr/lib/libmilter"] # needed for Debian
modules = ["mime"] modules = ["mime"]
if sys.version >= '3':
modules.append("sgmllib")
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.4',
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 +25,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=[
@@ -38,7 +35,9 @@ sending DSNs or doing CBVs.
library_dirs=libdirs, library_dirs=libdirs,
libraries=libs, libraries=libs,
# set MAX_ML_REPLY to 1 for sendmail < 8.13 # set MAX_ML_REPLY to 1 for sendmail < 8.13
define_macros = [ ('MAX_ML_REPLY',32) ] define_macros = [ ('MAX_ML_REPLY',32) ],
# save lots of debugging time testing rfc2553 compliance
extra_compile_args = [ "-Werror=implicit-function-declaration" ]
), ),
], ],
keywords = ['sendmail','milter'], keywords = ['sendmail','milter'],
-19
View File
@@ -1,19 +0,0 @@
#!/bin/sh
appname="$1"
script="${2:-${appname}}"
datadir="/var/lib/milter"
logdir="/var/log/milter"
piddir="/var/run/milter"
libdir="/usr/lib/pymilter"
python="python2.4"
exec >>${logdir}/${appname}.log 2>&1
if test -s ${datadir}/${script}.py; then
cd ${datadir} # use version in data dir if it exists for debugging
elif test -s ${logdir}/${script}.py; then
cd ${logdir} # use version in log dir if it exists for debugging
else
cd ${libdir}
fi
${python} ${script}.py &
echo $! >${piddir}/${appname}.pid
+46 -14
View File
@@ -3,6 +3,7 @@ import Milter
import sample import sample
import mime import mime
from Milter.test import TestBase from Milter.test import TestBase
from Milter.testctx import TestCtx
class TestMilter(TestBase,sample.sampleMilter): class TestMilter(TestBase,sample.sampleMilter):
def __init__(self): def __init__(self):
@@ -11,16 +12,47 @@ class TestMilter(TestBase,sample.sampleMilter):
class BMSMilterTestCase(unittest.TestCase): class BMSMilterTestCase(unittest.TestCase):
def testCtx(self,fname='virus1'):
ctx = TestCtx()
Milter.factory = sample.sampleMilter
ctx._setsymval('{auth_authen}','batman')
ctx._setsymval('{auth_type}','batcomputer')
ctx._setsymval('j','mailhost')
rc = ctx._connect()
self.assertTrue(rc == Milter.CONTINUE)
rc = ctx._feedMsg(fname)
milter = ctx.getpriv()
# self.assertTrue(milter.user == 'batman',"getsymval failed: "+
# "%s != %s"%(milter.user,'batman'))
self.assertEquals(milter.user,'batman')
self.assertTrue(milter.auth_type != 'batcomputer',"setsymlist failed")
self.assertTrue(rc == Milter.ACCEPT)
self.assertTrue(ctx._bodyreplaced,"Message body not replaced")
fp = ctx._body
open('test/'+fname+".tstout","wb").write(fp.getvalue())
#self.assertTrue(fp.getvalue() == open("test/virus1.out","r").read())
fp.seek(0)
msg = mime.message_from_file(fp)
s = msg.get_payload(1).get_payload()
milter.log(s)
ctx._close()
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.assertTrue(rc == Milter.CONTINUE)
rc = milter.feedMsg(fname) rc = milter.feedMsg(fname)
self.failUnless(rc == Milter.ACCEPT) self.assertTrue(milter.user == 'batman',"getsymval failed")
self.failUnless(milter._bodyreplaced,"Message body not replaced") # setsymlist not working in TestBase
#self.assertTrue(milter.auth_type != 'batcomputer',"setsymlist failed")
self.assertTrue(rc == Milter.ACCEPT)
self.assertTrue(milter._bodyreplaced,"Message body not replaced")
fp = milter._body fp = milter._body
open('test/'+fname+".tstout","wb").write(fp.getvalue()) open('test/'+fname+".tstout","wb").write(fp.getvalue())
#self.failUnless(fp.getvalue() == open("test/virus1.out","r").read()) #self.assertTrue(fp.getvalue() == open("test/virus1.out","r").read())
fp.seek(0) fp.seek(0)
msg = mime.message_from_file(fp) msg = mime.message_from_file(fp)
s = msg.get_payload(1).get_payload() s = msg.get_payload(1).get_payload()
@@ -31,8 +63,8 @@ class BMSMilterTestCase(unittest.TestCase):
milter = TestMilter() milter = TestMilter()
milter.connect('somehost') milter.connect('somehost')
rc = milter.feedMsg(fname) rc = milter.feedMsg(fname)
self.failUnless(rc == Milter.ACCEPT) self.assertTrue(rc == Milter.ACCEPT)
self.failIf(milter._bodyreplaced,"Milter needlessly replaced body.") self.assertFalse(milter._bodyreplaced,"Milter needlessly replaced body.")
fp = milter._body fp = milter._body
open('test/'+fname+".tstout","wb").write(fp.getvalue()) open('test/'+fname+".tstout","wb").write(fp.getvalue())
milter.close() milter.close()
@@ -41,18 +73,18 @@ class BMSMilterTestCase(unittest.TestCase):
milter = TestMilter() milter = TestMilter()
milter.connect('somehost') milter.connect('somehost')
rc = milter.feedMsg('samp1') rc = milter.feedMsg('samp1')
self.failUnless(rc == Milter.ACCEPT) self.assertTrue(rc == Milter.ACCEPT)
self.failIf(milter._bodyreplaced,"Milter needlessly replaced body.") self.assertFalse(milter._bodyreplaced,"Milter needlessly replaced body.")
rc = milter.feedMsg("virus3") rc = milter.feedMsg("virus3")
self.failUnless(rc == Milter.ACCEPT) self.assertTrue(rc == Milter.ACCEPT)
self.failUnless(milter._bodyreplaced,"Message body not replaced") self.assertTrue(milter._bodyreplaced,"Message body not replaced")
fp = milter._body fp = milter._body
open("test/virus3.tstout","wb").write(fp.getvalue()) open("test/virus3.tstout","wb").write(fp.getvalue())
#self.failUnless(fp.getvalue() == open("test/virus3.out","r").read()) #self.assertTrue(fp.getvalue() == open("test/virus3.out","r").read())
rc = milter.feedMsg("virus6") rc = milter.feedMsg("virus6")
self.failUnless(rc == Milter.ACCEPT) self.assertTrue(rc == Milter.ACCEPT)
self.failUnless(milter._bodyreplaced,"Message body not replaced") self.assertTrue(milter._bodyreplaced,"Message body not replaced")
self.failUnless(milter._headerschanged,"Message headers not adjusted") self.assertTrue(milter._headerschanged,"Message headers not adjusted")
fp = milter._body fp = milter._body
open("test/virus6.tstout","wb").write(fp.getvalue()) open("test/virus6.tstout","wb").write(fp.getvalue())
milter.close() milter.close()
+12 -7
View File
@@ -21,30 +21,35 @@ class AddrCacheTestCase(unittest.TestCase):
cache['foo@bar.com'] = None cache['foo@bar.com'] = None
cache.addperm('baz@bar.com') cache.addperm('baz@bar.com')
cache['temp@bar.com'] = 'testing' cache['temp@bar.com'] = 'testing'
self.failUnless(cache.has_key('foo@bar.com')) self.assertTrue(cache.has_key('foo@bar.com'))
self.failUnless(not cache.has_key('hello@bar.com')) self.assertTrue(not cache.has_key('hello@bar.com'))
self.failUnless('baz@bar.com' in cache) self.assertTrue('baz@bar.com' in cache)
self.assertEquals(cache['temp@bar.com'],'testing') self.assertEquals(cache['temp@bar.com'],'testing')
s = open(self.fname).readlines() s = open(self.fname).readlines()
self.failUnless(len(s) == 2) self.assertTrue(len(s) == 2)
self.failUnless(s[0].startswith('foo@bar.com ')) self.assertTrue(s[0].startswith('foo@bar.com '))
self.assertEquals(s[1].strip(),'baz@bar.com') self.assertEquals(s[1].strip(),'baz@bar.com')
# check that new result overrides old # check that new result overrides old
cache['temp@bar.com'] = None cache['temp@bar.com'] = None
self.failUnless(not cache['temp@bar.com']) self.assertTrue(not cache['temp@bar.com'])
def testDomain(self): def testDomain(self):
with open(self.fname,'w') as fp: with open(self.fname,'w') as fp:
print('spammer.com',file=fp) print('spammer.com',file=fp)
cache = AddrCache(fname=self.fname) cache = AddrCache(fname=self.fname)
cache.load(self.fname,30) cache.load(self.fname,30)
self.failUnless('spammer.com' in cache) self.assertTrue('spammer.com' in cache)
def testParseHeader(self): def testParseHeader(self):
s='=?UTF-8?B?TGFzdCBGZXcgQ29sZHBsYXkgQWxidW0gQXJ0d29ya3MgQXZhaWxhYmxlAA?=' s='=?UTF-8?B?TGFzdCBGZXcgQ29sZHBsYXkgQWxidW0gQXJ0d29ya3MgQXZhaWxhYmxlAA?='
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))