]> granicus.if.org Git - python/commitdiff
Issue #19785: smtplib now supports SSLContext.check_hostname and server name
authorChristian Heimes <christian@cheimes.de>
Mon, 2 Dec 2013 19:44:17 +0000 (20:44 +0100)
committerChristian Heimes <christian@cheimes.de>
Mon, 2 Dec 2013 19:44:17 +0000 (20:44 +0100)
indication for TLS/SSL connections.

Doc/library/smtplib.rst
Lib/smtplib.py
Lib/test/test_smtpnet.py
Misc/NEWS

index a7d15384d09c33a5550e22ca8ad7a58124553205..eba8ae9278d51e4f47621edc65da15de720b47dd 100644 (file)
@@ -90,6 +90,10 @@ Protocol) and :rfc:`1869` (SMTP Service Extensions).
    .. versionchanged:: 3.3
       source_address argument was added.
 
+   .. versionchanged:: 3.4
+      The class now supports hostname check with
+      :attr:`SSLContext.check_hostname` and *Server Name Indicator* (see
+      :data:`~ssl.HAS_SNI`).
 
 .. class:: LMTP(host='', port=LMTP_PORT, local_hostname=None, source_address=None)
 
@@ -316,6 +320,11 @@ An :class:`SMTP` instance has the following methods:
    .. versionchanged:: 3.3
       *context* was added.
 
+   .. versionchanged:: 3.4
+      The method now supports hostname check with
+      :attr:`SSLContext.check_hostname` and *Server Name Indicator* (see
+      :data:`~ssl.HAS_SNI`).
+
 
 .. method:: SMTP.sendmail(from_addr, to_addrs, msg, mail_options=[], rcpt_options=[])
 
index 6fc65f6b6a3a4e51a3ac8ba07ad025c1126c0186..796b86612247e7f43f32ba89af14a66e7b760874 100644 (file)
@@ -232,6 +232,7 @@ class SMTP:
         will be used.
 
         """
+        self._host = host
         self.timeout = timeout
         self.esmtp_features = {}
         self.source_address = source_address
@@ -667,7 +668,9 @@ class SMTP:
             if context is None:
                 context = ssl._create_stdlib_context(certfile=certfile,
                                                      keyfile=keyfile)
-            self.sock = context.wrap_socket(self.sock)
+            server_hostname = self._host if ssl.HAS_SNI else None
+            self.sock = context.wrap_socket(self.sock,
+                                            server_hostname=server_hostname)
             self.file = None
             # RFC 3207:
             # The client MUST discard any knowledge obtained from
@@ -892,7 +895,9 @@ if _have_ssl:
                 print('connect:', (host, port), file=stderr)
             new_socket = socket.create_connection((host, port), timeout,
                     self.source_address)
-            new_socket = self.context.wrap_socket(new_socket)
+            server_hostname = self._host if ssl.HAS_SNI else None
+            new_socket = self.context.wrap_socket(new_socket,
+                                                  server_hostname=server_hostname)
             return new_socket
 
     __all__.append("SMTP_SSL")
index 86224ef2e45beb669e3503f2300595bb515c1656..56952adca3f4de2f8946c795bd9eab5a4c837a9b 100644 (file)
@@ -3,23 +3,35 @@
 import unittest
 from test import support
 import smtplib
+import socket
 
 ssl = support.import_module("ssl")
 
 support.requires("network")
 
+def check_ssl_verifiy(host, port):
+    context = ssl.create_default_context()
+    with socket.create_connection((host, port)) as sock:
+        try:
+            sock = context.wrap_socket(sock, server_hostname=host)
+        except Exception:
+            return False
+        else:
+            sock.close()
+            return True
+
 
 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')
+        context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
         with support.transient_internet(self.testServer):
             server = smtplib.SMTP(self.testServer, self.remotePort)
             try:
-                server.starttls(context=self.context)
+                server.starttls(context=context)
             except smtplib.SMTPException as e:
                 if e.args[0] == 'STARTTLS extension not supported by server.':
                     unittest.skip(e.args[0])
@@ -32,7 +44,7 @@ class SmtpTest(unittest.TestCase):
 class SmtpSSLTest(unittest.TestCase):
     testServer = 'smtp.gmail.com'
     remotePort = 465
-    context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
+    can_verify = check_ssl_verifiy(testServer, remotePort)
 
     def test_connect(self):
         support.get_attribute(smtplib, 'SMTP_SSL')
@@ -49,9 +61,19 @@ class SmtpSSLTest(unittest.TestCase):
             server.quit()
 
     def test_connect_using_sslcontext(self):
+        context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
+        support.get_attribute(smtplib, 'SMTP_SSL')
+        with support.transient_internet(self.testServer):
+            server = smtplib.SMTP_SSL(self.testServer, self.remotePort, context=context)
+            server.ehlo()
+            server.quit()
+
+    @unittest.skipUnless(can_verify, "SSL certificate can't be verified")
+    def test_connect_using_sslcontext_verified(self):
         support.get_attribute(smtplib, 'SMTP_SSL')
+        context = ssl.create_default_context()
         with support.transient_internet(self.testServer):
-            server = smtplib.SMTP_SSL(self.testServer, self.remotePort, context=self.context)
+            server = smtplib.SMTP_SSL(self.testServer, self.remotePort, context=context)
             server.ehlo()
             server.quit()
 
index 20b80c113b725e3617404cd4f775cc7927125c16..3bd4db4223327081b7579e3c198816878f3a969e 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -18,6 +18,9 @@ Core and Builtins
 Library
 -------
 
+- Issue #19785: smtplib now supports SSLContext.check_hostname and server name
+  indication for TLS/SSL connections.
+
 - Issue #19784: poplib now supports SSLContext.check_hostname and server name
   indication for TLS/SSL connections.