Commit e0650206 authored by Antoine Pitrou's avatar Antoine Pitrou

Issue #8809: The SMTP_SSL constructor and SMTP.starttls() now support

passing a `context` argument pointing to an ssl.SSLContext instance.
Patch by Kasun Herath.
parent 5f3b1c49
...@@ -49,7 +49,7 @@ Protocol) and :rfc:`1869` (SMTP Service Extensions). ...@@ -49,7 +49,7 @@ Protocol) and :rfc:`1869` (SMTP Service Extensions).
Support for the :keyword:`with` statement was added. Support for the :keyword:`with` statement was added.
.. class:: SMTP_SSL(host='', port=0, local_hostname=None, keyfile=None, certfile=None[, timeout]) .. class:: SMTP_SSL(host='', port=0, local_hostname=None, keyfile=None, certfile=None[, timeout], context=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
...@@ -57,11 +57,16 @@ Protocol) and :rfc:`1869` (SMTP Service Extensions). ...@@ -57,11 +57,16 @@ Protocol) and :rfc:`1869` (SMTP Service Extensions).
not appropriate. If *host* is not specified, the local host is used. If not appropriate. If *host* is not specified, the local host is used. If
*port* is zero, the standard SMTP-over-SSL port (465) is used. *keyfile* *port* is zero, the standard SMTP-over-SSL port (465) is used. *keyfile*
and *certfile* are also optional, and can contain a PEM formatted private key and *certfile* are also optional, and can contain a PEM formatted private key
and certificate chain file for the SSL connection. The optional *timeout* 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
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).
.. versionchanged:: 3.3
*context* was added.
.. class:: LMTP(host='', port=LMTP_PORT, local_hostname=None) .. class:: LMTP(host='', port=LMTP_PORT, local_hostname=None)
...@@ -256,7 +261,7 @@ An :class:`SMTP` instance has the following methods: ...@@ -256,7 +261,7 @@ An :class:`SMTP` instance has the following methods:
No suitable authentication method was found. No suitable authentication method was found.
.. method:: SMTP.starttls(keyfile=None, certfile=None) .. method:: SMTP.starttls(keyfile=None, certfile=None, context=None)
Put the SMTP connection in TLS (Transport Layer Security) mode. All SMTP Put the SMTP connection in TLS (Transport Layer Security) mode. All SMTP
commands that follow will be encrypted. You should then call :meth:`ehlo` commands that follow will be encrypted. You should then call :meth:`ehlo`
...@@ -265,6 +270,9 @@ An :class:`SMTP` instance has the following methods: ...@@ -265,6 +270,9 @@ An :class:`SMTP` instance has the following methods:
If *keyfile* and *certfile* are provided, these are passed to the :mod:`socket` If *keyfile* and *certfile* are provided, these are passed to the :mod:`socket`
module's :func:`ssl` function. module's :func:`ssl` function.
Optional *context* parameter is a :class:`ssl.SSLContext` object; This is an alternative to
using a keyfile and a certfile and if specified both *keyfile* and *certfile* should be None.
If there has been no previous ``EHLO`` or ``HELO`` command this session, If there has been no previous ``EHLO`` or ``HELO`` command this session,
this method tries ESMTP ``EHLO`` first. this method tries ESMTP ``EHLO`` first.
...@@ -277,6 +285,9 @@ An :class:`SMTP` instance has the following methods: ...@@ -277,6 +285,9 @@ An :class:`SMTP` instance has the following methods:
:exc:`RuntimeError` :exc:`RuntimeError`
SSL/TLS support is not available to your Python interpreter. SSL/TLS support is not available to your Python interpreter.
.. versionchanged:: 3.3
*context* was added.
.. method:: SMTP.sendmail(from_addr, to_addrs, msg, mail_options=[], rcpt_options=[]) .. method:: SMTP.sendmail(from_addr, to_addrs, msg, mail_options=[], rcpt_options=[])
......
...@@ -635,7 +635,7 @@ class SMTP: ...@@ -635,7 +635,7 @@ class SMTP:
# We could not login sucessfully. Return result of last attempt. # We could not login sucessfully. Return result of last attempt.
raise SMTPAuthenticationError(code, resp) raise SMTPAuthenticationError(code, resp)
def starttls(self, keyfile=None, certfile=None): def starttls(self, keyfile=None, certfile=None, context=None):
"""Puts the connection to the SMTP server into TLS mode. """Puts the connection to the SMTP server into TLS mode.
If there has been no previous EHLO or HELO command this session, this If there has been no previous EHLO or HELO command this session, this
...@@ -659,7 +659,16 @@ class SMTP: ...@@ -659,7 +659,16 @@ class SMTP:
if resp == 220: if resp == 220:
if not _have_ssl: if not _have_ssl:
raise RuntimeError("No SSL support included in this Python") raise RuntimeError("No SSL support included in this Python")
self.sock = ssl.wrap_socket(self.sock, keyfile, certfile) if context is not None and keyfile is not None:
raise ValueError("context and keyfile arguments are mutually "
"exclusive")
if context is not None and certfile is not None:
raise ValueError("context and certfile arguments are mutually "
"exclusive")
if context is not None:
self.sock = context.wrap_socket(self.sock)
else:
self.sock = ssl.wrap_socket(self.sock, keyfile, certfile)
self.file = SSLFakeFile(self.sock) self.file = SSLFakeFile(self.sock)
# RFC 3207: # RFC 3207:
# The client MUST discard any knowledge obtained from # The client MUST discard any knowledge obtained from
...@@ -815,23 +824,35 @@ if _have_ssl: ...@@ -815,23 +824,35 @@ if _have_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. 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. 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
keyfile and certfile must be None.
""" """
default_port = SMTP_SSL_PORT default_port = SMTP_SSL_PORT
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): timeout=socket._GLOBAL_DEFAULT_TIMEOUT, context=None):
if context is not None and keyfile is not None:
raise ValueError("context and keyfile arguments are mutually "
"exclusive")
if context is not None and certfile is not None:
raise ValueError("context and certfile arguments are mutually "
"exclusive")
self.keyfile = keyfile self.keyfile = keyfile
self.certfile = certfile self.certfile = certfile
self.context = context
SMTP.__init__(self, host, port, local_hostname, timeout) SMTP.__init__(self, host, port, local_hostname, timeout)
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)
new_socket = ssl.wrap_socket(new_socket, self.keyfile, self.certfile) if self.context is not None:
new_socket = self.context.wrap_socket(new_socket)
else:
new_socket = ssl.wrap_socket(new_socket, self.keyfile, self.certfile)
self.file = SSLFakeFile(new_socket) self.file = SSLFakeFile(new_socket)
return new_socket return new_socket
......
...@@ -3,12 +3,29 @@ ...@@ -3,12 +3,29 @@
import unittest import unittest
from test import support from test import support
import smtplib import smtplib
import ssl
support.requires("network") support.requires("network")
class SmtpTest(unittest.TestCase):
testServer = 'smtp.gmail.com'
remotePort = 25
context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
def test_connect_starttls(self):
support.get_attribute(smtplib, 'SMTP_SSL')
with support.transient_internet(self.testServer):
server = smtplib.SMTP(self.testServer, self.remotePort)
server.starttls(context=self.context)
server.ehlo()
server.quit()
class SmtpSSLTest(unittest.TestCase): class SmtpSSLTest(unittest.TestCase):
testServer = 'smtp.gmail.com' testServer = 'smtp.gmail.com'
remotePort = 465 remotePort = 465
context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
def test_connect(self): def test_connect(self):
support.get_attribute(smtplib, 'SMTP_SSL') support.get_attribute(smtplib, 'SMTP_SSL')
...@@ -24,8 +41,16 @@ class SmtpSSLTest(unittest.TestCase): ...@@ -24,8 +41,16 @@ class SmtpSSLTest(unittest.TestCase):
server.ehlo() server.ehlo()
server.quit() server.quit()
def test_connect_using_sslcontext(self):
support.get_attribute(smtplib, 'SMTP_SSL')
with support.transient_internet(self.testServer):
server = smtplib.SMTP_SSL(self.testServer, self.remotePort, context=self.context)
server.ehlo()
server.quit()
def test_main(): def test_main():
support.run_unittest(SmtpSSLTest) support.run_unittest(SmtpTest, SmtpSSLTest)
if __name__ == "__main__": if __name__ == "__main__":
test_main() test_main()
...@@ -153,6 +153,10 @@ Core and Builtins ...@@ -153,6 +153,10 @@ Core and Builtins
Library Library
------- -------
- Issue #8809: The SMTP_SSL constructor and SMTP.starttls() now support
passing a ``context`` argument pointing to an ssl.SSLContext instance.
Patch by Kasun Herath.
- Issue #11088: don't crash when using F5 to run a script in IDLE on MacOSX - Issue #11088: don't crash when using F5 to run a script in IDLE on MacOSX
with Tk 8.5. with Tk 8.5.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment