diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst --- a/Doc/library/ssl.rst +++ b/Doc/library/ssl.rst @@ -766,7 +766,6 @@ - :meth:`~socket.socket.getpeername()`, :meth:`~socket.socket.getsockname()` - :meth:`~socket.socket.getsockopt()`, :meth:`~socket.socket.setsockopt()` - :meth:`~socket.socket.gettimeout()`, :meth:`~socket.socket.settimeout()`, - :meth:`~socket.socket.setblocking()` - :meth:`~socket.socket.listen()` - :meth:`~socket.socket.makefile()` - :meth:`~socket.socket.recv()`, :meth:`~socket.socket.recv_into()` @@ -782,6 +781,34 @@ SSL sockets also have the following additional methods and attributes: +.. method:: SSLSocket.setblocking(flag, raise_on_blocking_send=False) + + Set blocking or non-blocking mode of the socket: if *flag* is + ``False``, the socket is set to non-blocking, else to blocking + mode. + + If the socket is in non-blocking mode and *raise_on_blocking_send* + is ``False``, the :meth:`SSLSocket.send` method will return zero + instead of raising :exc:`SSLWantWriteError` or + :exc:`SSLWantReadError`. This behavior is the default for + backwards-compatibility reasons. + + New code is recommended to set *raise_on_blocking_send* to + ``True``. In this case, the :meth:`SSLSocket.send` method behaves + like the :meth:`SSLSocket.recv()` and + :meth:`SSLSocket.do_handshake` methods and raises + :exc:`SSLWantWriteError` or :exc:`SSLWantReadError` if a call would + block. + + If *flag* is ``False`` (so the socket is in blocking mode), the + *raise_on_blocking_send* parameter has no effect. + + .. versionchanged:: 3.5 + The *raise_on_blocking_send* argument was added. + + .. deprecated:: 3.5 + Setting *raise_on_blocking_send* to ``False`` is deprecated. + .. method:: SSLSocket.do_handshake() Perform the SSL setup handshake. @@ -1591,6 +1618,20 @@ When working with non-blocking sockets, there are several things you need to be aware of: +- The :meth:`SSLSocket.recv` method will raise :exc:`SSLWantReadError` or + :exc:`SSLWantWriteError` rather than :exc:`BlockingIOError` if the SSL + socket is in non-blocking mode and no data can be read immediately. + +- For backward compatibility reasons, the :meth:`SSLSocket.send` + method by default returns ``0`` rather than raising an exception if + the socket is non-blocking and no data can be send right away. + + It is recommended for new code to pass + ``raise_on_blocking_send=True`` to the :meth:`SSLSocket.setblocking` + method. In this case, :meth:`SSLSocket.send` will behave like + :meth:`SSLSocket.recv` and :meth:`SSLSocket.do_handshake` and raise + :exc:`SSLWantReadError` or :exc:`SSLWantWriteError`. + - Calling :func:`~select.select` tells you that the OS-level socket can be read from (or written to), but it does not imply that there is sufficient data at the upper SSL layer. For example, only part of an SSL frame might @@ -1615,7 +1656,8 @@ except ssl.SSLWantWriteError: select.select([], [sock], []) - + + .. _ssl-security: Security considerations diff --git a/Lib/ssl.py b/Lib/ssl.py --- a/Lib/ssl.py +++ b/Lib/ssl.py @@ -91,6 +91,7 @@ import re import sys import os +import warnings from collections import namedtuple from enum import Enum as _Enum @@ -539,6 +540,7 @@ self.server_hostname = server_hostname self.do_handshake_on_connect = do_handshake_on_connect self.suppress_ragged_eofs = suppress_ragged_eofs + self._legacy_send = False if sock is not None: socket.__init__(self, family=sock.family, @@ -668,6 +670,21 @@ else: return self._sslobj.compression() + def setblocking(self, flag, raise_on_blocking_send=False): + """Set the socket to blocking (flag is true) or non-blocking (false). + + In non-blocking mode, the send() and recv() methods will raise + SSLWantRead() or SSLWantWrite() exceptions if no data can be + read/written immediately. + + However, if *raise_on_blocking_send* is false (the default for backwards + compatibility reason), the send() method will return 0 instead of + raising an exception. + """ + + self._legacy_send = not raise_on_blocking_send + super().setblocking(flag) + def send(self, data, flags=0): self._checkClosed() if self._sslobj: @@ -675,17 +692,23 @@ raise ValueError( "non-zero flags not allowed in calls to send() on %s" % self.__class__) - try: - v = self._sslobj.write(data) - except SSLError as x: - if x.args[0] == SSL_ERROR_WANT_READ: - return 0 - elif x.args[0] == SSL_ERROR_WANT_WRITE: + if self._legacy_send: + try: + v = self._sslobj.write(data) + except SSLError as exc: + if exc.args[0] not in (SSL_ERROR_WANT_READ, + SSL_ERROR_WANT_WRITE): + raise + msg = ("Relying on send() to return 0 in non-blocking mode " + "is deprecated. Pass raise_on_blocking_send=True to " + "setblocking() and catch SSLWantReadError and " + "SSLWantWriteError exceptions instead.") + warnings.warn(msg, DeprecationWarning, stacklevel=2) return 0 else: - raise + return v else: - return v + return self._sslobj.write(data) else: return socket.send(self, data, flags)