Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1efc262dc5 | |||
| f3fbb1c99d | |||
| 27887daf3f | |||
| 23defb880b | |||
| 7502c29e47 | |||
| 594d3ad365 | |||
| b2e0b2ebc6 | |||
| 04a241f1e9 | |||
| 16bfe5d4da | |||
| 70d19001c0 | |||
| 0d001dd8e9 | |||
| 8f4a82794c | |||
| de0ec3430d | |||
| c9e32e4b06 | |||
| 83a1762515 | |||
| feb6526cb8 | |||
| 3a3add814e |
@@ -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.5
|
PROJECT_NUMBER = 0.9.6
|
||||||
|
|
||||||
# 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.
|
||||||
|
|||||||
+65
-9
@@ -8,13 +8,15 @@
|
|||||||
# 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.5'
|
__version__ = '0.9.7'
|
||||||
|
|
||||||
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
|
||||||
@@ -47,6 +49,9 @@ 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]
|
||||||
@@ -137,7 +142,12 @@ 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__)
|
||||||
return func
|
def wrapper(self,*args):
|
||||||
|
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
|
||||||
@@ -152,9 +162,14 @@ 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: return NOREPLY
|
if self._protocol & nr_mask:
|
||||||
|
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
|
||||||
@@ -247,7 +262,9 @@ 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 for each connection to the MTA. Called by the
|
||||||
|
# <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:
|
||||||
@@ -260,6 +277,17 @@ 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>
|
||||||
@@ -271,12 +299,26 @@ 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 when the SMTP client says MAIL FROM. Called by the
|
||||||
|
# <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 when the SMTP client says RCPT TO. Called by the
|
||||||
|
# <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.
|
||||||
@@ -310,7 +352,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.
|
||||||
@@ -336,7 +378,10 @@ 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.
|
# xffi_negotiate</a> callback. This is an advanced 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.
|
||||||
@@ -363,14 +408,24 @@ 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.
|
# first msg line is used. Any '%' in a message line
|
||||||
|
# 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!
|
||||||
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.
|
||||||
@@ -714,4 +769,5 @@ 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
|
||||||
#
|
#
|
||||||
|
|||||||
+24
-4
@@ -70,15 +70,23 @@ 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 not result:
|
if cname:
|
||||||
|
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 == (name, 'CNAME'):
|
if k == cnamek:
|
||||||
cname = v
|
cname = v
|
||||||
if (qtype,k[1]) in safe2cache:
|
if k[1] == 'CNAME' or (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:
|
||||||
@@ -89,10 +97,22 @@ 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__':
|
||||||
|
|||||||
+1
-1
@@ -50,7 +50,7 @@ class Greylist(object):
|
|||||||
# expired
|
# expired
|
||||||
log.debug('Expired greylist: %s',key)
|
log.debug('Expired greylist: %s',key)
|
||||||
r = Record()
|
r = Record()
|
||||||
elif now < r.firstseen + self.greylist_time:
|
elif now < r.firstseen + self.greylist_time + 5:
|
||||||
# still greylisted
|
# still greylisted
|
||||||
log.debug('Early greylist: %s',key)
|
log.debug('Early greylist: %s',key)
|
||||||
#r = Record()
|
#r = Record()
|
||||||
|
|||||||
+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)
|
return socket.inet_pton(socket.AF_INET6,s.strip())
|
||||||
else:
|
else:
|
||||||
from pyip6 import inet_ntop, inet_pton
|
from pyip6 import inet_ntop, inet_pton
|
||||||
|
|
||||||
|
|||||||
@@ -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.5
|
VERSION=0.9.6
|
||||||
CVSTAG=pymilter-0_9_5
|
CVSTAG=pymilter-0_9_6
|
||||||
PKG=pymilter-$(VERSION)
|
PKG=pymilter-$(VERSION)
|
||||||
SRCTAR=$(PKG).tar.gz
|
SRCTAR=$(PKG).tar.gz
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,79 @@
|
|||||||
|
## 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()
|
||||||
+1
-2
@@ -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, recipient, *str):
|
def envrcpt(self, to, *str):
|
||||||
rcptinfo = to,Milter.dictfromlist(str)
|
rcptinfo = to,Milter.dictfromlist(str)
|
||||||
self.R.append(rcptinfo)
|
self.R.append(rcptinfo)
|
||||||
|
|
||||||
@@ -110,7 +110,6 @@ 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.
|
||||||
|
|||||||
+48
-34
@@ -35,6 +35,12 @@ $ python setup.py help
|
|||||||
libraries=["milter","smutil","resolv"]
|
libraries=["milter","smutil","resolv"]
|
||||||
|
|
||||||
* $Log$
|
* $Log$
|
||||||
|
* 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.
|
||||||
*
|
*
|
||||||
@@ -257,10 +263,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 now 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.
|
||||||
*/
|
*/
|
||||||
#ifdef linux
|
#if defined(__FreeBSD_kernel__) || defined(__linux__)
|
||||||
#define HAVE_IPV6_RFC2553
|
#define HAVE_IPV6_RFC2553
|
||||||
#include <arpa/inet.h>
|
#include <arpa/inet.h>
|
||||||
#endif
|
#endif
|
||||||
@@ -274,41 +280,49 @@ $ python setup.py help
|
|||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* Yes, these are static. If you need multiple different callbacks, */
|
enum callbacks {
|
||||||
/* it's cleaner to use multiple filters, or convert to OO method calls. */
|
CONNECT,HELO,ENVFROM,ENVRCPT,HEADER,EOH,BODY,EOM,ABORT,CLOSE,
|
||||||
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
|
||||||
static PyObject *unknown_callback = NULL;
|
UNKNOWN,DATA,NEGOTIATE,
|
||||||
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 **cbp;
|
PyObject *cb;
|
||||||
const char *name;
|
const char *name;
|
||||||
} callback_names[] = {
|
} callback[NUMCALLBACKS+1] = {
|
||||||
{ &connect_callback,"connect" },
|
{ NULL ,"connect" },
|
||||||
{ &helo_callback,"helo" },
|
{ NULL ,"helo" },
|
||||||
{ &envfrom_callback,"envfrom" },
|
{ NULL ,"envfrom" },
|
||||||
{ &envrcpt_callback,"envrcpt" },
|
{ NULL ,"envrcpt" },
|
||||||
{ &header_callback,"header" },
|
{ NULL ,"header" },
|
||||||
{ &eoh_callback,"eoh" },
|
{ NULL ,"eoh" },
|
||||||
{ &body_callback,"body" },
|
{ NULL ,"body" },
|
||||||
{ &eom_callback,"eom" },
|
{ NULL ,"eom" },
|
||||||
{ &abort_callback,"abort" },
|
{ NULL ,"abort" },
|
||||||
{ &close_callback,"close" },
|
{ NULL ,"close" },
|
||||||
#ifdef SMFIS_ALL_OPTS
|
#ifdef SMFIS_ALL_OPTS
|
||||||
{ &unknown_callback,"unknown" },
|
{ NULL ,"unknown" },
|
||||||
{ &data_callback,"data" },
|
{ NULL ,"data" },
|
||||||
{ &negotiate_callback,"negotiate" },
|
{ NULL ,"negotiate" },
|
||||||
#endif
|
#endif
|
||||||
{ NULL , NULL }
|
{ NULL , NULL }
|
||||||
};
|
};
|
||||||
@@ -667,8 +681,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_names; p->cbp; ++p) {
|
for (p = callback; p->name; ++p) {
|
||||||
if (cb == *p->cbp) {
|
if (cb == p->cb) {
|
||||||
cbname = p->name;
|
cbname = p->name;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
# $Log$
|
# $Log$
|
||||||
|
# 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.
|
||||||
#
|
#
|
||||||
@@ -165,15 +168,14 @@ 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)
|
||||||
|
|||||||
+11
-2
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
Summary: Python interface to sendmail milter API
|
Summary: Python interface to sendmail milter API
|
||||||
Name: %{pythonbase}-pymilter
|
Name: %{pythonbase}-pymilter
|
||||||
Version: 0.9.5
|
Version: 0.9.7
|
||||||
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,7 +75,16 @@ chmod a+x $RPM_BUILD_ROOT%{libdir}/start.sh
|
|||||||
rm -rf $RPM_BUILD_ROOT
|
rm -rf $RPM_BUILD_ROOT
|
||||||
|
|
||||||
%changelog
|
%changelog
|
||||||
* Wed Mar 02 2010 Stuart Gathman <stuart@bmsi.com> 0.9.5-1
|
* 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,7 +35,9 @@ 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.5',
|
setup(name = "pymilter", version = '0.9.7',
|
||||||
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
|
||||||
|
|||||||
Reference in New Issue
Block a user