Issue #4471: Add the IMAP.starttls() method to enable encryption on

standard IMAP4 connections.  Original patch by Lorenzo M. Catucci.
This commit is contained in:
Antoine Pitrou 2010-11-12 18:49:16 +00:00
parent e0bf419ae7
commit f3b001f966
4 changed files with 63 additions and 8 deletions

View File

@ -56,6 +56,7 @@ Three exceptions are defined as attributes of the :class:`IMAP4` class:
write permission, and the mailbox will need to be re-opened to re-obtain write
permission.
There's also a subclass for secure connections:
@ -68,6 +69,7 @@ There's also a subclass for secure connections:
and *certfile* are also optional - they can contain a PEM formatted private key
and certificate chain file for the SSL connection.
The second subclass allows for connections created by a child process:
@ -406,6 +408,15 @@ An :class:`IMAP4` instance has the following methods:
This is an ``IMAP4rev1`` extension command.
.. method:: IMAP4.starttls(ssl_context=None)
Send a ``STARTTLS`` command. The *ssl_context* argument is optional
and should be a :class:`ssl.SSLContext` object. This will enable
encryption on the IMAP connection.
.. versionadded:: 3.2
.. method:: IMAP4.status(mailbox, names)
Request named status conditions for *mailbox*.

View File

@ -24,6 +24,12 @@ __version__ = "2.58"
import binascii, errno, random, re, socket, subprocess, sys, time
try:
import ssl
HAVE_SSL = True
except ImportError:
HAVE_SSL = False
__all__ = ["IMAP4", "IMAP4_stream", "Internaldate2tuple",
"Int2AP", "ParseFlags", "Time2Internaldate"]
@ -71,6 +77,7 @@ Commands = {
'SETANNOTATION':('AUTH', 'SELECTED'),
'SETQUOTA': ('AUTH', 'SELECTED'),
'SORT': ('SELECTED',),
'STARTTLS': ('NONAUTH',),
'STATUS': ('AUTH', 'SELECTED'),
'STORE': ('SELECTED',),
'SUBSCRIBE': ('AUTH', 'SELECTED'),
@ -156,6 +163,7 @@ class IMAP4:
self.continuation_response = '' # Last continuation response
self.is_readonly = False # READ-ONLY desired state
self.tagnum = 0
self._tls_established = False
# Open socket to server.
@ -711,6 +719,33 @@ class IMAP4:
return self._untagged_response(typ, dat, name)
def starttls(self, ssl_context=None):
name = 'STARTTLS'
if not HAVE_SSL:
raise self.error('SSL support missing')
if self._tls_established:
raise self.abort('TLS session already established')
if name not in self.capabilities:
raise self.abort('TLS not supported by server')
# Generate a default SSL context if none was passed.
if ssl_context is None:
ssl_context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
# SSLv2 considered harmful.
ssl_context.options |= ssl.OP_NO_SSLv2
typ, dat = self._simple_command(name)
if typ == 'OK':
self.sock = ssl_context.wrap_socket(self.sock)
self.file = self.sock.makefile('rb')
self._tls_established = True
typ, dat = self.capability()
if dat == [None]:
raise self.error('no CAPABILITY response from server')
self.capabilities = tuple(dat[-1].upper().split())
else:
raise self.error("Couldn't establish TLS session")
return self._untagged_response(typ, dat, name)
def status(self, mailbox, names):
"""Request named status conditions for mailbox.
@ -1125,12 +1160,8 @@ class IMAP4:
n -= 1
if HAVE_SSL:
try:
import ssl
except ImportError:
pass
else:
class IMAP4_SSL(IMAP4):
"""IMAP4 client class over SSL connection

View File

@ -209,8 +209,6 @@ class RemoteIMAPTest(unittest.TestCase):
def test_logincapa(self):
self.assertTrue('LOGINDISABLED' in self.server.capabilities)
def test_anonlogin(self):
self.assertTrue('AUTH=ANONYMOUS' in self.server.capabilities)
rs = self.server.login(self.username, self.password)
self.assertEqual(rs[0], 'OK')
@ -221,6 +219,18 @@ class RemoteIMAPTest(unittest.TestCase):
self.assertEqual(rs[0], 'BYE')
@unittest.skipUnless(ssl, "SSL not available")
class RemoteIMAP_STARTTLSTest(RemoteIMAPTest):
def setUp(self):
super().setUp()
rs = self.server.starttls()
self.assertEqual(rs[0], 'OK')
def test_logincapa(self):
self.assertFalse('LOGINDISABLED' in self.server.capabilities)
@unittest.skipUnless(ssl, "SSL not available")
class RemoteIMAP_SSLTest(RemoteIMAPTest):
port = 993
@ -243,7 +253,7 @@ def test_main():
raise support.TestFailed("Can't read certificate files!")
tests.extend([
ThreadedNetworkedTests, ThreadedNetworkedTestsSSL,
RemoteIMAPTest, RemoteIMAP_SSLTest,
RemoteIMAPTest, RemoteIMAP_SSLTest, RemoteIMAP_STARTTLSTest,
])
support.run_unittest(*tests)

View File

@ -63,6 +63,9 @@ Core and Builtins
Library
-------
- Issue #4471: Add the IMAP.starttls() method to enable encryption on
standard IMAP4 connections. Original patch by Lorenzo M. Catucci.
- Issue #1466065: Add 'validate' option to base64.b64decode to raise
an error if there are non-base64 alphabet characters in the input.