Issue #4471: Add the IMAP.starttls() method to enable encryption on
standard IMAP4 connections.  Original patch by Lorenzo M. Catucci.
diff --git a/Doc/library/imaplib.rst b/Doc/library/imaplib.rst
index b8ac03d..dc94beb 100644
--- a/Doc/library/imaplib.rst
+++ b/Doc/library/imaplib.rst
@@ -56,6 +56,7 @@
    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 @@
    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 @@
    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*.
diff --git a/Lib/imaplib.py b/Lib/imaplib.py
index 77806db..421cb97 100644
--- a/Lib/imaplib.py
+++ b/Lib/imaplib.py
@@ -24,6 +24,12 @@
 
 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 @@
         'SETANNOTATION':('AUTH', 'SELECTED'),
         'SETQUOTA':     ('AUTH', 'SELECTED'),
         'SORT':         ('SELECTED',),
+        'STARTTLS':     ('NONAUTH',),
         'STATUS':       ('AUTH', 'SELECTED'),
         'STORE':        ('SELECTED',),
         'SUBSCRIBE':    ('AUTH', 'SELECTED'),
@@ -156,6 +163,7 @@
         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 @@
         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 @@
                 n -= 1
 
 
+if HAVE_SSL:
 
-try:
-    import ssl
-except ImportError:
-    pass
-else:
     class IMAP4_SSL(IMAP4):
 
         """IMAP4 client class over SSL connection
diff --git a/Lib/test/test_imaplib.py b/Lib/test/test_imaplib.py
index 351659d..9bae65a 100644
--- a/Lib/test/test_imaplib.py
+++ b/Lib/test/test_imaplib.py
@@ -209,8 +209,6 @@
 
     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')
@@ -222,6 +220,18 @@
 
 
 @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
     imap_class = IMAP4_SSL
@@ -243,7 +253,7 @@
                 raise support.TestFailed("Can't read certificate files!")
         tests.extend([
             ThreadedNetworkedTests, ThreadedNetworkedTestsSSL,
-            RemoteIMAPTest, RemoteIMAP_SSLTest,
+            RemoteIMAPTest, RemoteIMAP_SSLTest, RemoteIMAP_STARTTLSTest,
         ])
 
     support.run_unittest(*tests)
diff --git a/Misc/NEWS b/Misc/NEWS
index 2ff7247..e522ed1 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -63,6 +63,9 @@
 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.