diff -r 63c1fbc4de4b Doc/library/nntplib.rst --- a/Doc/library/nntplib.rst Mon Nov 25 23:19:58 2013 +0100 +++ b/Doc/library/nntplib.rst Tue Nov 26 00:40:29 2013 +0100 @@ -87,14 +87,16 @@ .. versionchanged:: 3.3 Support for the :keyword:`with` statement was added. -.. class:: NNTP_SSL(host, port=563, user=None, password=None, ssl_context=None, readermode=None, usenetrc=False, [timeout]) +.. class:: NNTP_SSL(host, port=563, user=None, password=None, ssl_context=None, readermode=None, usenetrc=False, check_hostname=False, [timeout]) Return a new :class:`NNTP_SSL` object, representing an encrypted connection to the NNTP server running on host *host*, listening at port *port*. :class:`NNTP_SSL` objects have the same methods as :class:`NNTP` objects. If *port* is omitted, port 563 (NNTPS) is used. *ssl_context* is also optional, and is a :class:`~ssl.SSLContext` object. - All other parameters behave the same as for :class:`NNTP`. + If *check_hostname* is set, then host is matched against the host name(s) + allowed by the server cert. All other parameters behave the same as for + :class:`NNTP`. Note that SSL-on-563 is discouraged per :rfc:`4642`, in favor of STARTTLS as described below. However, some servers only support the @@ -102,6 +104,10 @@ .. versionadded:: 3.2 + .. versionchanged:: 3.4 + *check_hostname* and *Server Name Indicator* support (see + :data:`~ssl.HAS_SNI`) + .. exception:: NNTPError @@ -228,11 +234,12 @@ .. versionadded:: 3.2 -.. method:: NNTP.starttls(ssl_context=None) +.. method:: NNTP.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 NNTP connection. + and should be a :class:`ssl.SSLContext` object. If *check_hostname* is + set, then host is matched against the host name(s) allowed by the server + cert. This will enable encryption on the NNTP connection. Note that this may not be done after authentication information has been transmitted, and authentication occurs by default if possible during a @@ -241,6 +248,10 @@ .. versionadded:: 3.2 + .. versionchanged:: 3.4 + *check_hostname* and *Server Name Indicator* support (see + :data:`~ssl.HAS_SNI`) + .. method:: NNTP.newgroups(date, *, file=None) diff -r 63c1fbc4de4b Lib/nntplib.py --- a/Lib/nntplib.py Mon Nov 25 23:19:58 2013 +0100 +++ b/Lib/nntplib.py Tue Nov 26 00:40:29 2013 +0100 @@ -279,17 +279,35 @@ if _have_ssl: - def _encrypt_on(sock, context): + def _encrypt_on(sock, context, check_hostname, hostname): """Wrap a socket in SSL/TLS. Arguments: - sock: Socket to wrap - context: SSL context to use for the encrypted connection + - check_hostname: boolean + - hostname: Hostname to match Returns: - sock: New, encrypted socket. """ # Generate a default SSL context if none was passed. if context is None: context = ssl._create_stdlib_context() - return context.wrap_socket(sock) + will_verify = 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") + server_hostname = hostname if ssl.HAS_SNI else None + sock = context.wrap_socket(sock, + server_hostname=server_hostname) + if check_hostname: + try: + ssl.match_hostname(sock.getpeercert(), hostname) + except Exception: + sock.shutdown(socket.SHUT_RDWR) + sock.close() + raise + return sock # The classes themselves @@ -992,7 +1010,7 @@ raise if _have_ssl: - def starttls(self, context=None): + def starttls(self, context=None, check_hostname=False): """Process a STARTTLS command. Arguments: - context: SSL context to use for the encrypted connection """ @@ -1005,7 +1023,8 @@ resp = self._shortcmd('STARTTLS') if resp.startswith('382'): self.file.close() - self.sock = _encrypt_on(self.sock, context) + self.sock = _encrypt_on(self.sock, context, check_hostname, + self.host) self.file = self.sock.makefile("rwb") self.tls_on = True # Capabilities may change after TLS starts up, so ask for them @@ -1060,12 +1079,13 @@ def __init__(self, host, port=NNTP_SSL_PORT, user=None, password=None, ssl_context=None, readermode=None, usenetrc=False, - timeout=_GLOBAL_DEFAULT_TIMEOUT): + timeout=_GLOBAL_DEFAULT_TIMEOUT, + check_hostname=False): """This works identically to NNTP.__init__, except for the change in default port and the `ssl_context` argument for SSL connections. """ - self.sock = socket.create_connection((host, port), timeout) - self.sock = _encrypt_on(self.sock, ssl_context) + sock = socket.create_connection((host, port), timeout) + self.sock = _encrypt_on(sock, ssl_context, check_hostname, host) file = self.sock.makefile("rwb") _NNTPBase.__init__(self, file, host, readermode=readermode, timeout=timeout)