Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4b1a24df60 |
@@ -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 = 0.9.6
|
PROJECT_NUMBER = 0.9.5
|
||||||
|
|
||||||
# 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.
|
||||||
|
|||||||
+19
-96
@@ -8,15 +8,13 @@
|
|||||||
# Copyright 2001,2009 Business Management Systems, Inc.
|
# Copyright 2001,2009 Business Management Systems, Inc.
|
||||||
# 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.
|
||||||
|
|
||||||
__version__ = '0.9.8'
|
__version__ = '0.9.5'
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import re
|
|
||||||
import milter
|
import milter
|
||||||
import thread
|
import thread
|
||||||
|
|
||||||
from milter import *
|
from milter import *
|
||||||
from functools import wraps
|
|
||||||
|
|
||||||
_seq_lock = thread.allocate_lock()
|
_seq_lock = thread.allocate_lock()
|
||||||
_seq = 0
|
_seq = 0
|
||||||
@@ -49,9 +47,6 @@ OPTIONAL_CALLBACKS = {
|
|||||||
'header':(P_NR_HDR,P_NOHDRS)
|
'header':(P_NR_HDR,P_NOHDRS)
|
||||||
}
|
}
|
||||||
|
|
||||||
## @private
|
|
||||||
R = re.compile(r'%+')
|
|
||||||
|
|
||||||
## @private
|
## @private
|
||||||
def decode_mask(bits,names):
|
def decode_mask(bits,names):
|
||||||
t = [ (s,getattr(milter,s)) for s in names]
|
t = [ (s,getattr(milter,s)) for s in names]
|
||||||
@@ -142,12 +137,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__)
|
||||||
def wrapper(self,*args):
|
return func
|
||||||
if func(self,*args) != CONTINUE:
|
|
||||||
raise RuntimeError('%s return code must be CONTINUE with @nocallback'
|
|
||||||
% func.__name__)
|
|
||||||
return CONTINUE
|
|
||||||
return wrapper
|
|
||||||
|
|
||||||
## Function decorator to disable callback reply.
|
## Function decorator to disable callback reply.
|
||||||
# If the MTA supports it, tells the MTA not to wait for a reply from
|
# If the MTA supports it, tells the MTA not to wait for a reply from
|
||||||
@@ -162,14 +152,9 @@ def noreply(func):
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
'@noreply applied to non-optional method: '+func.__name__)
|
'@noreply applied to non-optional method: '+func.__name__)
|
||||||
@wraps(func)
|
|
||||||
def wrapper(self,*args):
|
def wrapper(self,*args):
|
||||||
rc = func(self,*args)
|
rc = func(self,*args)
|
||||||
if self._protocol & nr_mask:
|
if self._protocol & nr_mask: return NOREPLY
|
||||||
if rc != CONTINUE:
|
|
||||||
raise RuntimeError('%s return code must be CONTINUE with @noreply'
|
|
||||||
% func.__name__)
|
|
||||||
return NOREPLY
|
|
||||||
return rc
|
return rc
|
||||||
wrapper.milter_protocol = nr_mask
|
wrapper.milter_protocol = nr_mask
|
||||||
return wrapper
|
return wrapper
|
||||||
@@ -227,7 +212,7 @@ class Base(object):
|
|||||||
# Some optional actions may be disabled by calling milter.set_flags(), or
|
# Some optional actions may be disabled by calling milter.set_flags(), or
|
||||||
# by overriding the negotiate callback. The bits include:
|
# by overriding the negotiate callback. The bits include:
|
||||||
# <code>ADDHDRS,CHGBODY,MODBODY,ADDRCPT,ADDRCPT_PAR,DELRCPT
|
# <code>ADDHDRS,CHGBODY,MODBODY,ADDRCPT,ADDRCPT_PAR,DELRCPT
|
||||||
# CHGHDRS,QUARANTINE,CHGFROM,SETSYMLIST</code>.
|
# CHGHDRS,QUARANTINE,CHGFROM,SETSMLIST</code>.
|
||||||
# The <code>Milter.CURR_ACTS</code> bitmask is all actions
|
# The <code>Milter.CURR_ACTS</code> bitmask is all actions
|
||||||
# known when the milter module was compiled.
|
# known when the milter module was compiled.
|
||||||
# Application code can also inspect this field to determine
|
# Application code can also inspect this field to determine
|
||||||
@@ -262,9 +247,7 @@ class Base(object):
|
|||||||
|
|
||||||
## Defined by subclasses to write log messages.
|
## Defined by subclasses to write log messages.
|
||||||
def log(self,*msg): pass
|
def log(self,*msg): pass
|
||||||
## Called for each connection to the MTA. Called by the
|
## Called for each connection to the MTA.
|
||||||
# <a href="https://www.milter.org/developers/api/xxfi_connect">
|
|
||||||
# xxfi_connect</a> callback.
|
|
||||||
# The <code>hostname</code> provided by the local MTA is either
|
# The <code>hostname</code> provided by the local MTA is either
|
||||||
# the PTR name or the IP in the form "[1.2.3.4]" if no PTR is available.
|
# the PTR name or the IP in the form "[1.2.3.4]" if no PTR is available.
|
||||||
# The format of hostaddr depends on the socket family:
|
# The format of hostaddr depends on the socket family:
|
||||||
@@ -277,17 +260,6 @@ class Base(object):
|
|||||||
# <dt><code>socket.AF_UNIX</code>
|
# <dt><code>socket.AF_UNIX</code>
|
||||||
# <dd>A string with the socketname
|
# <dd>A string with the socketname
|
||||||
# </dl>
|
# </dl>
|
||||||
# To vary behavior based on what port the client connected to,
|
|
||||||
# for example skipping blacklist checks for port 587 (which must
|
|
||||||
# be authenticated), use @link #getsymval getsymval('{daemon_port}') @endlink.
|
|
||||||
# The <code>{daemon_port}</code> macro must be enabled in sendmail.cf
|
|
||||||
# <pre>
|
|
||||||
# O Milter.macros.connect=j, _, {daemon_name}, {daemon_port}, {if_name}, {if_addr}
|
|
||||||
# </pre>
|
|
||||||
# or sendmail.mc
|
|
||||||
# <pre>
|
|
||||||
# define(`confMILTER_MACROS_CONNECT', ``j, _, {daemon_name}, {daemon_port}, {if_name}, {if_addr}'')dnl
|
|
||||||
# </pre>
|
|
||||||
# @param hostname the PTR name or bracketed IP of the SMTP client
|
# @param hostname the PTR name or bracketed IP of the SMTP client
|
||||||
# @param family <code>socket.AF_INET</code>, <code>socket.AF_INET6</code>,
|
# @param family <code>socket.AF_INET</code>, <code>socket.AF_INET6</code>,
|
||||||
# or <code>socket.AF_UNIX</code>
|
# or <code>socket.AF_UNIX</code>
|
||||||
@@ -299,26 +271,12 @@ class Base(object):
|
|||||||
# this almost always results in terminating the connection.
|
# this almost always results in terminating the connection.
|
||||||
@nocallback
|
@nocallback
|
||||||
def hello(self,hostname): return CONTINUE
|
def hello(self,hostname): return CONTINUE
|
||||||
## Called when the SMTP client says MAIL FROM. Called by the
|
## Called when the SMTP client says MAIL FROM.
|
||||||
# <a href="https://www.milter.org/developers/api/xxfi_envfrom">
|
|
||||||
# xxfi_envfrom</a> callback.
|
|
||||||
# Returning REJECT rejects the message, but not the connection.
|
# Returning REJECT rejects the message, but not the connection.
|
||||||
# The sender is the "envelope" from as defined by
|
|
||||||
# <a href="http://tools.ietf.org/html/rfc5321">RFC 5321</a>.
|
|
||||||
# For the From: header (author) defined in
|
|
||||||
# <a href="http://tools.ietf.org/html/rfc5322">RFC 5322</a>,
|
|
||||||
# see @link #header the header callback @endlink.
|
|
||||||
@nocallback
|
@nocallback
|
||||||
def envfrom(self,f,*str): return CONTINUE
|
def envfrom(self,f,*str): return CONTINUE
|
||||||
## Called when the SMTP client says RCPT TO. Called by the
|
## Called when the SMTP client says RCPT TO.
|
||||||
# <a href="https://www.milter.org/developers/api/xxfi_envrcpt">
|
|
||||||
# xxfi_envrcpt</a> callback.
|
|
||||||
# Returning REJECT rejects the current recipient, not the entire message.
|
# Returning REJECT rejects the current recipient, not the entire message.
|
||||||
# The recipient is the "envelope" recipient as defined by
|
|
||||||
# <a href="http://tools.ietf.org/html/rfc5321">RFC 5321</a>.
|
|
||||||
# For recipients defined in
|
|
||||||
# <a href="http://tools.ietf.org/html/rfc5322">RFC 5322</a>,
|
|
||||||
# for example To: or Cc:, see @link #header the header callback @endlink.
|
|
||||||
@nocallback
|
@nocallback
|
||||||
def envrcpt(self,to,*str): return CONTINUE
|
def envrcpt(self,to,*str): return CONTINUE
|
||||||
## Called when the SMTP client says DATA.
|
## Called when the SMTP client says DATA.
|
||||||
@@ -352,7 +310,7 @@ class Base(object):
|
|||||||
## Called when the connection is closed.
|
## Called when the connection is closed.
|
||||||
def close(self): return CONTINUE
|
def close(self): return CONTINUE
|
||||||
|
|
||||||
## Return mask of SMFIP_N* protocol option bits to clear for this class
|
## Return mask of SMFIP_N.. protocol option bits to clear for this class
|
||||||
# The @@nocallback and @@noreply decorators set the
|
# The @@nocallback and @@noreply decorators set the
|
||||||
# <code>milter_protocol</code> function attribute to the protocol mask bit to
|
# <code>milter_protocol</code> function attribute to the protocol mask bit to
|
||||||
# pass to libmilter, causing that callback or its reply to be skipped.
|
# pass to libmilter, causing that callback or its reply to be skipped.
|
||||||
@@ -378,10 +336,7 @@ class Base(object):
|
|||||||
|
|
||||||
## Negotiate milter protocol options. Called by the
|
## Negotiate milter protocol options. Called by the
|
||||||
# <a href="https://www.milter.org/developers/api/xxfi_negotiate">
|
# <a href="https://www.milter.org/developers/api/xxfi_negotiate">
|
||||||
# xffi_negotiate</a> callback. This is an advanced callback,
|
# xffi_negotiate</a> callback.
|
||||||
# do not override unless you know what you are doing. Most
|
|
||||||
# negotiation can be done simply by using the supplied
|
|
||||||
# class and function decorators.
|
|
||||||
# Options are passed as
|
# Options are passed as
|
||||||
# a list of 4 32-bit ints which can be modified and are passed
|
# a list of 4 32-bit ints which can be modified and are passed
|
||||||
# back to libmilter on return.
|
# back to libmilter on return.
|
||||||
@@ -408,59 +363,28 @@ class Base(object):
|
|||||||
## Return the value of an MTA macro. Sendmail macro names
|
## Return the value of an MTA macro. Sendmail macro names
|
||||||
# are either single chars (e.g. "j") or multiple chars enclosed
|
# are either single chars (e.g. "j") or multiple chars enclosed
|
||||||
# in braces (e.g. "{auth_type}"). Macro names are MTA dependent.
|
# in braces (e.g. "{auth_type}"). Macro names are MTA dependent.
|
||||||
# See <a href="https://www.milter.org/developers/api/smfi_getsymval">
|
|
||||||
# smfi_getsymval</a> for default sendmail macros.
|
|
||||||
# @param sym the macro name
|
# @param sym the macro name
|
||||||
def getsymval(self,sym):
|
def getsymval(self,sym):
|
||||||
return self._ctx.getsymval(sym)
|
return self._ctx.getsymval(sym)
|
||||||
|
|
||||||
## Set the SMTP reply code and message.
|
## Set the SMTP reply code and message.
|
||||||
# If the MTA does not support setmlreply, then only the
|
# If the MTA does not support setmlreply, then only the
|
||||||
# first msg line is used. Any '%%' in a message line
|
# first msg line is used.
|
||||||
# must be doubled, or libmilter will silently ignore the setreply.
|
|
||||||
# Beginning with 0.9.6, we test for that case and throw ValueError to avoid
|
|
||||||
# head scratching. What will <i>really</i> irritate you, however,
|
|
||||||
# is that if you carefully double any '%%', your message will be
|
|
||||||
# sent - but with the '%%' still doubled!
|
|
||||||
# See <a href="https://www.milter.org/developers/api/smfi_setreply">
|
|
||||||
# smfi_setreply</a> for more information.
|
|
||||||
# @param rcode The three-digit (RFC 821/2821) SMTP reply code as a string.
|
|
||||||
# rcode cannot be None, and <b>must be a valid 4XX or 5XX reply code</b>.
|
|
||||||
# @param xcode The extended (RFC 1893/2034) reply code. If xcode is None,
|
|
||||||
# no extended code is used. Otherwise, xcode must conform to RFC 1893/2034.
|
|
||||||
# @param msg The text part of the SMTP reply. If msg is None,
|
|
||||||
# an empty message is used.
|
|
||||||
# @param ml Optional additional message lines.
|
|
||||||
def setreply(self,rcode,xcode=None,msg=None,*ml):
|
def setreply(self,rcode,xcode=None,msg=None,*ml):
|
||||||
for m in (msg,)+ml:
|
|
||||||
if 1 in [len(s)&1 for s in R.findall(m)]:
|
|
||||||
raise ValueError("'%' must be doubled: "+m)
|
|
||||||
return self._ctx.setreply(rcode,xcode,msg,*ml)
|
return self._ctx.setreply(rcode,xcode,msg,*ml)
|
||||||
|
|
||||||
## Tell the MTA which macro names will be used.
|
## Tell the MTA which macro names will be used.
|
||||||
# This information can reduce the size of messages received from sendmail,
|
# The <code>Milter.SETSMLIST</code> action flag must be set.
|
||||||
# and hence could reduce bandwidth between sendmail and your milter where
|
|
||||||
# that is a factor. The <code>Milter.SETSYMLIST</code> action flag must be
|
|
||||||
# set. The protocol stages are M_CONNECT, M_HELO, M_ENVFROM, M_ENVRCPT,
|
|
||||||
# M_DATA, M_EOM, M_EOH.
|
|
||||||
#
|
#
|
||||||
# May only be called from negotiate callback.
|
# May only be called from negotiate callback.
|
||||||
# @since 0.9.8, previous version was misspelled!
|
# @since 0.9.2
|
||||||
# @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
|
# @param macros a string with a space delimited list of macros
|
||||||
# @param macros space separated and/or lists of strings
|
def setsmlist(self,stage,macros):
|
||||||
def setsymlist(self,stage,*macros):
|
if not self._actions & SETSMLIST: raise DisabledAction("SETSMLIST")
|
||||||
if not self._actions & SETSYMLIST: raise DisabledAction("SETSYMLIST")
|
if type(macros) in (list,tuple):
|
||||||
a = []
|
macros = ' '.join(macros)
|
||||||
for m in macros:
|
return self._ctx.setsmlist(stage,macros)
|
||||||
try:
|
|
||||||
m = m.encode('utf8')
|
|
||||||
except: pass
|
|
||||||
try:
|
|
||||||
m = m.split(' ')
|
|
||||||
except: pass
|
|
||||||
a += m
|
|
||||||
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.
|
||||||
|
|
||||||
@@ -790,5 +714,4 @@ for priv in ('os','milter','thread','factory','_seq','_seq_lock','__version__'):
|
|||||||
__all__ = __all__.keys()
|
__all__ = __all__.keys()
|
||||||
|
|
||||||
## @example milter-template.py
|
## @example milter-template.py
|
||||||
## @example milter-nomix.py
|
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -10,9 +10,6 @@
|
|||||||
# CBV results.
|
# CBV results.
|
||||||
#
|
#
|
||||||
# $Log$
|
# $Log$
|
||||||
# Revision 1.9 2008/05/08 21:35:57 customdesigned
|
|
||||||
# Allow explicitly whitelisted email from banned_users.
|
|
||||||
#
|
|
||||||
# Revision 1.8 2007/09/03 16:18:45 customdesigned
|
# Revision 1.8 2007/09/03 16:18:45 customdesigned
|
||||||
# Delete unparseable timestamps when loading address cache. These have
|
# Delete unparseable timestamps when loading address cache. These have
|
||||||
# arisen because of failure to parse MAIL FROM properly. Will have to
|
# arisen because of failure to parse MAIL FROM properly. Will have to
|
||||||
|
|||||||
+4
-24
@@ -70,23 +70,15 @@ class Session(object):
|
|||||||
pre: qtype in ['A', 'AAAA', 'MX', 'PTR', 'TXT', 'SPF']
|
pre: qtype in ['A', 'AAAA', 'MX', 'PTR', 'TXT', 'SPF']
|
||||||
post: isinstance(__return__, types.ListType)
|
post: isinstance(__return__, types.ListType)
|
||||||
"""
|
"""
|
||||||
if name.endswith('.'): name = name[:-1]
|
|
||||||
if not reduce(lambda x,y:x and 0 < len(y) < 64, name.split('.'),True):
|
|
||||||
return [] # invalid DNS name (too long or empty)
|
|
||||||
result = self.cache.get( (name, qtype) )
|
result = self.cache.get( (name, qtype) )
|
||||||
cname = None
|
cname = None
|
||||||
if result: return result
|
|
||||||
cnamek = (name,'CNAME')
|
|
||||||
cname = self.cache.get( cnamek )
|
|
||||||
|
|
||||||
if cname:
|
if not result:
|
||||||
cname = cname[0]
|
|
||||||
else:
|
|
||||||
safe2cache = Session.SAFE2CACHE
|
safe2cache = Session.SAFE2CACHE
|
||||||
for k, v in DNSLookup(name, qtype):
|
for k, v in DNSLookup(name, qtype):
|
||||||
if k == cnamek:
|
if k == (name, 'CNAME'):
|
||||||
cname = v
|
cname = v
|
||||||
if k[1] == 'CNAME' or (qtype,k[1]) in safe2cache:
|
if (qtype,k[1]) in safe2cache:
|
||||||
self.cache.setdefault(k, []).append(v)
|
self.cache.setdefault(k, []).append(v)
|
||||||
result = self.cache.get( (name, qtype), [])
|
result = self.cache.get( (name, qtype), [])
|
||||||
if not result and cname:
|
if not result and cname:
|
||||||
@@ -97,22 +89,10 @@ class Session(object):
|
|||||||
raise DNSError('Length of CNAME chain exceeds %d' % MAX_CNAME)
|
raise DNSError('Length of CNAME chain exceeds %d' % MAX_CNAME)
|
||||||
cnames[name] = cname
|
cnames[name] = cname
|
||||||
if cname in cnames:
|
if cname in cnames:
|
||||||
raise DNSError('CNAME loop')
|
raise DNSError, 'CNAME loop'
|
||||||
result = self.dns(cname, qtype, cnames=cnames)
|
result = self.dns(cname, qtype, cnames=cnames)
|
||||||
if result:
|
|
||||||
self.cache[(name,qtype)] = result
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def dns_txt(self, domainname, enc='ascii'):
|
|
||||||
"Get a list of TXT records for a domain name."
|
|
||||||
if domainname:
|
|
||||||
try:
|
|
||||||
return [''.join(s.decode(enc) for s in a)
|
|
||||||
for a in self.dns(domainname, 'TXT')]
|
|
||||||
except UnicodeEncodeError:
|
|
||||||
raise DNSError('Non-ascii character in SPF TXT record.')
|
|
||||||
return []
|
|
||||||
|
|
||||||
DNS.DiscoverNameServers()
|
DNS.DiscoverNameServers()
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
@@ -5,9 +5,6 @@
|
|||||||
# Send DSNs, do call back verification,
|
# Send DSNs, do call back verification,
|
||||||
# and generate DSN messages from a template
|
# and generate DSN messages from a template
|
||||||
# $Log$
|
# $Log$
|
||||||
# Revision 1.22 2011/03/18 20:41:31 customdesigned
|
|
||||||
# Python2.6 SMTP.close() fails when instance never connected.
|
|
||||||
#
|
|
||||||
# Revision 1.21 2011/03/03 05:11:58 customdesigned
|
# Revision 1.21 2011/03/03 05:11:58 customdesigned
|
||||||
# Release 0.9.4
|
# Release 0.9.4
|
||||||
#
|
#
|
||||||
|
|||||||
+9
-37
@@ -18,19 +18,13 @@ def quoteAddress(s):
|
|||||||
class Record(object):
|
class Record(object):
|
||||||
__slots__ = ( 'firstseen', 'lastseen', 'umis', 'cnt' )
|
__slots__ = ( 'firstseen', 'lastseen', 'umis', 'cnt' )
|
||||||
|
|
||||||
def __init__(self,timeinc=0):
|
def __init__(self):
|
||||||
now = time.time() + timeinc
|
now = time.time()
|
||||||
self.firstseen = now
|
self.firstseen = now
|
||||||
self.lastseen = now
|
self.lastseen = now
|
||||||
self.cnt = 0
|
self.cnt = 0
|
||||||
self.umis = None
|
self.umis = None
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return "Grey[%s:%s:%s:%d]" % (
|
|
||||||
time.ctime(self.firstseen),time.ctime(self.lastseen),
|
|
||||||
self.umis,self.cnt
|
|
||||||
)
|
|
||||||
|
|
||||||
class Greylist(object):
|
class Greylist(object):
|
||||||
|
|
||||||
def __init__(self,dbname,grey_time=10,grey_expire=4,grey_retain=36):
|
def __init__(self,dbname,grey_time=10,grey_expire=4,grey_retain=36):
|
||||||
@@ -41,26 +35,7 @@ class Greylist(object):
|
|||||||
self.dbp = shelve.open(dbname,'c',protocol=2)
|
self.dbp = shelve.open(dbname,'c',protocol=2)
|
||||||
self.lock = thread.allocate_lock()
|
self.lock = thread.allocate_lock()
|
||||||
|
|
||||||
def clean(self,timeinc=0):
|
def check(self,ip,sender,recipient):
|
||||||
"Delete records past the retention limit."
|
|
||||||
now = time.time() + timeinc
|
|
||||||
cnt = 0
|
|
||||||
dbp = self.dbp
|
|
||||||
for key, r in dbp.iteritems():
|
|
||||||
#print key,r,time.ctime(now)
|
|
||||||
if now > r.lastseen + self.greylist_retain:
|
|
||||||
self.lock.acquire()
|
|
||||||
try:
|
|
||||||
r = dbp[key]
|
|
||||||
now = time.time() + timeinc
|
|
||||||
if now > r.lastseen + self.greylist_retain:
|
|
||||||
del dbp[key]
|
|
||||||
cnt += 1
|
|
||||||
finally:
|
|
||||||
self.lock.release()
|
|
||||||
return cnt
|
|
||||||
|
|
||||||
def check(self,ip,sender,recipient,timeinc=0):
|
|
||||||
"Return number of allowed messages for greylist triple."
|
"Return number of allowed messages for greylist triple."
|
||||||
sender = quoteAddress(sender)
|
sender = quoteAddress(sender)
|
||||||
recipient = quoteAddress(recipient)
|
recipient = quoteAddress(recipient)
|
||||||
@@ -70,15 +45,15 @@ class Greylist(object):
|
|||||||
dbp = self.dbp
|
dbp = self.dbp
|
||||||
try:
|
try:
|
||||||
r = dbp[key]
|
r = dbp[key]
|
||||||
now = time.time() + timeinc
|
now = time.time()
|
||||||
if now > r.lastseen + self.greylist_retain:
|
if now > r.lastseen + self.greylist_retain:
|
||||||
# expired
|
# expired
|
||||||
log.debug('Expired greylist: %s',key)
|
log.debug('Expired greylist: %s',key)
|
||||||
r = Record(timeinc)
|
r = Record()
|
||||||
elif now < r.firstseen + self.greylist_time + 5:
|
elif now < r.firstseen + self.greylist_time:
|
||||||
# still greylisted
|
# still greylisted
|
||||||
log.debug('Early greylist: %s',key)
|
log.debug('Early greylist: %s',key)
|
||||||
#r = Record(timeinc)
|
#r = Record()
|
||||||
r.lastseen = now
|
r.lastseen = now
|
||||||
elif r.cnt or now < r.firstseen + self.greylist_expire:
|
elif r.cnt or now < r.firstseen + self.greylist_expire:
|
||||||
# in greylist window or active
|
# in greylist window or active
|
||||||
@@ -88,15 +63,12 @@ class Greylist(object):
|
|||||||
else:
|
else:
|
||||||
# passed greylist window
|
# passed greylist window
|
||||||
log.debug('Late greylist: %s',key)
|
log.debug('Late greylist: %s',key)
|
||||||
r = Record(timeinc)
|
r = Record()
|
||||||
dbp[key] = r
|
dbp[key] = r
|
||||||
except:
|
except:
|
||||||
r = Record(timeinc)
|
r = Record()
|
||||||
dbp[key] = r
|
dbp[key] = r
|
||||||
dbp.sync()
|
dbp.sync()
|
||||||
finally:
|
finally:
|
||||||
self.lock.release()
|
self.lock.release()
|
||||||
return r.cnt
|
return r.cnt
|
||||||
|
|
||||||
def close(self):
|
|
||||||
self.dbp.close()
|
|
||||||
|
|||||||
@@ -1,86 +0,0 @@
|
|||||||
import time
|
|
||||||
import logging
|
|
||||||
import urllib
|
|
||||||
import sqlite3
|
|
||||||
import thread
|
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
log = logging.getLogger('milter.greylist')
|
|
||||||
|
|
||||||
_db_lock = thread.allocate_lock()
|
|
||||||
|
|
||||||
class Greylist(object):
|
|
||||||
|
|
||||||
def __init__(self,dbname,grey_time=10,grey_expire=4,grey_retain=36):
|
|
||||||
self.ignoreLastByte = False
|
|
||||||
self.greylist_time = grey_time * 60 # minutes
|
|
||||||
self.greylist_expire = grey_expire * 3600 # hours
|
|
||||||
self.greylist_retain = grey_retain * 24 * 3600 # days
|
|
||||||
self.conn = sqlite3.connect(dbname)
|
|
||||||
self.conn.row_factory = sqlite3.Row
|
|
||||||
try:
|
|
||||||
self.conn.execute('''create table greylist(
|
|
||||||
ip text , sender text, recipient text,
|
|
||||||
firstseen timestamp, lastseen timestamp, cnt integer, umis text,
|
|
||||||
primary key (ip,sender,recipient))''')
|
|
||||||
except: pass
|
|
||||||
|
|
||||||
def clean(self,timeinc=0):
|
|
||||||
"Delete records past the retention limit."
|
|
||||||
now = time.time() + timeinc - self.greylist_retain
|
|
||||||
cur = self.conn.cursor()
|
|
||||||
try:
|
|
||||||
cur.execute('delete from greylist where lastseen < ?',(now,))
|
|
||||||
cnt = cur.rowcount
|
|
||||||
self.conn.commit()
|
|
||||||
finally: cur.close()
|
|
||||||
return cnt
|
|
||||||
|
|
||||||
def check(self,ip,sender,recipient,timeinc=0):
|
|
||||||
"Return number of allowed messages for greylist triple."
|
|
||||||
_db_lock.acquire()
|
|
||||||
cur = self.conn.execute('begin immediate')
|
|
||||||
try:
|
|
||||||
cur.execute('''select firstseen,lastseen,cnt,umis from greylist where
|
|
||||||
ip=? and sender=? and recipient=?''',(ip,sender,recipient))
|
|
||||||
r = cur.fetchone()
|
|
||||||
now = time.time() + timeinc
|
|
||||||
cnt = 0
|
|
||||||
if not r:
|
|
||||||
cur.execute('''insert into
|
|
||||||
greylist(ip,sender,recipient,firstseen,lastseen,cnt,umis)
|
|
||||||
values(?,?,?,?,?,?,?)''', (ip,sender,recipient,now,now,0,None))
|
|
||||||
elif now > r['lastseen'] + self.greylist_retain:
|
|
||||||
# expired
|
|
||||||
log.debug('Expired greylist: %s:%s:%s',ip,sender,recipient)
|
|
||||||
cur.execute('''update greylist set firstseen=?,lastseen=?,cnt=?,umis=?
|
|
||||||
where ip=? and sender=? and recipient=?''',
|
|
||||||
(now,now,0,None,ip,sender,recipient))
|
|
||||||
elif now < r['firstseen'] + self.greylist_time + 5:
|
|
||||||
# still greylisted
|
|
||||||
log.debug('Early greylist: %s:%s:%s',ip,sender,recipient)
|
|
||||||
#r = Record()
|
|
||||||
cur.execute('''update greylist set lastseen=?
|
|
||||||
where ip=? and sender=? and recipient=?''',
|
|
||||||
(now,ip,sender,recipient))
|
|
||||||
elif r['cnt'] or now < r['firstseen'] + self.greylist_expire:
|
|
||||||
# in greylist window or active
|
|
||||||
cnt = r['cnt'] + 1
|
|
||||||
cur.execute('''update greylist set lastseen=?,cnt=?
|
|
||||||
where ip=? and sender=? and recipient=?''',
|
|
||||||
(now,cnt,ip,sender,recipient))
|
|
||||||
log.debug('Active greylist(%d): %s:%s:%s',cnt,ip,sender,recipient)
|
|
||||||
else:
|
|
||||||
# passed greylist window
|
|
||||||
log.debug('Late greylist: %s:%s:%s',ip,sender,recipient)
|
|
||||||
cur.execute('''update greylist set firstseen=?,lastseen=?,cnt=?,umis=?
|
|
||||||
where ip=? and sender=? and recipient=?''',
|
|
||||||
(now,now,0,None,ip,sender,recipient))
|
|
||||||
self.conn.commit()
|
|
||||||
finally:
|
|
||||||
cur.close()
|
|
||||||
_db_lock.release()
|
|
||||||
return cnt
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
self.conn.close()
|
|
||||||
-192
@@ -1,192 +0,0 @@
|
|||||||
## @package Milter.test
|
|
||||||
# A test framework for milters
|
|
||||||
|
|
||||||
import rfc822
|
|
||||||
import StringIO
|
|
||||||
import Milter
|
|
||||||
|
|
||||||
Milter.NOREPLY = Milter.CONTINUE
|
|
||||||
|
|
||||||
## Test mixin for unit testing milter applications.
|
|
||||||
# This mixin overrides many Milter.MilterBase methods
|
|
||||||
# with stub versions that simply record what was done.
|
|
||||||
# @since 0.9.8
|
|
||||||
class TestBase(object):
|
|
||||||
|
|
||||||
def __init__(self,logfile='test/milter.log'):
|
|
||||||
self._protocol = 0
|
|
||||||
self.logfp = open(logfile,"a")
|
|
||||||
## List of recipients deleted
|
|
||||||
self._delrcpt = []
|
|
||||||
## List of recipients added
|
|
||||||
self._addrcpt = []
|
|
||||||
## Macros defined
|
|
||||||
self._macros = { }
|
|
||||||
## 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
|
|
||||||
## Reply codes and messages set by milter
|
|
||||||
self._reply = None
|
|
||||||
## The rfc822 message object for the current email being fed to the milter.
|
|
||||||
self._msg = None
|
|
||||||
self._symlist = [ None, None, None, None, None, None, None ]
|
|
||||||
|
|
||||||
def log(self,*msg):
|
|
||||||
for i in msg: print >>self.logfp, i,
|
|
||||||
print >>self.logfp
|
|
||||||
|
|
||||||
## Set a macro value.
|
|
||||||
# These are retrieved by the milter with getsymval.
|
|
||||||
# @param name the macro name, as passed to getsymval
|
|
||||||
# @param val the macro value
|
|
||||||
def setsymval(self,name,val):
|
|
||||||
self._macros[name] = val
|
|
||||||
|
|
||||||
def getsymval(self,name):
|
|
||||||
# FIXME: track stage, and use _symlist
|
|
||||||
return self._macros.get(name,'')
|
|
||||||
|
|
||||||
def replacebody(self,chunk):
|
|
||||||
if self._body:
|
|
||||||
self._body.write(chunk)
|
|
||||||
self._bodyreplaced = True
|
|
||||||
else:
|
|
||||||
raise IOError,"replacebody not called from eom()"
|
|
||||||
|
|
||||||
# FIXME: rfc822 indexing does not really reflect the way chg/add header
|
|
||||||
# work for a milter
|
|
||||||
def chgheader(self,field,idx,value):
|
|
||||||
if not self._body:
|
|
||||||
raise IOError,"chgheader not called from eom()"
|
|
||||||
self.log('chgheader: %s[%d]=%s' % (field,idx,value))
|
|
||||||
if value == '':
|
|
||||||
del self._msg[field]
|
|
||||||
else:
|
|
||||||
self._msg[field] = value
|
|
||||||
self._headerschanged = True
|
|
||||||
|
|
||||||
def addheader(self,field,value,idx=-1):
|
|
||||||
if not self._body:
|
|
||||||
raise IOError,"addheader not called from eom()"
|
|
||||||
self.log('addheader: %s=%s' % (field,value))
|
|
||||||
self._msg[field] = value
|
|
||||||
self._headerschanged = True
|
|
||||||
|
|
||||||
def delrcpt(self,rcpt):
|
|
||||||
if not self._body:
|
|
||||||
raise IOError,"delrcpt not called from eom()"
|
|
||||||
self._delrcpt.append(rcpt)
|
|
||||||
|
|
||||||
def addrcpt(self,rcpt):
|
|
||||||
if not self._body:
|
|
||||||
raise IOError,"addrcpt not called from eom()"
|
|
||||||
self._addrcpt.append(rcpt)
|
|
||||||
|
|
||||||
## Save the reply codes and messages in self._reply.
|
|
||||||
def setreply(self,rcode,xcode,*msg):
|
|
||||||
self._reply = (rcode,xcode) + msg
|
|
||||||
|
|
||||||
def setsymlist(self,stage,macros):
|
|
||||||
if not self._actions & SETSYMLIST: raise DisabledAction("SETSYMLIST")
|
|
||||||
# not used yet, but just for grins we save the data
|
|
||||||
a = []
|
|
||||||
for m in macros:
|
|
||||||
try:
|
|
||||||
m = m.encode('utf8')
|
|
||||||
except: pass
|
|
||||||
try:
|
|
||||||
m = m.split(' ')
|
|
||||||
except: pass
|
|
||||||
a += m
|
|
||||||
self._symlist[stage] = set(a)
|
|
||||||
|
|
||||||
## Feed a file like object to the milter. Calls envfrom, envrcpt for
|
|
||||||
# each recipient, header for each header field, body for each body
|
|
||||||
# block, and finally eom. A return code from the milter other than
|
|
||||||
# CONTINUE returns immediately with that return code.
|
|
||||||
#
|
|
||||||
# This is a convenience method, a test could invoke the callbacks
|
|
||||||
# in sequence on its own - and for some complex tests, this may
|
|
||||||
# be necessary.
|
|
||||||
# @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 = rfc822.Message(fp)
|
|
||||||
rc = self.envfrom('<%s>'%sender)
|
|
||||||
if rc != Milter.CONTINUE: return rc
|
|
||||||
for rcpt in (rcpt,) + rcpts:
|
|
||||||
rc = self.envrcpt('<%s>'%rcpt)
|
|
||||||
if rc != Milter.CONTINUE: return rc
|
|
||||||
line = None
|
|
||||||
for h in msg.headers:
|
|
||||||
if h[:1].isspace():
|
|
||||||
line = line + h
|
|
||||||
continue
|
|
||||||
if not line:
|
|
||||||
line = h
|
|
||||||
continue
|
|
||||||
s = line.split(': ',1)
|
|
||||||
if len(s) > 1: val = s[1].strip()
|
|
||||||
else: val = ''
|
|
||||||
rc = self.header(s[0],val)
|
|
||||||
if rc != Milter.CONTINUE: return rc
|
|
||||||
line = h
|
|
||||||
if line:
|
|
||||||
s = line.split(': ',1)
|
|
||||||
rc = self.header(s[0],s[1])
|
|
||||||
if rc != Milter.CONTINUE: return rc
|
|
||||||
rc = self.eoh()
|
|
||||||
if rc != Milter.CONTINUE: return rc
|
|
||||||
while 1:
|
|
||||||
buf = fp.read(8192)
|
|
||||||
if len(buf) == 0: break
|
|
||||||
rc = self.body(buf)
|
|
||||||
if rc != Milter.CONTINUE: return rc
|
|
||||||
self._msg = msg
|
|
||||||
self._body = StringIO.StringIO()
|
|
||||||
rc = self.eom()
|
|
||||||
if self._bodyreplaced:
|
|
||||||
body = self._body.getvalue()
|
|
||||||
else:
|
|
||||||
msg.rewindbody()
|
|
||||||
body = msg.fp.read()
|
|
||||||
self._body = StringIO.StringIO()
|
|
||||||
self._body.writelines(msg.headers)
|
|
||||||
self._body.write('\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,'r') as fp:
|
|
||||||
return self.feedFile(fp,sender,*rcpts)
|
|
||||||
|
|
||||||
## Call the connect and helo callbacks.
|
|
||||||
# The helo callback is not called if connect does not return CONTINUE.
|
|
||||||
# @param host the hostname passed to the connect callback
|
|
||||||
# @param helo the hostname passed to the helo callback
|
|
||||||
# @param ip the IP address passed to the connect callback
|
|
||||||
def connect(self,host='localhost',helo='spamrelay',ip='1.2.3.4'):
|
|
||||||
self._body = None
|
|
||||||
self._bodyreplaced = False
|
|
||||||
opts = [ Milter.CURR_ACTS,~0,0,0 ]
|
|
||||||
rc = self.negotiate(opts)
|
|
||||||
rc = super(TestBase,self).connect(host,1,(ip,1234))
|
|
||||||
if rc != Milter.CONTINUE:
|
|
||||||
self.close()
|
|
||||||
return rc
|
|
||||||
rc = self.hello(helo)
|
|
||||||
if rc != Milter.CONTINUE:
|
|
||||||
self.close()
|
|
||||||
return rc
|
|
||||||
+1
-1
@@ -49,7 +49,7 @@ if hasattr(socket,'has_ipv6') and socket.has_ipv6:
|
|||||||
def inet_ntop(s):
|
def inet_ntop(s):
|
||||||
return socket.inet_ntop(socket.AF_INET6,s)
|
return socket.inet_ntop(socket.AF_INET6,s)
|
||||||
def inet_pton(s):
|
def inet_pton(s):
|
||||||
return socket.inet_pton(socket.AF_INET6,s.strip())
|
return socket.inet_pton(socket.AF_INET6,s)
|
||||||
else:
|
else:
|
||||||
from pyip6 import inet_ntop, inet_pton
|
from pyip6 import inet_ntop, inet_pton
|
||||||
|
|
||||||
|
|||||||
+2
-12
@@ -54,12 +54,7 @@ class milterContext(object):
|
|||||||
def chgfrom(self,sender,param=None): pass
|
def chgfrom(self,sender,param=None): pass
|
||||||
## Tell the MTA which macro values we are interested in for a given stage.
|
## Tell the MTA which macro values we are interested in for a given stage.
|
||||||
# Of interest only when you need to squeeze a few more bytes of bandwidth.
|
# Of interest only when you need to squeeze a few more bytes of bandwidth.
|
||||||
# It may only be called from the negotiate callback.
|
def setsmlist(self,stage,macrolist): pass
|
||||||
# The protocol stages are
|
|
||||||
# M_CONNECT, M_HELO, M_ENVFROM, M_ENVRCPT, M_DATA, M_EOM, M_EOH.
|
|
||||||
# Calls <a href="https://www.milter.org/developers/api/smfi_setsymlist">smfi_setsymlist</a>.
|
|
||||||
# @param stage protocol stage in which the macro list should be used
|
|
||||||
def setsymlist(self,stage,macrolist): pass
|
|
||||||
|
|
||||||
class error(Exception): pass
|
class error(Exception): pass
|
||||||
|
|
||||||
@@ -82,12 +77,7 @@ def set_abort_callback(cb): pass
|
|||||||
def set_close_callback(cb): pass
|
def set_close_callback(cb): pass
|
||||||
|
|
||||||
## Sets the return code for untrapped Python exceptions during a callback.
|
## Sets the return code for untrapped Python exceptions during a callback.
|
||||||
# Must be one of TEMPFAIL,REJECT,CONTINUE. The default is TEMPFAIL.
|
# Must be one of TEMPFAIL,REJECT,CONTINUE
|
||||||
# You should not depend on this handler. Your application should
|
|
||||||
# have its own top level exception handler for each callback. You can
|
|
||||||
# then choose your own reply message, log the stack track were you please,
|
|
||||||
# and so on. However, if you miss one, this last ditch handler will
|
|
||||||
# print a standard stack trace to sys.stderr, and return to sendmail.
|
|
||||||
def set_exception_policy(code): pass
|
def set_exception_policy(code): pass
|
||||||
|
|
||||||
## Register python milter with libmilter.
|
## Register python milter with libmilter.
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ web:
|
|||||||
doxygen
|
doxygen
|
||||||
rsync -ravK doc/html/ spidey2.bmsi.com:/Public/pymilter
|
rsync -ravK doc/html/ spidey2.bmsi.com:/Public/pymilter
|
||||||
|
|
||||||
VERSION=0.9.8
|
VERSION=0.9.5
|
||||||
CVSTAG=pymilter-0_9_8
|
CVSTAG=pymilter-0_9_5
|
||||||
PKG=pymilter-$(VERSION)
|
PKG=pymilter-$(VERSION)
|
||||||
SRCTAR=$(PKG).tar.gz
|
SRCTAR=$(PKG).tar.gz
|
||||||
|
|
||||||
|
|||||||
@@ -1,79 +0,0 @@
|
|||||||
## A very simple milter to prevent mixing of internal and external mail.
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
import Milter
|
|
||||||
import time
|
|
||||||
import sys
|
|
||||||
from Milter.utils import parse_addr
|
|
||||||
|
|
||||||
internal_tlds = ["corp", "personal"]
|
|
||||||
|
|
||||||
## Determine if a hostname is internal or not.
|
|
||||||
# True if internal, False otherwise
|
|
||||||
def is_internal(hostname):
|
|
||||||
components = hostname.split(".")
|
|
||||||
return components.pop() in internal_tlds:
|
|
||||||
|
|
||||||
# Determine if internal and external hosts are mixed based on a list
|
|
||||||
# of hostnames
|
|
||||||
def are_mixed(hostnames):
|
|
||||||
hostnames_mapped = map(is_internal, hostnames)
|
|
||||||
|
|
||||||
# Num internals
|
|
||||||
num_internal_hosts = hostnames_mapped.count(True)
|
|
||||||
|
|
||||||
# Num externals
|
|
||||||
num_external_hosts = hostnames_mapped.count(False)
|
|
||||||
|
|
||||||
return num_external_hosts >= 1 and num_internal_hosts >= 1
|
|
||||||
|
|
||||||
class NoMixMilter(Milter.Base):
|
|
||||||
|
|
||||||
def __init__(self): # A new instance with each new connection.
|
|
||||||
self.id = Milter.uniqueID() # Integer incremented with each call.
|
|
||||||
|
|
||||||
|
|
||||||
## def envfrom(self,f,*str):
|
|
||||||
@Milter.noreply
|
|
||||||
def envfrom(self, mailfrom, *str):
|
|
||||||
self.mailfrom = mailfrom
|
|
||||||
self.domains = []
|
|
||||||
t = parse_addr(mailfrom)
|
|
||||||
if len(t) > 1:
|
|
||||||
self.domains.append(t[1])
|
|
||||||
else:
|
|
||||||
self.domains.append('local')
|
|
||||||
self.internal = False
|
|
||||||
return Milter.CONTINUE
|
|
||||||
|
|
||||||
## def envrcpt(self, to, *str):
|
|
||||||
def envrcpt(self, to, *str):
|
|
||||||
self.R.append(to)
|
|
||||||
t = parse_addr(to)
|
|
||||||
if len(t) > 1:
|
|
||||||
self.domains.append(t[1])
|
|
||||||
else:
|
|
||||||
self.domains.append('local')
|
|
||||||
|
|
||||||
if are_mixed(self.domains):
|
|
||||||
# FIXME: log recipients collected in self.mailfrom and self.R
|
|
||||||
self.setreply('550','5.7.1','Mixing internal and external TLDs')
|
|
||||||
return Milter.REJECT
|
|
||||||
|
|
||||||
return Milter.CONTINUE
|
|
||||||
|
|
||||||
def main():
|
|
||||||
socketname = "/var/run/nomixsock"
|
|
||||||
timeout = 600
|
|
||||||
# Register to have the Milter factory create instances of your class:
|
|
||||||
Milter.factory = NoMixMilter
|
|
||||||
print "%s milter startup" % time.strftime('%Y%b%d %H:%M:%S')
|
|
||||||
sys.stdout.flush()
|
|
||||||
Milter.runmilter("nomixfilter",socketname,timeout)
|
|
||||||
logq.put(None)
|
|
||||||
bt.join()
|
|
||||||
print "%s nomix milter shutdown" % time.strftime('%Y%b%d %H:%M:%S')
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
+2
-1
@@ -79,7 +79,7 @@ class myMilter(Milter.Base):
|
|||||||
|
|
||||||
## def envrcpt(self, to, *str):
|
## def envrcpt(self, to, *str):
|
||||||
@Milter.noreply
|
@Milter.noreply
|
||||||
def envrcpt(self, to, *str):
|
def envrcpt(self, recipient, *str):
|
||||||
rcptinfo = to,Milter.dictfromlist(str)
|
rcptinfo = to,Milter.dictfromlist(str)
|
||||||
self.R.append(rcptinfo)
|
self.R.append(rcptinfo)
|
||||||
|
|
||||||
@@ -110,6 +110,7 @@ class myMilter(Milter.Base):
|
|||||||
self.addrcpt('<%s>' % 'spy@example.com')
|
self.addrcpt('<%s>' % 'spy@example.com')
|
||||||
return Milter.ACCEPT
|
return Milter.ACCEPT
|
||||||
|
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
# always called, even when abort is called. Clean up
|
# always called, even when abort is called. Clean up
|
||||||
# any external resources here.
|
# any external resources here.
|
||||||
|
|||||||
+82
-120
@@ -35,25 +35,6 @@ $ python setup.py help
|
|||||||
libraries=["milter","smutil","resolv"]
|
libraries=["milter","smutil","resolv"]
|
||||||
|
|
||||||
* $Log$
|
* $Log$
|
||||||
* 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
|
* Revision 1.28 2011/06/08 23:13:48 customdesigned
|
||||||
* Generate special exception when callback return not int.
|
* Generate special exception when callback return not int.
|
||||||
*
|
*
|
||||||
@@ -276,10 +257,10 @@ $ python setup.py help
|
|||||||
#define HAVE_IPV6_SUPPORT /* use this for #ifdef's later on */
|
#define HAVE_IPV6_SUPPORT /* use this for #ifdef's later on */
|
||||||
/* Now see if it supports the RFC-2553 socket's API spec. Early
|
/* Now see if it supports the RFC-2553 socket's API spec. Early
|
||||||
* IPv6 "prototype" implementations existed before the RFC was
|
* IPv6 "prototype" implementations existed before the RFC was
|
||||||
* published. Unfortunately I know of no good way to do this
|
* published. Unfortunately I know of now good way to do this
|
||||||
* other than with OS-specific tests.
|
* other than with OS-specific tests.
|
||||||
*/
|
*/
|
||||||
#if defined(__FreeBSD_kernel__) || defined(__linux__)
|
#ifdef linux
|
||||||
#define HAVE_IPV6_RFC2553
|
#define HAVE_IPV6_RFC2553
|
||||||
#include <arpa/inet.h>
|
#include <arpa/inet.h>
|
||||||
#endif
|
#endif
|
||||||
@@ -293,51 +274,43 @@ $ python setup.py help
|
|||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
enum callbacks {
|
/* Yes, these are static. If you need multiple different callbacks, */
|
||||||
CONNECT,HELO,ENVFROM,ENVRCPT,HEADER,EOH,BODY,EOM,ABORT,CLOSE,
|
/* it's cleaner to use multiple filters, or convert to OO method calls. */
|
||||||
|
static PyObject *connect_callback = NULL;
|
||||||
|
static PyObject *helo_callback = NULL;
|
||||||
|
static PyObject *envfrom_callback = NULL;
|
||||||
|
static PyObject *envrcpt_callback = NULL;
|
||||||
|
static PyObject *header_callback = NULL;
|
||||||
|
static PyObject *eoh_callback = NULL;
|
||||||
|
static PyObject *body_callback = NULL;
|
||||||
|
static PyObject *eom_callback = NULL;
|
||||||
|
static PyObject *abort_callback = NULL;
|
||||||
|
static PyObject *close_callback = NULL;
|
||||||
#ifdef SMFIS_ALL_OPTS
|
#ifdef SMFIS_ALL_OPTS
|
||||||
UNKNOWN,DATA,NEGOTIATE,
|
static PyObject *unknown_callback = NULL;
|
||||||
|
static PyObject *data_callback = NULL;
|
||||||
|
static PyObject *negotiate_callback = NULL;
|
||||||
#endif
|
#endif
|
||||||
NUMCALLBACKS
|
|
||||||
};
|
|
||||||
|
|
||||||
#define connect_callback callback[CONNECT].cb
|
|
||||||
#define helo_callback callback[HELO].cb
|
|
||||||
#define envfrom_callback callback[ENVFROM].cb
|
|
||||||
#define envrcpt_callback callback[ENVRCPT].cb
|
|
||||||
#define header_callback callback[HEADER].cb
|
|
||||||
#define eoh_callback callback[EOH].cb
|
|
||||||
#define body_callback callback[BODY].cb
|
|
||||||
#define eom_callback callback[EOM].cb
|
|
||||||
#define abort_callback callback[ABORT].cb
|
|
||||||
#define close_callback callback[CLOSE].cb
|
|
||||||
#define unknown_callback callback[UNKNOWN].cb
|
|
||||||
#define data_callback callback[DATA].cb
|
|
||||||
#define negotiate_callback callback[NEGOTIATE].cb
|
|
||||||
|
|
||||||
/* Yes, these are static. If you need multiple different callbacks,
|
|
||||||
it's cleaner to use multiple filters, or convert to OO method calls. */
|
|
||||||
|
|
||||||
static struct MilterCallback {
|
static struct MilterCallback {
|
||||||
PyObject *cb;
|
PyObject **cbp;
|
||||||
const char *name;
|
const char *name;
|
||||||
} callback[NUMCALLBACKS+1] = {
|
} callback_names[] = {
|
||||||
{ NULL ,"connect" },
|
{ &connect_callback,"connect" },
|
||||||
{ NULL ,"helo" },
|
{ &helo_callback,"helo" },
|
||||||
{ NULL ,"envfrom" },
|
{ &envfrom_callback,"envfrom" },
|
||||||
{ NULL ,"envrcpt" },
|
{ &envrcpt_callback,"envrcpt" },
|
||||||
{ NULL ,"header" },
|
{ &header_callback,"header" },
|
||||||
{ NULL ,"eoh" },
|
{ &eoh_callback,"eoh" },
|
||||||
{ NULL ,"body" },
|
{ &body_callback,"body" },
|
||||||
{ NULL ,"eom" },
|
{ &eom_callback,"eom" },
|
||||||
{ NULL ,"abort" },
|
{ &abort_callback,"abort" },
|
||||||
{ NULL ,"close" },
|
{ &close_callback,"close" },
|
||||||
#ifdef SMFIS_ALL_OPTS
|
#ifdef SMFIS_ALL_OPTS
|
||||||
{ NULL ,"unknown" },
|
{ &unknown_callback,"unknown" },
|
||||||
{ NULL ,"data" },
|
{ &data_callback,"data" },
|
||||||
{ NULL ,"negotiate" },
|
{ &negotiate_callback,"negotiate" },
|
||||||
#endif
|
#endif
|
||||||
{ NULL , NULL }
|
{ NULL, NULL }
|
||||||
};
|
};
|
||||||
|
|
||||||
staticforward struct smfiDesc description; /* forward declaration */
|
staticforward struct smfiDesc description; /* forward declaration */
|
||||||
@@ -449,7 +422,7 @@ _thread_return(PyThreadState *t,int val,char *errstr) {
|
|||||||
return _generic_return(val,errstr);
|
return _generic_return(val,errstr);
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char milter_set_flags__doc__[] =
|
static char milter_set_flags__doc__[] =
|
||||||
"set_flags(int) -> None\n\
|
"set_flags(int) -> None\n\
|
||||||
Set flags for filter capabilities; OR of one or more of:\n\
|
Set flags for filter capabilities; OR of one or more of:\n\
|
||||||
ADDHDRS - filter may add headers\n\
|
ADDHDRS - filter may add headers\n\
|
||||||
@@ -490,7 +463,7 @@ generic_set_callback(PyObject *args,char *t,PyObject **cb) {
|
|||||||
return Py_None;
|
return Py_None;
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char milter_set_connect_callback__doc__[] =
|
static char milter_set_connect_callback__doc__[] =
|
||||||
"set_connect_callback(Function) -> None\n\
|
"set_connect_callback(Function) -> None\n\
|
||||||
Sets the Python function invoked when a connection is made to sendmail.\n\
|
Sets the Python function invoked when a connection is made to sendmail.\n\
|
||||||
Function takes args (ctx, hostname, integer, hostaddr) -> int\n\
|
Function takes args (ctx, hostname, integer, hostaddr) -> int\n\
|
||||||
@@ -517,7 +490,7 @@ milter_set_connect_callback(PyObject *self, PyObject *args) {
|
|||||||
"O:set_connect_callback", &connect_callback);
|
"O:set_connect_callback", &connect_callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char milter_set_helo_callback__doc__[] =
|
static char milter_set_helo_callback__doc__[] =
|
||||||
"set_helo_callback(Function) -> None\n\
|
"set_helo_callback(Function) -> None\n\
|
||||||
Sets the Python function invoked upon SMTP HELO.\n\
|
Sets the Python function invoked upon SMTP HELO.\n\
|
||||||
Function takes args (ctx, hostname) -> int\n\
|
Function takes args (ctx, hostname) -> int\n\
|
||||||
@@ -528,7 +501,7 @@ milter_set_helo_callback(PyObject *self, PyObject *args) {
|
|||||||
return generic_set_callback(args, "O:set_helo_callback", &helo_callback);
|
return generic_set_callback(args, "O:set_helo_callback", &helo_callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char milter_set_envfrom_callback__doc__[] =
|
static char milter_set_envfrom_callback__doc__[] =
|
||||||
"set_envfrom_callback(Function) -> None\n\
|
"set_envfrom_callback(Function) -> None\n\
|
||||||
Sets the Python function invoked on envelope from.\n\
|
Sets the Python function invoked on envelope from.\n\
|
||||||
Function takes args (ctx, from, *str) -> int\n\
|
Function takes args (ctx, from, *str) -> int\n\
|
||||||
@@ -541,7 +514,7 @@ milter_set_envfrom_callback(PyObject *self, PyObject *args) {
|
|||||||
&envfrom_callback);
|
&envfrom_callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char milter_set_envrcpt_callback__doc__[] =
|
static char milter_set_envrcpt_callback__doc__[] =
|
||||||
"set_envrcpt_callback(Function) -> None\n\
|
"set_envrcpt_callback(Function) -> None\n\
|
||||||
Sets the Python function invoked on each envelope recipient.\n\
|
Sets the Python function invoked on each envelope recipient.\n\
|
||||||
Function takes args (ctx, rcpt, *str) -> int\n\
|
Function takes args (ctx, rcpt, *str) -> int\n\
|
||||||
@@ -554,7 +527,7 @@ milter_set_envrcpt_callback(PyObject *self, PyObject *args) {
|
|||||||
&envrcpt_callback);
|
&envrcpt_callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char milter_set_header_callback__doc__[] =
|
static char milter_set_header_callback__doc__[] =
|
||||||
"set_header_callback(Function) -> None\n\
|
"set_header_callback(Function) -> None\n\
|
||||||
Sets the Python function invoked on each message header.\n\
|
Sets the Python function invoked on each message header.\n\
|
||||||
Function takes args (ctx, field, value) ->int\n\
|
Function takes args (ctx, field, value) ->int\n\
|
||||||
@@ -567,7 +540,7 @@ milter_set_header_callback(PyObject *self, PyObject *args) {
|
|||||||
&header_callback);
|
&header_callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char milter_set_eoh_callback__doc__[] =
|
static char milter_set_eoh_callback__doc__[] =
|
||||||
"set_eoh_callback(Function) -> None\n\
|
"set_eoh_callback(Function) -> None\n\
|
||||||
Sets the Python function invoked at end of header.\n\
|
Sets the Python function invoked at end of header.\n\
|
||||||
Function takes args (ctx) -> int";
|
Function takes args (ctx) -> int";
|
||||||
@@ -577,7 +550,7 @@ milter_set_eoh_callback(PyObject *self, PyObject *args) {
|
|||||||
return generic_set_callback(args, "O:set_eoh_callback", &eoh_callback);
|
return generic_set_callback(args, "O:set_eoh_callback", &eoh_callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char milter_set_body_callback__doc__[] =
|
static char milter_set_body_callback__doc__[] =
|
||||||
"set_body_callback(Function) -> None\n\
|
"set_body_callback(Function) -> None\n\
|
||||||
Sets the Python function invoked for each body chunk. There may\n\
|
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\
|
||||||
@@ -590,7 +563,7 @@ milter_set_body_callback(PyObject *self, PyObject *args) {
|
|||||||
return generic_set_callback(args, "O:set_body_callback", &body_callback);
|
return generic_set_callback(args, "O:set_body_callback", &body_callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char milter_set_eom_callback__doc__[] =
|
static char milter_set_eom_callback__doc__[] =
|
||||||
"set_eom_callback(Function) -> None\n\
|
"set_eom_callback(Function) -> None\n\
|
||||||
Sets the Python function invoked at end of message.\n\
|
Sets the Python function invoked at end of message.\n\
|
||||||
This routine is the only place where special operations\n\
|
This routine is the only place where special operations\n\
|
||||||
@@ -603,7 +576,7 @@ milter_set_eom_callback(PyObject *self, PyObject *args) {
|
|||||||
return generic_set_callback(args, "O:set_eom_callback", &eom_callback);
|
return generic_set_callback(args, "O:set_eom_callback", &eom_callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char milter_set_abort_callback__doc__[] =
|
static char milter_set_abort_callback__doc__[] =
|
||||||
"set_abort_callback(Function) -> None\n\
|
"set_abort_callback(Function) -> None\n\
|
||||||
Sets the Python function invoked if message is aborted\n\
|
Sets the Python function invoked if message is aborted\n\
|
||||||
outside of the control of the filter, for example,\n\
|
outside of the control of the filter, for example,\n\
|
||||||
@@ -617,7 +590,7 @@ milter_set_abort_callback(PyObject *self, PyObject *args) {
|
|||||||
return generic_set_callback(args, "O:set_abort_callback", &abort_callback);
|
return generic_set_callback(args, "O:set_abort_callback", &abort_callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char milter_set_close_callback__doc__[] =
|
static char milter_set_close_callback__doc__[] =
|
||||||
"set_close_callback(Function) -> None\n\
|
"set_close_callback(Function) -> None\n\
|
||||||
Sets the Python function invoked at end of the connection. This\n\
|
Sets the Python function invoked at end of the connection. This\n\
|
||||||
is called on close even if the previous mail transaction was aborted.\n\
|
is called on close even if the previous mail transaction was aborted.\n\
|
||||||
@@ -630,7 +603,7 @@ milter_set_close_callback(PyObject *self, PyObject *args) {
|
|||||||
|
|
||||||
static int exception_policy = SMFIS_TEMPFAIL;
|
static int exception_policy = SMFIS_TEMPFAIL;
|
||||||
|
|
||||||
static const char milter_set_exception_policy__doc__[] =
|
static char milter_set_exception_policy__doc__[] =
|
||||||
"set_exception_policy(i) -> None\n\
|
"set_exception_policy(i) -> None\n\
|
||||||
Sets the policy for untrapped Python exceptions during a callback.\n\
|
Sets the policy for untrapped Python exceptions during a callback.\n\
|
||||||
Must be one of TEMPFAIL,REJECT,CONTINUE";
|
Must be one of TEMPFAIL,REJECT,CONTINUE";
|
||||||
@@ -656,23 +629,19 @@ _release_thread(PyThreadState *t) {
|
|||||||
PyEval_ReleaseThread(t);
|
PyEval_ReleaseThread(t);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/** Report and clear any python exception before returning to libmilter.
|
/** Report and clear any python exception before returning to libmilter.
|
||||||
The interpreter is locked when we are called, and we unlock it. */
|
The interpreter is locked when we are called, and we unlock it. */
|
||||||
static int _report_exception(milter_ContextObject *self) {
|
static int _report_exception(milter_ContextObject *self) {
|
||||||
char untrapped_msg[80];
|
|
||||||
sprintf(untrapped_msg,"pymilter: untrapped exception in %.40s",
|
|
||||||
description.xxfi_name);
|
|
||||||
if (PyErr_Occurred()) {
|
if (PyErr_Occurred()) {
|
||||||
PyErr_Print();
|
PyErr_Print();
|
||||||
PyErr_Clear(); /* must clear since not returning to python */
|
PyErr_Clear(); /* must clear since not returning to python */
|
||||||
_release_thread(self->t);
|
_release_thread(self->t);
|
||||||
switch (exception_policy) {
|
switch (exception_policy) {
|
||||||
case SMFIS_REJECT:
|
case SMFIS_REJECT:
|
||||||
smfi_setreply(self->ctx, "554", "5.3.0", untrapped_msg);
|
smfi_setreply(self->ctx, "554", "5.3.0", "Filter failure");
|
||||||
return SMFIS_REJECT;
|
return SMFIS_REJECT;
|
||||||
case SMFIS_TEMPFAIL:
|
case SMFIS_TEMPFAIL:
|
||||||
smfi_setreply(self->ctx, "451", "4.3.0", untrapped_msg);
|
smfi_setreply(self->ctx, "451", "4.3.0", "Filter failure");
|
||||||
return SMFIS_TEMPFAIL;
|
return SMFIS_TEMPFAIL;
|
||||||
}
|
}
|
||||||
return SMFIS_CONTINUE;
|
return SMFIS_CONTINUE;
|
||||||
@@ -698,8 +667,8 @@ _generic_wrapper(milter_ContextObject *self, PyObject *cb, PyObject *arglist) {
|
|||||||
const char *cbname = "milter";
|
const char *cbname = "milter";
|
||||||
char buf[40];
|
char buf[40];
|
||||||
Py_DECREF(result);
|
Py_DECREF(result);
|
||||||
for (p = callback; p->name; ++p) {
|
for (p = callback_names; p->cbp; ++p) {
|
||||||
if (cb == p->cb) {
|
if (cb == *p->cbp) {
|
||||||
cbname = p->name;
|
cbname = p->name;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1004,7 +973,7 @@ milter_wrap_close(SMFICTX *ctx) {
|
|||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char milter_register__doc__[] =
|
static char milter_register__doc__[] =
|
||||||
"register(name,unknown=,data=,negotiate=) -> None\n\
|
"register(name,unknown=,data=,negotiate=) -> None\n\
|
||||||
Registers the milter name with current callbacks, and flags.\n\
|
Registers the milter name with current callbacks, and flags.\n\
|
||||||
Required before main() is called.";
|
Required before main() is called.";
|
||||||
@@ -1049,7 +1018,7 @@ milter_register(PyObject *self, PyObject *args, PyObject *kwds) {
|
|||||||
return _generic_return(smfi_register(description), "cannot register");
|
return _generic_return(smfi_register(description), "cannot register");
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char milter_opensocket__doc__[] =
|
static char milter_opensocket__doc__[] =
|
||||||
"opensocket(rmsock) -> None\n\
|
"opensocket(rmsock) -> None\n\
|
||||||
Attempts to create and open the socket provided with setconn.\n\
|
Attempts to create and open the socket provided with setconn.\n\
|
||||||
Removes the socket first if rmsock is True.";
|
Removes the socket first if rmsock is True.";
|
||||||
@@ -1062,7 +1031,7 @@ milter_opensocket(PyObject *self, PyObject *args) {
|
|||||||
return _generic_return(smfi_opensocket(rmsock), "cannot opensocket");
|
return _generic_return(smfi_opensocket(rmsock), "cannot opensocket");
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char milter_main__doc__[] =
|
static char milter_main__doc__[] =
|
||||||
"main() -> None\n\
|
"main() -> None\n\
|
||||||
Main milter routine. Set any callbacks, and flags desired, then call\n\
|
Main milter routine. Set any callbacks, and flags desired, then call\n\
|
||||||
setconn(), then call register(name), and finally call main().";
|
setconn(), then call register(name), and finally call main().";
|
||||||
@@ -1086,7 +1055,7 @@ milter_main(PyObject *self, PyObject *args) {
|
|||||||
return o;
|
return o;
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char milter_setdbg__doc__[] =
|
static char milter_setdbg__doc__[] =
|
||||||
"setdbg(int) -> None\n\
|
"setdbg(int) -> None\n\
|
||||||
Sets debug level in sendmail/libmilter source. Dubious usefulness.";
|
Sets debug level in sendmail/libmilter source. Dubious usefulness.";
|
||||||
|
|
||||||
@@ -1097,7 +1066,7 @@ milter_setdbg(PyObject *self, PyObject *args) {
|
|||||||
return _generic_return(smfi_setdbg(val), "cannot set debug value");
|
return _generic_return(smfi_setdbg(val), "cannot set debug value");
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char milter_setbacklog__doc__[] =
|
static char milter_setbacklog__doc__[] =
|
||||||
"setbacklog(int) -> None\n\
|
"setbacklog(int) -> None\n\
|
||||||
Set the TCP connection queue size for the milter socket.";
|
Set the TCP connection queue size for the milter socket.";
|
||||||
|
|
||||||
@@ -1109,7 +1078,7 @@ milter_setbacklog(PyObject *self, PyObject *args) {
|
|||||||
return _generic_return(smfi_setbacklog(val), "cannot set backlog");
|
return _generic_return(smfi_setbacklog(val), "cannot set backlog");
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char milter_settimeout__doc__[] =
|
static char milter_settimeout__doc__[] =
|
||||||
"settimeout(int) -> None\n\
|
"settimeout(int) -> None\n\
|
||||||
Set the time (in seconds) that sendmail will wait before\n\
|
Set the time (in seconds) that sendmail will wait before\n\
|
||||||
considering this filter dead.";
|
considering this filter dead.";
|
||||||
@@ -1122,7 +1091,7 @@ milter_settimeout(PyObject *self, PyObject *args) {
|
|||||||
return _generic_return(smfi_settimeout(val), "cannot set timeout");
|
return _generic_return(smfi_settimeout(val), "cannot set timeout");
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char milter_setconn__doc__[] =
|
static char milter_setconn__doc__[] =
|
||||||
"setconn(filename) -> None\n\
|
"setconn(filename) -> None\n\
|
||||||
Sets the pathname to the unix, inet, or inet6 socket that\n\
|
Sets the pathname to the unix, inet, or inet6 socket that\n\
|
||||||
sendmail will use to communicate with this filter. By default,\n\
|
sendmail will use to communicate with this filter. By default,\n\
|
||||||
@@ -1142,7 +1111,7 @@ milter_setconn(PyObject *self, PyObject *args) {
|
|||||||
return _generic_return(smfi_setconn(str), "cannot set connection");
|
return _generic_return(smfi_setconn(str), "cannot set connection");
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char milter_stop__doc__[] =
|
static char milter_stop__doc__[] =
|
||||||
"stop() -> None\n\
|
"stop() -> None\n\
|
||||||
This function appears to be a controlled method to tell sendmail to\n\
|
This function appears to be a controlled method to tell sendmail to\n\
|
||||||
stop using this filter. It will close the socket.";
|
stop using this filter. It will close the socket.";
|
||||||
@@ -1155,7 +1124,7 @@ milter_stop(PyObject *self, PyObject *args) {
|
|||||||
return _thread_return(t,smfi_stop(), "cannot stop");
|
return _thread_return(t,smfi_stop(), "cannot stop");
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char milter_getdiag__doc__[] =
|
static char milter_getdiag__doc__[] =
|
||||||
"getdiag() -> tuple\n\
|
"getdiag() -> tuple\n\
|
||||||
Return a tuple of diagnostic data. The first two items are context new\n\
|
Return a tuple of diagnostic data. The first two items are context new\n\
|
||||||
count and context del count. The rest are yet to be defined.";
|
count and context del count. The rest are yet to be defined.";
|
||||||
@@ -1165,7 +1134,7 @@ milter_getdiag(PyObject *self, PyObject *args) {
|
|||||||
return Py_BuildValue("(kk)", diag.contextNew,diag.contextDel);
|
return Py_BuildValue("(kk)", diag.contextNew,diag.contextDel);
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char milter_getversion__doc__[] =
|
static char milter_getversion__doc__[] =
|
||||||
"getversion() -> tuple\n\
|
"getversion() -> tuple\n\
|
||||||
Return runtime libmilter version as a tuple of major,minor,patchlevel.";
|
Return runtime libmilter version as a tuple of major,minor,patchlevel.";
|
||||||
static PyObject *
|
static PyObject *
|
||||||
@@ -1179,7 +1148,7 @@ milter_getversion(PyObject *self, PyObject *args) {
|
|||||||
return Py_BuildValue("(kkk)", major,minor,patch);
|
return Py_BuildValue("(kkk)", major,minor,patch);
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char milter_getsymval__doc__[] =
|
static char milter_getsymval__doc__[] =
|
||||||
"getsymval(String) -> String\n\
|
"getsymval(String) -> String\n\
|
||||||
Returns a symbol's value. Context-dependent, and unclear from the dox.";
|
Returns a symbol's value. Context-dependent, and unclear from the dox.";
|
||||||
|
|
||||||
@@ -1194,7 +1163,7 @@ milter_getsymval(PyObject *self, PyObject *args) {
|
|||||||
return Py_BuildValue("s", smfi_getsymval(ctx, str));
|
return Py_BuildValue("s", smfi_getsymval(ctx, str));
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char milter_setreply__doc__[] =
|
static char milter_setreply__doc__[] =
|
||||||
"setreply(rcode, xcode, message) -> None\n\
|
"setreply(rcode, xcode, message) -> None\n\
|
||||||
Sets the specific reply code to be used in response\n\
|
Sets the specific reply code to be used in response\n\
|
||||||
to the active command.\n\
|
to the active command.\n\
|
||||||
@@ -1258,7 +1227,7 @@ milter_setreply(PyObject *self, PyObject *args) {
|
|||||||
"cannot set reply");
|
"cannot set reply");
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char milter_addheader__doc__[] =
|
static char milter_addheader__doc__[] =
|
||||||
"addheader(field, value, idx=-1) -> None\n\
|
"addheader(field, value, idx=-1) -> None\n\
|
||||||
Add a header to the message. This header is not passed to other\n\
|
Add a header to the message. This header is not passed to other\n\
|
||||||
filters. It is not checked for standards compliance;\n\
|
filters. It is not checked for standards compliance;\n\
|
||||||
@@ -1295,7 +1264,7 @@ milter_addheader(PyObject *self, PyObject *args) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#ifdef SMFIF_CHGFROM
|
#ifdef SMFIF_CHGFROM
|
||||||
static const char milter_chgfrom__doc__[] =
|
static char milter_chgfrom__doc__[] =
|
||||||
"chgfrom(sender,params) -> None\n\
|
"chgfrom(sender,params) -> None\n\
|
||||||
Change the envelope sender (MAIL From) of the current message.\n\
|
Change the envelope sender (MAIL From) of the current message.\n\
|
||||||
A filter which calls smfi_chgfrom must have set the CHGFROM flag\n\
|
A filter which calls smfi_chgfrom must have set the CHGFROM flag\n\
|
||||||
@@ -1318,7 +1287,7 @@ milter_chgfrom(PyObject *self, PyObject *args) {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static const char milter_chgheader__doc__[] =
|
static char milter_chgheader__doc__[] =
|
||||||
"chgheader(field, int, value) -> None\n\
|
"chgheader(field, int, value) -> None\n\
|
||||||
Change/delete a header in the message. \n\
|
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\
|
||||||
@@ -1346,7 +1315,7 @@ milter_chgheader(PyObject *self, PyObject *args) {
|
|||||||
"cannot change header");
|
"cannot change header");
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char milter_addrcpt__doc__[] =
|
static char milter_addrcpt__doc__[] =
|
||||||
"addrcpt(string,params=None) -> None\n\
|
"addrcpt(string,params=None) -> None\n\
|
||||||
Add a recipient to the envelope. It must be in the same format\n\
|
Add a recipient to the envelope. It must be in the same format\n\
|
||||||
as is passed to the envrcpt callback in the first tuple element.\n\
|
as is passed to the envrcpt callback in the first tuple element.\n\
|
||||||
@@ -1376,7 +1345,7 @@ milter_addrcpt(PyObject *self, PyObject *args) {
|
|||||||
return _thread_return(t,rc, "cannot add recipient");
|
return _thread_return(t,rc, "cannot add recipient");
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char milter_delrcpt__doc__[] =
|
static char milter_delrcpt__doc__[] =
|
||||||
"delrcpt(string) -> None\n\
|
"delrcpt(string) -> None\n\
|
||||||
Delete a recipient from the envelope.\n\
|
Delete a recipient from the envelope.\n\
|
||||||
This function can only be called from the EOM callback.";
|
This function can only be called from the EOM callback.";
|
||||||
@@ -1394,7 +1363,7 @@ milter_delrcpt(PyObject *self, PyObject *args) {
|
|||||||
return _thread_return(t,smfi_delrcpt(ctx, rcpt), "cannot delete recipient");
|
return _thread_return(t,smfi_delrcpt(ctx, rcpt), "cannot delete recipient");
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char milter_replacebody__doc__[] =
|
static char milter_replacebody__doc__[] =
|
||||||
"replacebody(string) -> None\n\
|
"replacebody(string) -> None\n\
|
||||||
Replace the body of the message. This routine may be called multiple\n\
|
Replace the body of the message. This routine may be called multiple\n\
|
||||||
times if the body is longer than convenient to send in one call. End of\n\
|
times if the body is longer than convenient to send in one call. End of\n\
|
||||||
@@ -1416,7 +1385,7 @@ milter_replacebody(PyObject *self, PyObject *args) {
|
|||||||
(unsigned char *)bodyp, bodylen), "cannot replace message body");
|
(unsigned char *)bodyp, bodylen), "cannot replace message body");
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char milter_setpriv__doc__[] =
|
static char milter_setpriv__doc__[] =
|
||||||
"setpriv(object) -> object\n\
|
"setpriv(object) -> object\n\
|
||||||
Associates any Python object with this context, and returns\n\
|
Associates any Python object with this context, and returns\n\
|
||||||
the old value or None. Use this to\n\
|
the old value or None. Use this to\n\
|
||||||
@@ -1442,7 +1411,7 @@ milter_setpriv(PyObject *self, PyObject *args) {
|
|||||||
return old;
|
return old;
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char milter_getpriv__doc__[] =
|
static char milter_getpriv__doc__[] =
|
||||||
"getpriv() -> None\n\
|
"getpriv() -> None\n\
|
||||||
Returns the Python object associated with the current context (if any).\n\
|
Returns the Python object associated with the current context (if any).\n\
|
||||||
Use this in conjunction with setpriv to keep track of data in a thread-safe\n\
|
Use this in conjunction with setpriv to keep track of data in a thread-safe\n\
|
||||||
@@ -1460,7 +1429,7 @@ milter_getpriv(PyObject *self, PyObject *args) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#ifdef SMFIF_QUARANTINE
|
#ifdef SMFIF_QUARANTINE
|
||||||
static const char milter_quarantine__doc__[] =
|
static char milter_quarantine__doc__[] =
|
||||||
"quarantine(string) -> None\n\
|
"quarantine(string) -> None\n\
|
||||||
Place the message in quarantine. A string with a description of the reason\n\
|
Place the message in quarantine. A string with a description of the reason\n\
|
||||||
is the only argument.";
|
is the only argument.";
|
||||||
@@ -1481,7 +1450,7 @@ milter_quarantine(PyObject *self, PyObject *args) {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef SMFIR_PROGRESS
|
#ifdef SMFIR_PROGRESS
|
||||||
static const char milter_progress__doc__[] =
|
static char milter_progress__doc__[] =
|
||||||
"progress() -> None\n\
|
"progress() -> None\n\
|
||||||
Notify the MTA that we are working on a message so it will reset timeouts.";
|
Notify the MTA that we are working on a message so it will reset timeouts.";
|
||||||
|
|
||||||
@@ -1498,23 +1467,23 @@ milter_progress(PyObject *self, PyObject *args) {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef SMFIF_SETSYMLIST
|
#ifdef SMFIF_SETSMLIST
|
||||||
static const char milter_setsymlist__doc__[] =
|
static char milter_setsmlist__doc__[] =
|
||||||
"setsymlist(stage,macrolist) -> None\n\
|
"setsmlist(stage,macrolist) -> None\n\
|
||||||
Tell the MTA which macro values we are interested in for a given stage";
|
Tell the MTA which macro values we are interested in for a given stage";
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
milter_setsymlist(PyObject *self, PyObject *args) {
|
milter_setsmlist(PyObject *self, PyObject *args) {
|
||||||
SMFICTX *ctx;
|
SMFICTX *ctx;
|
||||||
PyThreadState *t;
|
PyThreadState *t;
|
||||||
int stage = 0;
|
int stage = 0;
|
||||||
char *smlist = 0;
|
char *smlist = 0;
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args, "is:setsymlist",&stage, &smlist)) return NULL;
|
if (!PyArg_ParseTuple(args, "is:setsmlist",&stage, &smlist)) return NULL;
|
||||||
ctx = _find_context(self);
|
ctx = _find_context(self);
|
||||||
if (ctx == NULL) return NULL;
|
if (ctx == NULL) return NULL;
|
||||||
t = PyEval_SaveThread();
|
t = PyEval_SaveThread();
|
||||||
return _thread_return(t,smfi_setsymlist(ctx,stage,smlist),
|
return _thread_return(t,smfi_setsmlist(ctx,stage,smlist),
|
||||||
"cannot set macro list");
|
"cannot set macro list");
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -1538,8 +1507,8 @@ static PyMethodDef context_methods[] = {
|
|||||||
#ifdef SMFIF_CHGFROM
|
#ifdef SMFIF_CHGFROM
|
||||||
{ "chgfrom", milter_chgfrom, METH_VARARGS, milter_chgfrom__doc__},
|
{ "chgfrom", milter_chgfrom, METH_VARARGS, milter_chgfrom__doc__},
|
||||||
#endif
|
#endif
|
||||||
#ifdef SMFIF_SETSYMLIST
|
#ifdef SMFIF_SETSMLIST
|
||||||
{ "setsymlist", milter_setsymlist, METH_VARARGS, milter_setsymlist__doc__},
|
{ "setsmlist", milter_setsmlist, METH_VARARGS, milter_setsmlist__doc__},
|
||||||
#endif
|
#endif
|
||||||
{ NULL, NULL }
|
{ NULL, NULL }
|
||||||
};
|
};
|
||||||
@@ -1620,7 +1589,7 @@ static PyTypeObject milter_ContextType = {
|
|||||||
Py_TPFLAGS_DEFAULT, /* tp_flags */
|
Py_TPFLAGS_DEFAULT, /* tp_flags */
|
||||||
};
|
};
|
||||||
|
|
||||||
static const char milter_documentation[] =
|
static char milter_documentation[] =
|
||||||
"This module interfaces with Sendmail's libmilter functionality,\n\
|
"This module interfaces with Sendmail's libmilter functionality,\n\
|
||||||
allowing one to write email filters directly in Python.\n\
|
allowing one to write email filters directly in Python.\n\
|
||||||
Libmilter is currently marked FFR, and needs to be explicitly installed.\n\
|
Libmilter is currently marked FFR, and needs to be explicitly installed.\n\
|
||||||
@@ -1662,15 +1631,8 @@ initmilter(void) {
|
|||||||
#ifdef SMFIF_CHGFROM
|
#ifdef SMFIF_CHGFROM
|
||||||
setitem(d,"CHGFROM",SMFIF_CHGFROM);
|
setitem(d,"CHGFROM",SMFIF_CHGFROM);
|
||||||
#endif
|
#endif
|
||||||
#ifdef SMFIF_SETSYMLIST
|
#ifdef SMFIF_SETSMLIST
|
||||||
setitem(d,"SETSYMLIST",SMFIF_SETSYMLIST);
|
setitem(d,"SETSMLIST",SMFIF_SETSMLIST);
|
||||||
setitem(d,"M_CONNECT",SMFIM_CONNECT);/* connect */
|
|
||||||
setitem(d,"M_HELO",SMFIM_HELO); /* HELO/EHLO */
|
|
||||||
setitem(d,"M_ENVFROM",SMFIM_ENVFROM);/* MAIL From */
|
|
||||||
setitem(d,"M_ENVRCPT",SMFIM_ENVRCPT);/* RCPT To */
|
|
||||||
setitem(d,"M_DATA",SMFIM_DATA); /* DATA */
|
|
||||||
setitem(d,"M_EOM",SMFIM_EOM); /* end of message (final dot) */
|
|
||||||
setitem(d,"M_EOH",SMFIM_EOH); /* end of header */
|
|
||||||
#endif
|
#endif
|
||||||
#ifdef SMFIS_ALL_OPTS
|
#ifdef SMFIS_ALL_OPTS
|
||||||
setitem(d,"P_RCPT_REJ",SMFIP_RCPT_REJ);
|
setitem(d,"P_RCPT_REJ",SMFIP_RCPT_REJ);
|
||||||
|
|||||||
@@ -1,10 +1,4 @@
|
|||||||
# $Log$
|
# $Log$
|
||||||
# Revision 1.8 2011/11/05 15:51:03 customdesigned
|
|
||||||
# New example
|
|
||||||
#
|
|
||||||
# Revision 1.7 2009/06/13 21:15:12 customdesigned
|
|
||||||
# Doxygen updates.
|
|
||||||
#
|
|
||||||
# Revision 1.6 2009/06/09 03:13:13 customdesigned
|
# Revision 1.6 2009/06/09 03:13:13 customdesigned
|
||||||
# More doxygen docs.
|
# More doxygen docs.
|
||||||
#
|
#
|
||||||
@@ -171,14 +165,15 @@ class MimeMessage(Message):
|
|||||||
"""
|
"""
|
||||||
def __init__(self,fp=None,seekable=1):
|
def __init__(self,fp=None,seekable=1):
|
||||||
Message.__init__(self)
|
Message.__init__(self)
|
||||||
|
self.headerchange = None
|
||||||
self.submsg = None
|
self.submsg = None
|
||||||
self.modified = False
|
self.modified = False
|
||||||
|
|
||||||
## @var headerchange
|
## @var headerchange
|
||||||
# Provide a headerchange event for integration with Milter.
|
# Provide a headerchange event for integration with Milter.
|
||||||
# The headerchange attribute can be assigned a function to be called when
|
# The headerchange attribute can be assigned a function to be called when
|
||||||
# changing headers. The signature is:
|
# changing headers. The signature is:
|
||||||
# headerchange(msg,name,value) -> None
|
# headerchange(msg,name,value) -> None
|
||||||
self.headerchange = None
|
|
||||||
|
|
||||||
def get_param(self, param, failobj=None, header='content-type', unquote=True):
|
def get_param(self, param, failobj=None, header='content-type', unquote=True):
|
||||||
val = Message.get_param(self,param,failobj,header,unquote)
|
val = Message.get_param(self,param,failobj,header,unquote)
|
||||||
|
|||||||
+3
-18
@@ -1,12 +1,12 @@
|
|||||||
%define __python python2.6
|
%define __python python2.6
|
||||||
%define pythonbase python
|
%define pythonbase python26
|
||||||
|
|
||||||
%define libdir %{_libdir}/pymilter
|
%define libdir %{_libdir}/pymilter
|
||||||
%{!?python_sitearch: %define python_sitearch %(%{__python} -c "from distutils.sysconfig import get_python_lib; print get_python_lib(1)")}
|
%{!?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
|
Summary: Python interface to sendmail milter API
|
||||||
Name: %{pythonbase}-pymilter
|
Name: %{pythonbase}-pymilter
|
||||||
Version: 0.9.8
|
Version: 0.9.5
|
||||||
Release: 1%{dist}
|
Release: 1%{dist}
|
||||||
Source: http://downloads.sourceforge.net/pymilter/pymilter-%{version}.tar.gz
|
Source: http://downloads.sourceforge.net/pymilter/pymilter-%{version}.tar.gz
|
||||||
License: GPLv2+
|
License: GPLv2+
|
||||||
@@ -75,22 +75,7 @@ chmod a+x $RPM_BUILD_ROOT%{libdir}/start.sh
|
|||||||
rm -rf $RPM_BUILD_ROOT
|
rm -rf $RPM_BUILD_ROOT
|
||||||
|
|
||||||
%changelog
|
%changelog
|
||||||
* Sat Mar 9 2013 Stuart Gathman <stuart@bmsi.com> 0.9.8-1
|
* Wed Mar 02 2010 Stuart Gathman <stuart@bmsi.com> 0.9.5-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"
|
|
||||||
|
|
||||||
* Sat Feb 25 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.
|
- Print milter.error for invalid callback return type.
|
||||||
(Since stacktrace is empty, the TypeError exception is confusing.)
|
(Since stacktrace is empty, the TypeError exception is confusing.)
|
||||||
- Fix milter-template.py
|
- Fix milter-template.py
|
||||||
|
|||||||
@@ -35,9 +35,7 @@ class sampleMilter(Milter.Milter):
|
|||||||
# 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.noreply
|
|
||||||
def envfrom(self,f,*str):
|
def envfrom(self,f,*str):
|
||||||
"start of MAIL transaction"
|
|
||||||
self.log("mail from",f,str)
|
self.log("mail from",f,str)
|
||||||
self.fp = StringIO.StringIO()
|
self.fp = StringIO.StringIO()
|
||||||
self.tempname = None
|
self.tempname = None
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ libs = ["milter"]
|
|||||||
libdirs = ["/usr/lib/libmilter"] # needed for Debian
|
libdirs = ["/usr/lib/libmilter"] # needed for Debian
|
||||||
|
|
||||||
# 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 = '0.9.8',
|
setup(name = "pymilter", version = '0.9.5',
|
||||||
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
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import unittest
|
|||||||
import testmime
|
import testmime
|
||||||
import testsample
|
import testsample
|
||||||
import testutils
|
import testutils
|
||||||
import testgrey
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
def suite():
|
def suite():
|
||||||
@@ -10,7 +9,6 @@ def suite():
|
|||||||
s.addTest(testmime.suite())
|
s.addTest(testmime.suite())
|
||||||
s.addTest(testsample.suite())
|
s.addTest(testsample.suite())
|
||||||
s.addTest(testutils.suite())
|
s.addTest(testutils.suite())
|
||||||
s.addTest(testgrey.suite())
|
|
||||||
return s
|
return s
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
-18587
File diff suppressed because it is too large
Load Diff
-55
@@ -1,55 +0,0 @@
|
|||||||
import unittest
|
|
||||||
import doctest
|
|
||||||
import os
|
|
||||||
#from Milter.greylist import Greylist
|
|
||||||
from Milter.greysql import Greylist
|
|
||||||
|
|
||||||
class GreylistTestCase(unittest.TestCase):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
self.fname = 'test.db'
|
|
||||||
os.remove(self.fname)
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
#os.remove(self.fname)
|
|
||||||
pass
|
|
||||||
|
|
||||||
def testGrey(self):
|
|
||||||
grey = Greylist(self.fname)
|
|
||||||
# first time
|
|
||||||
rc = grey.check('1.2.3.4','foo@bar.com','baz@spat.com')
|
|
||||||
self.assertEqual(rc,0)
|
|
||||||
# not in window yet
|
|
||||||
rc = grey.check('1.2.3.4','foo@bar.com','baz@spat.com',timeinc=5*60)
|
|
||||||
self.assertEqual(rc,0)
|
|
||||||
# within window
|
|
||||||
rc = grey.check('1.2.3.4','foo@bar.com','baz@spat.com',timeinc=15*60)
|
|
||||||
self.assertEqual(rc,1)
|
|
||||||
# new triple
|
|
||||||
rc = grey.check('1.2.3.5','foo@bar.com','baz@spat.com',timeinc=15*60)
|
|
||||||
self.assertEqual(rc,0)
|
|
||||||
# seen again
|
|
||||||
rc = grey.check('1.2.3.4','foo@bar.com','baz@spat.com',timeinc=5*3600)
|
|
||||||
self.assertEqual(rc,2)
|
|
||||||
# new one past expire
|
|
||||||
rc = grey.check('1.2.3.5','foo@bar.com','baz@spat.com',timeinc=6*3600)
|
|
||||||
self.assertEqual(rc,0)
|
|
||||||
# original past retain
|
|
||||||
rc = grey.check('1.2.3.4','foo@bar.com','baz@spat.com',timeinc=37*24*3600)
|
|
||||||
self.assertEqual(rc,0)
|
|
||||||
# new one for testing expire
|
|
||||||
rc = grey.check('1.2.3.5','flub@bar.com','baz@spat.com',timeinc=20*24*3600)
|
|
||||||
self.assertEqual(rc,0)
|
|
||||||
grey.close()
|
|
||||||
# test cleanup
|
|
||||||
grey = Greylist(self.fname)
|
|
||||||
rc = grey.clean(timeinc=37*24*3600)
|
|
||||||
self.assertEqual(rc,1)
|
|
||||||
grey.close()
|
|
||||||
|
|
||||||
def suite():
|
|
||||||
s = unittest.makeSuite(GreylistTestCase,'test')
|
|
||||||
return s
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
unittest.TextTestRunner().run(suite())
|
|
||||||
+1
-4
@@ -1,7 +1,4 @@
|
|||||||
# $Log$
|
# $Log$
|
||||||
# Revision 1.5 2011/06/09 17:27:42 customdesigned
|
|
||||||
# Documentation updates.
|
|
||||||
#
|
|
||||||
# Revision 1.4 2005/07/20 14:49:44 customdesigned
|
# Revision 1.4 2005/07/20 14:49:44 customdesigned
|
||||||
# Handle corrupt and empty ZIP files.
|
# Handle corrupt and empty ZIP files.
|
||||||
#
|
#
|
||||||
@@ -173,7 +170,7 @@ class MimeTestCase(unittest.TestCase):
|
|||||||
msg = mime.message_from_file(open('test/'+fname,'r'))
|
msg = mime.message_from_file(open('test/'+fname,'r'))
|
||||||
mime.defang(msg,scan_zip=True)
|
mime.defang(msg,scan_zip=True)
|
||||||
self.failIf(msg.ismodified())
|
self.failIf(msg.ismodified())
|
||||||
msg = mime.message_from_file(open('test/test2','r'))
|
msg = mime.message_from_file(open('test/tmpytgcE5.fail','r'))
|
||||||
rc = mime.check_attachments(msg,self._chk_attach)
|
rc = mime.check_attachments(msg,self._chk_attach)
|
||||||
self.assertEquals(self.filename,"7501'S FOR TWO GOLDEN SOURCES SHIPMENTS FOR TAX & DUTY PURPOSES ONLY.PDF")
|
self.assertEquals(self.filename,"7501'S FOR TWO GOLDEN SOURCES SHIPMENTS FOR TAX & DUTY PURPOSES ONLY.PDF")
|
||||||
self.assertEquals(rc,Milter.CONTINUE)
|
self.assertEquals(rc,Milter.CONTINUE)
|
||||||
|
|||||||
+95
-10
@@ -4,12 +4,97 @@ import sample
|
|||||||
import mime
|
import mime
|
||||||
import rfc822
|
import rfc822
|
||||||
import StringIO
|
import StringIO
|
||||||
from Milter.test import TestBase
|
|
||||||
|
|
||||||
class TestMilter(TestBase,sample.sampleMilter):
|
class TestMilter(sample.sampleMilter):
|
||||||
|
|
||||||
|
_protocol = 0
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
TestBase.__init__(self)
|
self.logfp = open("test/milter.log","a")
|
||||||
sample.sampleMilter.__init__(self)
|
|
||||||
|
def log(self,*msg):
|
||||||
|
for i in msg: print >>self.logfp, i,
|
||||||
|
print >>self.logfp
|
||||||
|
|
||||||
|
def replacebody(self,chunk):
|
||||||
|
if self._body:
|
||||||
|
self._body.write(chunk)
|
||||||
|
self.bodyreplaced = True
|
||||||
|
else:
|
||||||
|
raise IOError,"replacebody not called from eom()"
|
||||||
|
|
||||||
|
# FIXME: rfc822 indexing does not really reflect the way chg/add header
|
||||||
|
# work for a milter
|
||||||
|
def chgheader(self,field,idx,value):
|
||||||
|
self.log('chgheader: %s[%d]=%s' % (field,idx,value))
|
||||||
|
if value == '':
|
||||||
|
del self._msg[field]
|
||||||
|
else:
|
||||||
|
self._msg[field] = value
|
||||||
|
self.headerschanged = True
|
||||||
|
|
||||||
|
def addheader(self,field,value):
|
||||||
|
self.log('addheader: %s=%s' % (field,value))
|
||||||
|
self._msg[field] = value
|
||||||
|
self.headerschanged = True
|
||||||
|
|
||||||
|
def feedMsg(self,fname):
|
||||||
|
self._body = None
|
||||||
|
self.bodyreplaced = False
|
||||||
|
self.headerschanged = 0
|
||||||
|
fp = open('test/'+fname,'r')
|
||||||
|
msg = rfc822.Message(fp)
|
||||||
|
rc = self.envfrom('<spam@advertisements.com>')
|
||||||
|
if rc != Milter.CONTINUE: return rc
|
||||||
|
rc = self.envrcpt('<victim@lamb.com>')
|
||||||
|
if rc != Milter.CONTINUE: return rc
|
||||||
|
line = None
|
||||||
|
for h in msg.headers:
|
||||||
|
if h[:1].isspace():
|
||||||
|
line = line + h
|
||||||
|
continue
|
||||||
|
if not line:
|
||||||
|
line = h
|
||||||
|
continue
|
||||||
|
s = line.split(': ',1)
|
||||||
|
rc = self.header(s[0],s[1].strip())
|
||||||
|
if rc != Milter.CONTINUE: return rc
|
||||||
|
line = h
|
||||||
|
if line:
|
||||||
|
s = line.split(': ',1)
|
||||||
|
rc = self.header(s[0],s[1])
|
||||||
|
if rc != Milter.CONTINUE: return rc
|
||||||
|
rc = self.eoh()
|
||||||
|
if rc != Milter.CONTINUE: return rc
|
||||||
|
while 1:
|
||||||
|
buf = fp.read(8192)
|
||||||
|
if len(buf) == 0: break
|
||||||
|
rc = self.body(buf)
|
||||||
|
if rc != Milter.CONTINUE: return rc
|
||||||
|
self._msg = msg
|
||||||
|
self._body = StringIO.StringIO()
|
||||||
|
rc = self.eom()
|
||||||
|
if self.bodyreplaced:
|
||||||
|
body = self._body.getvalue()
|
||||||
|
else:
|
||||||
|
msg.rewindbody()
|
||||||
|
body = msg.fp.read()
|
||||||
|
self._body = StringIO.StringIO()
|
||||||
|
self._body.writelines(msg.headers)
|
||||||
|
self._body.write('\n')
|
||||||
|
self._body.write(body)
|
||||||
|
return rc
|
||||||
|
|
||||||
|
def connect(self,host='localhost'):
|
||||||
|
self._body = None
|
||||||
|
self.bodyreplaced = False
|
||||||
|
rc = sample.sampleMilter.connect(self,host,1,0)
|
||||||
|
if rc != Milter.CONTINUE and rc != Milter.ACCEPT:
|
||||||
|
self.close()
|
||||||
|
return rc
|
||||||
|
rc = self.hello('spamrelay')
|
||||||
|
if rc != Milter.CONTINUE:
|
||||||
|
self.close()
|
||||||
|
return rc
|
||||||
|
|
||||||
class BMSMilterTestCase(unittest.TestCase):
|
class BMSMilterTestCase(unittest.TestCase):
|
||||||
|
|
||||||
@@ -19,7 +104,7 @@ class BMSMilterTestCase(unittest.TestCase):
|
|||||||
self.failUnless(rc == Milter.CONTINUE)
|
self.failUnless(rc == Milter.CONTINUE)
|
||||||
rc = milter.feedMsg(fname)
|
rc = milter.feedMsg(fname)
|
||||||
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
|
||||||
open('test/'+fname+".tstout","w").write(fp.getvalue())
|
open('test/'+fname+".tstout","w").write(fp.getvalue())
|
||||||
#self.failUnless(fp.getvalue() == open("test/virus1.out","r").read())
|
#self.failUnless(fp.getvalue() == open("test/virus1.out","r").read())
|
||||||
@@ -34,7 +119,7 @@ class BMSMilterTestCase(unittest.TestCase):
|
|||||||
milter.connect('somehost')
|
milter.connect('somehost')
|
||||||
rc = milter.feedMsg(fname)
|
rc = milter.feedMsg(fname)
|
||||||
self.failUnless(rc == Milter.ACCEPT)
|
self.failUnless(rc == Milter.ACCEPT)
|
||||||
self.failIf(milter._bodyreplaced,"Milter needlessly replaced body.")
|
self.failIf(milter.bodyreplaced,"Milter needlessly replaced body.")
|
||||||
fp = milter._body
|
fp = milter._body
|
||||||
open('test/'+fname+".tstout","w").write(fp.getvalue())
|
open('test/'+fname+".tstout","w").write(fp.getvalue())
|
||||||
milter.close()
|
milter.close()
|
||||||
@@ -44,17 +129,17 @@ class BMSMilterTestCase(unittest.TestCase):
|
|||||||
milter.connect('somehost')
|
milter.connect('somehost')
|
||||||
rc = milter.feedMsg('samp1')
|
rc = milter.feedMsg('samp1')
|
||||||
self.failUnless(rc == Milter.ACCEPT)
|
self.failUnless(rc == Milter.ACCEPT)
|
||||||
self.failIf(milter._bodyreplaced,"Milter needlessly replaced body.")
|
self.failIf(milter.bodyreplaced,"Milter needlessly replaced body.")
|
||||||
rc = milter.feedMsg("virus3")
|
rc = milter.feedMsg("virus3")
|
||||||
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
|
||||||
open("test/virus3.tstout","w").write(fp.getvalue())
|
open("test/virus3.tstout","w").write(fp.getvalue())
|
||||||
#self.failUnless(fp.getvalue() == open("test/virus3.out","r").read())
|
#self.failUnless(fp.getvalue() == open("test/virus3.out","r").read())
|
||||||
rc = milter.feedMsg("virus6")
|
rc = milter.feedMsg("virus6")
|
||||||
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")
|
||||||
self.failUnless(milter._headerschanged,"Message headers not adjusted")
|
self.failUnless(milter.headerschanged,"Message headers not adjusted")
|
||||||
fp = milter._body
|
fp = milter._body
|
||||||
open("test/virus6.tstout","w").write(fp.getvalue())
|
open("test/virus6.tstout","w").write(fp.getvalue())
|
||||||
milter.close()
|
milter.close()
|
||||||
|
|||||||
Reference in New Issue
Block a user