diff -r 63c1fbc4de4b Doc/library/imaplib.rst --- a/Doc/library/imaplib.rst Mon Nov 25 23:19:58 2013 +0100 +++ b/Doc/library/imaplib.rst Tue Nov 26 00:40:06 2013 +0100 @@ -64,7 +64,7 @@ There's also a subclass for secure connections: -.. class:: IMAP4_SSL(host='', port=IMAP4_SSL_PORT, keyfile=None, certfile=None, ssl_context=None) +.. class:: IMAP4_SSL(host='', port=IMAP4_SSL_PORT, keyfile=None, certfile=None, ssl_context=None, check_hostname=None) This is a subclass derived from :class:`IMAP4` that connects over an SSL encrypted socket (to use this class you need a socket module that was compiled @@ -76,10 +76,17 @@ options, certificates and private keys into a single (potentially long-lived) structure. Note that the *keyfile*/*certfile* parameters are mutually exclusive with *ssl_context*, a :class:`ValueError` is raised if *keyfile*/*certfile* is provided along with *ssl_context*. + If *check_hostname* is set, then host is matched against the host name(s) + allowed by the server cert. + .. versionchanged:: 3.3 *ssl_context* parameter added. + .. versionchanged:: 3.4 + *check_hostname* and *Server Name Indicator* support (see + :data:`~ssl.HAS_SNI`) + The second subclass allows for connections created by a child process: @@ -429,14 +436,19 @@ This is an ``IMAP4rev1`` extension command. -.. method:: IMAP4.starttls(ssl_context=None) +.. method:: IMAP4.starttls(ssl_context=None, check_hostname=False) 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. + encryption on the IMAP connection. If *check_hostname* is set, then host + is matched against the host name(s) allowed by the server cert. .. versionadded:: 3.2 + .. versionchanged:: 3.4 + *check_hostname* and *Server Name Indicator* support (see + :data:`~ssl.HAS_SNI`) + .. method:: IMAP4.status(mailbox, names) diff -r 63c1fbc4de4b Lib/imaplib.py --- a/Lib/imaplib.py Mon Nov 25 23:19:58 2013 +0100 +++ b/Lib/imaplib.py Tue Nov 26 00:40:06 2013 +0100 @@ -732,7 +732,7 @@ return self._untagged_response(typ, dat, name) - def starttls(self, ssl_context=None): + def starttls(self, ssl_context=None, check_hostname=False): name = 'STARTTLS' if not HAVE_SSL: raise self.error('SSL support missing') @@ -743,9 +743,25 @@ # Generate a default SSL context if none was passed. if ssl_context is None: ssl_context = ssl._create_stdlib_context() + will_verify = ssl_context.verify_mode != ssl.CERT_NONE + if check_hostname is None: + check_hostname = will_verify + elif check_hostname and not will_verify: + raise ValueError("check_hostname needs a SSL context with " + "either CERT_OPTIONAL or CERT_REQUIRED") typ, dat = self._simple_command(name) if typ == 'OK': - self.sock = ssl_context.wrap_socket(self.sock) + server_hostname = self.host if ssl.HAS_SNI else None + self.sock = ssl_context.wrap_socket(self.sock, + server_hostname=server_hostname) + + if check_hostname: + try: + ssl.match_hostname(self.sock.getpeercert(), self.host) + except Exception: + self.sock.shutdown(socket.SHUT_RDWR) + self.sock.close() + raise self.file = self.sock.makefile('rb') self._tls_established = True self._get_capabilities() @@ -1198,7 +1214,8 @@ """ - def __init__(self, host='', port=IMAP4_SSL_PORT, keyfile=None, certfile=None, ssl_context=None): + def __init__(self, host='', port=IMAP4_SSL_PORT, keyfile=None, + certfile=None, ssl_context=None, check_hostname=False): if ssl_context is not None and keyfile is not None: raise ValueError("ssl_context and keyfile arguments are mutually " "exclusive") @@ -1211,12 +1228,29 @@ if ssl_context is None: ssl_context = ssl._create_stdlib_context(certfile=certfile, keyfile=keyfile) + will_verify = ssl_context.verify_mode != ssl.CERT_NONE + if check_hostname is None: + check_hostname = will_verify + elif check_hostname and not will_verify: + raise ValueError("check_hostname needs a SSL context with " + "either CERT_OPTIONAL or CERT_REQUIRED") self.ssl_context = ssl_context + self._check_hostname = check_hostname IMAP4.__init__(self, host, port) def _create_socket(self): sock = IMAP4._create_socket(self) - return self.ssl_context.wrap_socket(sock) + server_hostname = self.host if ssl.HAS_SNI else None + sock = self.ssl_context.wrap_socket(sock, + server_hostname=server_hostname) + if self._check_hostname: + try: + ssl.match_hostname(sock.getpeercert(), self.host) + except Exception: + sock.shutdown(socket.SHUT_RDWR) + sock.close() + raise + return sock def open(self, host='', port=IMAP4_SSL_PORT): """Setup connection to remote server on "host:port".