mirror of https://github.com/python/cpython
Fix closes Issue11281 - smtplib.STMP gets source_address parameter, which adds the ability to bind to specific source address on a machine with multiple interfaces. Patch by Paulo Scardine.
This commit is contained in:
parent
f83e4acbae
commit
3d23fd6493
|
@ -20,7 +20,7 @@ details of SMTP and ESMTP operation, consult :rfc:`821` (Simple Mail Transfer
|
|||
Protocol) and :rfc:`1869` (SMTP Service Extensions).
|
||||
|
||||
|
||||
.. class:: SMTP(host='', port=0, local_hostname=None[, timeout])
|
||||
.. class:: SMTP(host='', port=0, local_hostname=None[, timeout], source_address=None)
|
||||
|
||||
A :class:`SMTP` instance encapsulates an SMTP connection. It has methods
|
||||
that support a full repertoire of SMTP and ESMTP operations. If the optional
|
||||
|
@ -29,7 +29,12 @@ Protocol) and :rfc:`1869` (SMTP Service Extensions).
|
|||
raised if the specified host doesn't respond correctly. The optional
|
||||
*timeout* parameter specifies a timeout in seconds for blocking operations
|
||||
like the connection attempt (if not specified, the global default timeout
|
||||
setting will be used).
|
||||
setting will be used). The optional source_address parameter allows to bind to some
|
||||
specific source address in a machine with multiple network interfaces,
|
||||
and/or to some specific source tcp port. It takes a 2-tuple (host, port),
|
||||
for the socket to bind to as its source address before connecting. If
|
||||
ommited (or if host or port are '' and/or 0 respectively) the OS default
|
||||
behavior will be used.
|
||||
|
||||
For normal use, you should only require the initialization/connect,
|
||||
:meth:`sendmail`, and :meth:`quit` methods. An example is included below.
|
||||
|
@ -48,8 +53,10 @@ Protocol) and :rfc:`1869` (SMTP Service Extensions).
|
|||
.. versionchanged:: 3.3
|
||||
Support for the :keyword:`with` statement was added.
|
||||
|
||||
.. versionadded:: 3.3
|
||||
source_address parameter.
|
||||
|
||||
.. class:: SMTP_SSL(host='', port=0, local_hostname=None, keyfile=None, certfile=None[, timeout], context=None)
|
||||
.. class:: SMTP_SSL(host='', port=0, local_hostname=None, keyfile=None, certfile=None[, timeout], context=None, source_address=None)
|
||||
|
||||
A :class:`SMTP_SSL` instance behaves exactly the same as instances of
|
||||
:class:`SMTP`. :class:`SMTP_SSL` should be used for situations where SSL is
|
||||
|
@ -62,18 +69,28 @@ Protocol) and :rfc:`1869` (SMTP Service Extensions).
|
|||
keyfile and certfile must be None. The optional *timeout*
|
||||
parameter specifies a timeout in seconds for blocking operations like the
|
||||
connection attempt (if not specified, the global default timeout setting
|
||||
will be used).
|
||||
will be used). The optional source_address parameter allows to bind to some
|
||||
specific source address in a machine with multiple network interfaces,
|
||||
and/or to some specific source tcp port. It takes a 2-tuple (host, port),
|
||||
for the socket to bind to as its source address before connecting. If
|
||||
ommited (or if host or port are '' and/or 0 respectively) the OS default
|
||||
behavior will be used.
|
||||
|
||||
.. versionchanged:: 3.3
|
||||
*context* was added.
|
||||
|
||||
.. versionadded:: 3.3
|
||||
source_address parameter.
|
||||
|
||||
.. class:: LMTP(host='', port=LMTP_PORT, local_hostname=None)
|
||||
|
||||
.. class:: LMTP(host='', port=LMTP_PORT, local_hostname=None, source_address=None)
|
||||
|
||||
The LMTP protocol, which is very similar to ESMTP, is heavily based on the
|
||||
standard SMTP client. It's common to use Unix sockets for LMTP, so our :meth:`connect`
|
||||
method must support that as well as a regular host:port server. To specify a
|
||||
Unix socket, you must use an absolute path for *host*, starting with a '/'.
|
||||
standard SMTP client. It's common to use Unix sockets for LMTP, so our
|
||||
:meth:`connect` method must support that as well as a regular host:port
|
||||
server. The optional parameters local_hostname and source_address has the
|
||||
same meaning as that of SMTP client.To specify a Unix socket, you must use
|
||||
an absolute path for *host*, starting with a '/'.
|
||||
|
||||
Authentication is supported, using the regular SMTP mechanism. When using a Unix
|
||||
socket, LMTP generally don't support or require any authentication, but your
|
||||
|
|
|
@ -215,7 +215,8 @@ class SMTP:
|
|||
default_port = SMTP_PORT
|
||||
|
||||
def __init__(self, host='', port=0, local_hostname=None,
|
||||
timeout=socket._GLOBAL_DEFAULT_TIMEOUT):
|
||||
timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
|
||||
source_address=None):
|
||||
"""Initialize a new instance.
|
||||
|
||||
If specified, `host' is the name of the remote host to which to
|
||||
|
@ -223,11 +224,16 @@ class SMTP:
|
|||
By default, smtplib.SMTP_PORT is used. An SMTPConnectError is raised
|
||||
if the specified `host' doesn't respond correctly. If specified,
|
||||
`local_hostname` is used as the FQDN of the local host. By default,
|
||||
the local hostname is found using socket.getfqdn().
|
||||
the local hostname is found using socket.getfqdn(). The
|
||||
`source_address` parameter takes a 2-tuple (host, port) for the socket
|
||||
to bind to as its source address before connecting. If the host is ''
|
||||
and port is 0, the OS default behavior will be used.
|
||||
|
||||
"""
|
||||
self.timeout = timeout
|
||||
self.esmtp_features = {}
|
||||
self.source_address = source_address
|
||||
|
||||
if host:
|
||||
(code, msg) = self.connect(host, port)
|
||||
if code != 220:
|
||||
|
@ -276,10 +282,11 @@ class SMTP:
|
|||
# This makes it simpler for SMTP_SSL to use the SMTP connect code
|
||||
# and just alter the socket connection bit.
|
||||
if self.debuglevel > 0:
|
||||
print('connect:', (host, port), file=stderr)
|
||||
return socket.create_connection((host, port), timeout)
|
||||
print('connect: to', (host, port), self.source_address, file=stderr)
|
||||
return socket.create_connection((host, port), timeout,
|
||||
self.source_address)
|
||||
|
||||
def connect(self, host='localhost', port=0):
|
||||
def connect(self, host='localhost', port=0, source_address=None):
|
||||
"""Connect to a host on a given port.
|
||||
|
||||
If the hostname ends with a colon (`:') followed by a number, and
|
||||
|
@ -290,6 +297,7 @@ class SMTP:
|
|||
specified during instantiation.
|
||||
|
||||
"""
|
||||
if source_address: self.source_address = source_address
|
||||
if not port and (host.find(':') == host.rfind(':')):
|
||||
i = host.rfind(':')
|
||||
if i >= 0:
|
||||
|
@ -829,7 +837,8 @@ if _have_ssl:
|
|||
""" This is a subclass derived from SMTP that connects over an SSL encrypted
|
||||
socket (to use this class you need a socket module that was compiled with SSL
|
||||
support). If host is not specified, '' (the local host) is used. If port is
|
||||
omitted, the standard SMTP-over-SSL port (465) is used. keyfile and certfile
|
||||
omitted, the standard SMTP-over-SSL port (465) is used. The optional
|
||||
source_address takes a two-tuple (host,port) for socket to bind to. keyfile and certfile
|
||||
are also optional - they can contain a PEM formatted private key and
|
||||
certificate chain file for the SSL connection. context also optional, can contain
|
||||
a SSLContext, and is an alternative to keyfile and certfile; If it is specified both
|
||||
|
@ -840,7 +849,8 @@ if _have_ssl:
|
|||
|
||||
def __init__(self, host='', port=0, local_hostname=None,
|
||||
keyfile=None, certfile=None,
|
||||
timeout=socket._GLOBAL_DEFAULT_TIMEOUT, context=None):
|
||||
timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
|
||||
source_address=None, context=None):
|
||||
if context is not None and keyfile is not None:
|
||||
raise ValueError("context and keyfile arguments are mutually "
|
||||
"exclusive")
|
||||
|
@ -850,12 +860,14 @@ if _have_ssl:
|
|||
self.keyfile = keyfile
|
||||
self.certfile = certfile
|
||||
self.context = context
|
||||
SMTP.__init__(self, host, port, local_hostname, timeout)
|
||||
SMTP.__init__(self, host, port, local_hostname, timeout,
|
||||
source_address)
|
||||
|
||||
def _get_socket(self, host, port, timeout):
|
||||
if self.debuglevel > 0:
|
||||
print('connect:', (host, port), file=stderr)
|
||||
new_socket = socket.create_connection((host, port), timeout)
|
||||
new_socket = socket.create_connection((host, port), timeout,
|
||||
self.source_address)
|
||||
if self.context is not None:
|
||||
new_socket = self.context.wrap_socket(new_socket)
|
||||
else:
|
||||
|
@ -884,14 +896,16 @@ class LMTP(SMTP):
|
|||
|
||||
ehlo_msg = "lhlo"
|
||||
|
||||
def __init__(self, host='', port=LMTP_PORT, local_hostname=None):
|
||||
def __init__(self, host='', port=LMTP_PORT, local_hostname=None,
|
||||
source_address=None):
|
||||
"""Initialize a new instance."""
|
||||
SMTP.__init__(self, host, port, local_hostname)
|
||||
SMTP.__init__(self, host, port, local_hostname = local_hostname,
|
||||
source_address = source_address)
|
||||
|
||||
def connect(self, host='localhost', port=0):
|
||||
def connect(self, host='localhost', port=0, source_address=None):
|
||||
"""Connect to the LMTP daemon, on either a Unix or a TCP socket."""
|
||||
if host[0] != '/':
|
||||
return SMTP.connect(self, host, port)
|
||||
return SMTP.connect(self, host, port, source_address = source_address)
|
||||
|
||||
# Handle Unix-domain sockets.
|
||||
try:
|
||||
|
|
|
@ -106,7 +106,8 @@ def socket(family=None, type=None, proto=None):
|
|||
return MockSocket()
|
||||
|
||||
|
||||
def create_connection(address, timeout=socket_module._GLOBAL_DEFAULT_TIMEOUT):
|
||||
def create_connection(address, timeout=socket_module._GLOBAL_DEFAULT_TIMEOUT,
|
||||
source_address=None):
|
||||
try:
|
||||
int_port = int(address[1])
|
||||
except ValueError:
|
||||
|
|
|
@ -72,6 +72,14 @@ class GeneralTests(unittest.TestCase):
|
|||
smtp = smtplib.SMTP(HOST, self.port)
|
||||
smtp.close()
|
||||
|
||||
def testSourceAddress(self):
|
||||
mock_socket.reply_with(b"220 Hola mundo")
|
||||
# connects
|
||||
smtp = smtplib.SMTP(HOST, self.port,
|
||||
source_address=('127.0.0.1',19876))
|
||||
self.assertEqual(smtp.source_address, ('127.0.0.1', 19876))
|
||||
smtp.close()
|
||||
|
||||
def testBasic2(self):
|
||||
mock_socket.reply_with(b"220 Hola mundo")
|
||||
# connects, include port in host name
|
||||
|
@ -206,6 +214,15 @@ class DebuggingServerTests(unittest.TestCase):
|
|||
smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost', timeout=3)
|
||||
smtp.quit()
|
||||
|
||||
def testSourceAddress(self):
|
||||
# connect
|
||||
smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost', timeout=3,
|
||||
source_address=('127.0.0.1', 19876))
|
||||
self.assertEqual(smtp.source_address, ('127.0.0.1', 19876))
|
||||
self.assertEqual(smtp.local_hostname, 'localhost')
|
||||
print(dir(smtp))
|
||||
smtp.quit()
|
||||
|
||||
def testNOOP(self):
|
||||
smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost', timeout=3)
|
||||
expected = (250, b'Ok')
|
||||
|
|
|
@ -246,6 +246,10 @@ Core and Builtins
|
|||
Library
|
||||
-------
|
||||
|
||||
- Issue #11281: smtplib.STMP gets source_address parameter, which adds the
|
||||
ability to bind to specific source address on a machine with multiple
|
||||
interfaces. Patch by Paulo Scardine.
|
||||
|
||||
- Issue #12464: tempfile.TemporaryDirectory.cleanup() should not follow
|
||||
symlinks: fix it. Patch by Petri Lehtinen.
|
||||
|
||||
|
|
Loading…
Reference in New Issue