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:
parent
e0bf419ae7
commit
f3b001f966
|
@ -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*.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
Loading…
Reference in New Issue