summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Doc/library/smtpd.rst19
-rwxr-xr-xLib/smtpd.py12
-rw-r--r--Lib/test/test_logging.py64
-rw-r--r--Misc/NEWS3
4 files changed, 30 insertions, 68 deletions
diff --git a/Doc/library/smtpd.rst b/Doc/library/smtpd.rst
index 2ca71ff..2c2df8a 100644
--- a/Doc/library/smtpd.rst
+++ b/Doc/library/smtpd.rst
@@ -27,7 +27,8 @@ SMTPServer Objects
------------------
-.. class:: SMTPServer(localaddr, remoteaddr, data_size_limit=33554432)
+.. class:: SMTPServer(localaddr, remoteaddr, data_size_limit=33554432,
+ map=None)
Create a new :class:`SMTPServer` object, which binds to local address
*localaddr*. It will treat *remoteaddr* as an upstream SMTP relayer. It
@@ -38,6 +39,8 @@ SMTPServer Objects
accepted in a ``DATA`` command. A value of ``None`` or ``0`` means no
limit.
+ A dictionary can be specified in *map* to avoid using a global socket map.
+
.. method:: process_message(peer, mailfrom, rcpttos, data)
Raise :exc:`NotImplementedError` exception. Override this in subclasses to
@@ -53,6 +56,9 @@ SMTPServer Objects
Override this in subclasses to use a custom :class:`SMTPChannel` for
managing SMTP clients.
+ .. versionchanged:: 3.4
+ The *map* argument was added.
+
DebuggingServer Objects
-----------------------
@@ -90,11 +96,20 @@ MailmanProxy Objects
SMTPChannel Objects
-------------------
-.. class:: SMTPChannel(server, conn, addr)
+.. class:: SMTPChannel(server, conn, addr, data_size_limit=33554432,
+ map=None))
Create a new :class:`SMTPChannel` object which manages the communication
between the server and a single SMTP client.
+ *conn* and *addr* are as per the instance variables described below.
+
+ *data_size_limit* specifies the maximum number of bytes that will be
+ accepted in a ``DATA`` command. A value of ``None`` or ``0`` means no
+ limit.
+
+ A dictionary can be specified in *map* to avoid using a global socket map.
+
To use a custom SMTPChannel implementation you need to override the
:attr:`SMTPServer.channel_class` of your :class:`SMTPServer`.
diff --git a/Lib/smtpd.py b/Lib/smtpd.py
index 3cab264..f28036a 100755
--- a/Lib/smtpd.py
+++ b/Lib/smtpd.py
@@ -121,8 +121,9 @@ class SMTPChannel(asynchat.async_chat):
})
max_command_size_limit = max(command_size_limits.values())
- def __init__(self, server, conn, addr, data_size_limit=DATA_SIZE_DEFAULT):
- asynchat.async_chat.__init__(self, conn)
+ def __init__(self, server, conn, addr, data_size_limit=DATA_SIZE_DEFAULT,
+ map=None):
+ asynchat.async_chat.__init__(self, conn, map=map)
self.smtp_server = server
self.conn = conn
self.addr = addr
@@ -576,11 +577,11 @@ class SMTPServer(asyncore.dispatcher):
channel_class = SMTPChannel
def __init__(self, localaddr, remoteaddr,
- data_size_limit=DATA_SIZE_DEFAULT):
+ data_size_limit=DATA_SIZE_DEFAULT, map=None):
self._localaddr = localaddr
self._remoteaddr = remoteaddr
self.data_size_limit = data_size_limit
- asyncore.dispatcher.__init__(self)
+ asyncore.dispatcher.__init__(self, map=map)
try:
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
# try to re-use a server port if possible
@@ -597,7 +598,8 @@ class SMTPServer(asyncore.dispatcher):
def handle_accepted(self, conn, addr):
print('Incoming connection from %s' % repr(addr), file=DEBUGSTREAM)
- channel = self.channel_class(self, conn, addr, self.data_size_limit)
+ channel = self.channel_class(self, conn, addr, self.data_size_limit,
+ self._map)
# API for "doing something useful with the message"
def process_message(self, peer, mailfrom, rcpttos, data):
diff --git a/Lib/test/test_logging.py b/Lib/test/test_logging.py
index aa878da..63d49fe 100644
--- a/Lib/test/test_logging.py
+++ b/Lib/test/test_logging.py
@@ -659,41 +659,6 @@ class StreamHandlerTest(BaseTest):
# -- if it proves to be of wider utility than just test_logging
if threading:
- class TestSMTPChannel(smtpd.SMTPChannel):
- """
- This derived class has had to be created because smtpd does not
- support use of custom channel maps, although they are allowed by
- asyncore's design. Issue #11959 has been raised to address this,
- and if resolved satisfactorily, some of this code can be removed.
- """
- def __init__(self, server, conn, addr, sockmap):
- asynchat.async_chat.__init__(self, conn, sockmap)
- self.smtp_server = server
- self.conn = conn
- self.addr = addr
- self.data_size_limit = None
- self.received_lines = []
- self.smtp_state = self.COMMAND
- self.seen_greeting = ''
- self.mailfrom = None
- self.rcpttos = []
- self.received_data = ''
- self.fqdn = socket.getfqdn()
- self.num_bytes = 0
- try:
- self.peer = conn.getpeername()
- except OSError as err:
- # a race condition may occur if the other end is closing
- # before we can get the peername
- self.close()
- if err.args[0] != errno.ENOTCONN:
- raise
- return
- self.push('220 %s %s' % (self.fqdn, smtpd.__version__))
- self.set_terminator(b'\r\n')
- self.extended_smtp = False
-
-
class TestSMTPServer(smtpd.SMTPServer):
"""
This class implements a test SMTP server.
@@ -714,37 +679,14 @@ if threading:
:func:`asyncore.loop`. This avoids changing the
:mod:`asyncore` module's global state.
"""
- channel_class = TestSMTPChannel
def __init__(self, addr, handler, poll_interval, sockmap):
- self._localaddr = addr
- self._remoteaddr = None
- self.data_size_limit = None
- self.sockmap = sockmap
- asyncore.dispatcher.__init__(self, map=sockmap)
- try:
- sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- sock.setblocking(0)
- self.set_socket(sock, map=sockmap)
- # try to re-use a server port if possible
- self.set_reuse_addr()
- self.bind(addr)
- self.port = sock.getsockname()[1]
- self.listen(5)
- except:
- self.close()
- raise
+ smtpd.SMTPServer.__init__(self, addr, None, map=sockmap)
+ self.port = self.socket.getsockname()[1]
self._handler = handler
self._thread = None
self.poll_interval = poll_interval
- def handle_accepted(self, conn, addr):
- """
- Redefined only because the base class does not pass in a
- map, forcing use of a global in :mod:`asyncore`.
- """
- channel = self.channel_class(self, conn, addr, self.sockmap)
-
def process_message(self, peer, mailfrom, rcpttos, data):
"""
Delegates to the handler passed in to the server's constructor.
@@ -775,7 +717,7 @@ if threading:
:func:`asyncore.loop`.
"""
try:
- asyncore.loop(poll_interval, map=self.sockmap)
+ asyncore.loop(poll_interval, map=self._map)
except OSError:
# On FreeBSD 8, closing the server repeatably
# raises this error. We swallow it if the
diff --git a/Misc/NEWS b/Misc/NEWS
index 8e4dbcb..84e9137 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -112,6 +112,9 @@ Core and Builtins
Library
-------
+- Issue #11959: SMTPServer and SMTPChannel now take an optional map, use of
+ which avoids affecting global state.
+
- Issue #18109: os.uname() now decodes fields from the locale encoding, and
socket.gethostname() now decodes the hostname from the locale encoding,
instead of using the UTF-8 encoding in strict mode.