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).
|
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
|
A :class:`SMTP` instance encapsulates an SMTP connection. It has methods
|
||||||
that support a full repertoire of SMTP and ESMTP operations. If the optional
|
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
|
raised if the specified host doesn't respond correctly. The optional
|
||||||
*timeout* parameter specifies a timeout in seconds for blocking operations
|
*timeout* parameter specifies a timeout in seconds for blocking operations
|
||||||
like the connection attempt (if not specified, the global default timeout
|
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,
|
For normal use, you should only require the initialization/connect,
|
||||||
:meth:`sendmail`, and :meth:`quit` methods. An example is included below.
|
: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
|
.. versionchanged:: 3.3
|
||||||
Support for the :keyword:`with` statement was added.
|
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
|
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
|
: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*
|
keyfile and certfile must be None. The optional *timeout*
|
||||||
parameter specifies a timeout in seconds for blocking operations like the
|
parameter specifies a timeout in seconds for blocking operations like the
|
||||||
connection attempt (if not specified, the global default timeout setting
|
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
|
.. versionchanged:: 3.3
|
||||||
*context* was added.
|
*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
|
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`
|
standard SMTP client. It's common to use Unix sockets for LMTP, so our
|
||||||
method must support that as well as a regular host:port server. To specify a
|
:meth:`connect` method must support that as well as a regular host:port
|
||||||
Unix socket, you must use an absolute path for *host*, starting with a '/'.
|
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
|
Authentication is supported, using the regular SMTP mechanism. When using a Unix
|
||||||
socket, LMTP generally don't support or require any authentication, but your
|
socket, LMTP generally don't support or require any authentication, but your
|
||||||
|
@ -215,7 +215,8 @@ class SMTP:
|
|||||||
default_port = SMTP_PORT
|
default_port = SMTP_PORT
|
||||||
|
|
||||||
def __init__(self, host='', port=0, local_hostname=None,
|
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.
|
"""Initialize a new instance.
|
||||||
|
|
||||||
If specified, `host' is the name of the remote host to which to
|
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
|
By default, smtplib.SMTP_PORT is used. An SMTPConnectError is raised
|
||||||
if the specified `host' doesn't respond correctly. If specified,
|
if the specified `host' doesn't respond correctly. If specified,
|
||||||
`local_hostname` is used as the FQDN of the local host. By default,
|
`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.timeout = timeout
|
||||||
self.esmtp_features = {}
|
self.esmtp_features = {}
|
||||||
|
self.source_address = source_address
|
||||||
|
|
||||||
if host:
|
if host:
|
||||||
(code, msg) = self.connect(host, port)
|
(code, msg) = self.connect(host, port)
|
||||||
if code != 220:
|
if code != 220:
|
||||||
@ -276,10 +282,11 @@ class SMTP:
|
|||||||
# This makes it simpler for SMTP_SSL to use the SMTP connect code
|
# This makes it simpler for SMTP_SSL to use the SMTP connect code
|
||||||
# and just alter the socket connection bit.
|
# and just alter the socket connection bit.
|
||||||
if self.debuglevel > 0:
|
if self.debuglevel > 0:
|
||||||
print('connect:', (host, port), file=stderr)
|
print('connect: to', (host, port), self.source_address, file=stderr)
|
||||||
return socket.create_connection((host, port), timeout)
|
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.
|
"""Connect to a host on a given port.
|
||||||
|
|
||||||
If the hostname ends with a colon (`:') followed by a number, and
|
If the hostname ends with a colon (`:') followed by a number, and
|
||||||
@ -290,6 +297,7 @@ class SMTP:
|
|||||||
specified during instantiation.
|
specified during instantiation.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
if source_address: self.source_address = source_address
|
||||||
if not port and (host.find(':') == host.rfind(':')):
|
if not port and (host.find(':') == host.rfind(':')):
|
||||||
i = host.rfind(':')
|
i = host.rfind(':')
|
||||||
if i >= 0:
|
if i >= 0:
|
||||||
@ -829,7 +837,8 @@ if _have_ssl:
|
|||||||
""" This is a subclass derived from SMTP that connects over an SSL encrypted
|
""" 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
|
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
|
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
|
are also optional - they can contain a PEM formatted private key and
|
||||||
certificate chain file for the SSL connection. context also optional, can contain
|
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
|
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,
|
def __init__(self, host='', port=0, local_hostname=None,
|
||||||
keyfile=None, certfile=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:
|
if context is not None and keyfile is not None:
|
||||||
raise ValueError("context and keyfile arguments are mutually "
|
raise ValueError("context and keyfile arguments are mutually "
|
||||||
"exclusive")
|
"exclusive")
|
||||||
@ -850,12 +860,14 @@ if _have_ssl:
|
|||||||
self.keyfile = keyfile
|
self.keyfile = keyfile
|
||||||
self.certfile = certfile
|
self.certfile = certfile
|
||||||
self.context = context
|
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):
|
def _get_socket(self, host, port, timeout):
|
||||||
if self.debuglevel > 0:
|
if self.debuglevel > 0:
|
||||||
print('connect:', (host, port), file=stderr)
|
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:
|
if self.context is not None:
|
||||||
new_socket = self.context.wrap_socket(new_socket)
|
new_socket = self.context.wrap_socket(new_socket)
|
||||||
else:
|
else:
|
||||||
@ -884,14 +896,16 @@ class LMTP(SMTP):
|
|||||||
|
|
||||||
ehlo_msg = "lhlo"
|
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."""
|
"""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."""
|
"""Connect to the LMTP daemon, on either a Unix or a TCP socket."""
|
||||||
if host[0] != '/':
|
if host[0] != '/':
|
||||||
return SMTP.connect(self, host, port)
|
return SMTP.connect(self, host, port, source_address = source_address)
|
||||||
|
|
||||||
# Handle Unix-domain sockets.
|
# Handle Unix-domain sockets.
|
||||||
try:
|
try:
|
||||||
|
@ -106,7 +106,8 @@ def socket(family=None, type=None, proto=None):
|
|||||||
return MockSocket()
|
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:
|
try:
|
||||||
int_port = int(address[1])
|
int_port = int(address[1])
|
||||||
except ValueError:
|
except ValueError:
|
||||||
|
@ -72,6 +72,14 @@ class GeneralTests(unittest.TestCase):
|
|||||||
smtp = smtplib.SMTP(HOST, self.port)
|
smtp = smtplib.SMTP(HOST, self.port)
|
||||||
smtp.close()
|
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):
|
def testBasic2(self):
|
||||||
mock_socket.reply_with(b"220 Hola mundo")
|
mock_socket.reply_with(b"220 Hola mundo")
|
||||||
# connects, include port in host name
|
# 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 = smtplib.SMTP(HOST, self.port, local_hostname='localhost', timeout=3)
|
||||||
smtp.quit()
|
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):
|
def testNOOP(self):
|
||||||
smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost', timeout=3)
|
smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost', timeout=3)
|
||||||
expected = (250, b'Ok')
|
expected = (250, b'Ok')
|
||||||
|
@ -246,6 +246,10 @@ Core and Builtins
|
|||||||
Library
|
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
|
- Issue #12464: tempfile.TemporaryDirectory.cleanup() should not follow
|
||||||
symlinks: fix it. Patch by Petri Lehtinen.
|
symlinks: fix it. Patch by Petri Lehtinen.
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user