summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSenthil Kumaran <senthil@uthcode.com>2011-07-30 02:56:50 (GMT)
committerSenthil Kumaran <senthil@uthcode.com>2011-07-30 02:56:50 (GMT)
commit3d23fd649309fe80fdd1dee04b668fefb50c1b97 (patch)
tree912395e2e554691e6b32a3dbab7b1254a15381fc
parentf83e4acbaec012e01e11d5cc4ea6514cf2a7b34c (diff)
downloadcpython-3d23fd649309fe80fdd1dee04b668fefb50c1b97.zip
cpython-3d23fd649309fe80fdd1dee04b668fefb50c1b97.tar.gz
cpython-3d23fd649309fe80fdd1dee04b668fefb50c1b97.tar.bz2
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.
-rw-r--r--Doc/library/smtplib.rst33
-rw-r--r--Lib/smtplib.py40
-rw-r--r--Lib/test/mock_socket.py3
-rw-r--r--Lib/test/test_smtplib.py17
-rw-r--r--Misc/NEWS4
5 files changed, 75 insertions, 22 deletions
diff --git a/Doc/library/smtplib.rst b/Doc/library/smtplib.rst
index 24aff05..afe6b7b 100644
--- a/Doc/library/smtplib.rst
+++ b/Doc/library/smtplib.rst
@@ -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
diff --git a/Lib/smtplib.py b/Lib/smtplib.py
index c7ff821..2039592 100644
--- a/Lib/smtplib.py
+++ b/Lib/smtplib.py
@@ -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:
diff --git a/Lib/test/mock_socket.py b/Lib/test/mock_socket.py
index 8036932..d09e78c 100644
--- a/Lib/test/mock_socket.py
+++ b/Lib/test/mock_socket.py
@@ -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:
diff --git a/Lib/test/test_smtplib.py b/Lib/test/test_smtplib.py
index 9f8a054..70654c5 100644
--- a/Lib/test/test_smtplib.py
+++ b/Lib/test/test_smtplib.py
@@ -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')
diff --git a/Misc/NEWS b/Misc/NEWS
index 5ab2d17..b0a747c 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -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.