Skip to content

Commit

Permalink
merge upstream fixes from cherrypy#518 & cherrypy#649
Browse files Browse the repository at this point in the history
  • Loading branch information
scyclops committed May 3, 2024
1 parent 7e1d2fc commit 5f2162f
Show file tree
Hide file tree
Showing 3 changed files with 206 additions and 143 deletions.
39 changes: 21 additions & 18 deletions cheroot/connections.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,17 @@
"""Utilities to manage open connections."""

from __future__ import absolute_import, division, print_function
__metaclass__ = type

import io
import os
import socket
import threading
import time
import selectors
from contextlib import suppress

from . import errors
from ._compat import selectors
from ._compat import suppress
from ._compat import IS_WINDOWS
from .makefile import MakeFile

import six

try:
import fcntl
except ImportError:
Expand Down Expand Up @@ -280,8 +275,7 @@ def _remove_invalid_sockets(self):
# One of the reason on why a socket could cause an error
# is that the socket is already closed, ignore the
# socket error if we try to close it at this point.
# This is equivalent to OSError in Py3
with suppress(socket.error):
with suppress(OSError):
conn.close()

def _from_server_socket(self, server_socket): # noqa: C901 # FIXME
Expand All @@ -299,7 +293,20 @@ def _from_server_socket(self, server_socket): # noqa: C901 # FIXME
if self.server.ssl_adapter is not None:
try:
s, ssl_env = self.server.ssl_adapter.wrap(s)
except errors.NoSSLError:
except errors.FatalSSLAlert as tls_connection_drop_error:
self.server.error_log(
f'Client {addr !s} lost — peer dropped the TLS '
'connection suddenly, during handshake: '
f'{tls_connection_drop_error !s}',
)
return
except errors.NoSSLError as http_over_https_err:
self.server.error_log(
f'Client {addr !s} attempted to speak plain HTTP into '
'a TCP connection configured for TLS-only traffic — '
'trying to send back a plain HTTP error response: '
f'{http_over_https_err !s}',
)
msg = (
'The client sent a plain HTTP request, but '
'this server only speaks HTTPS on this port.'
Expand All @@ -311,11 +318,10 @@ def _from_server_socket(self, server_socket): # noqa: C901 # FIXME
msg,
]

sock_to_make = s if not six.PY2 else s._sock
wfile = mf(sock_to_make, 'wb', io.DEFAULT_BUFFER_SIZE)
wfile = mf(s, 'wb', io.DEFAULT_BUFFER_SIZE)
try:
wfile.write(''.join(buf).encode('ISO-8859-1'))
except socket.error as ex:
except OSError as ex:
if ex.args[0] not in errors.socket_errors_to_ignore:
raise
return
Expand All @@ -328,10 +334,7 @@ def _from_server_socket(self, server_socket): # noqa: C901 # FIXME

conn = self.server.ConnectionClass(self.server, s, mf)

if not isinstance(
self.server.bind_addr,
(six.text_type, six.binary_type),
):
if not isinstance(self.server.bind_addr, (str, bytes)):
# optional values
# Until we do DNS lookups, omit REMOTE_HOST
if addr is None: # sometimes this can happen
Expand All @@ -354,7 +357,7 @@ def _from_server_socket(self, server_socket): # noqa: C901 # FIXME
# accept() by default
self.server.sstat('fss-timeout')
return
except socket.error as ex:
except OSError as ex:
if self.server.stats['Enabled']:
self.server.stats['Socket Errors'] += 1
if ex.args[0] in errors.socket_error_eintr:
Expand Down
89 changes: 26 additions & 63 deletions cheroot/ssl/builtin.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,10 @@
``BuiltinSSLAdapter``.
"""

from __future__ import absolute_import, division, print_function
__metaclass__ = type

import socket
import sys
import threading
from contextlib import suppress

try:
import ssl
Expand All @@ -27,19 +25,11 @@
except ImportError:
DEFAULT_BUFFER_SIZE = -1

import six

from . import Adapter
from .. import errors
from .._compat import IS_ABOVE_OPENSSL10, suppress
from ..makefile import StreamReader, StreamWriter
from ..server import HTTPServer

if six.PY2:
generic_socket_error = socket.error
else:
generic_socket_error = OSError


def _assert_ssl_exc_contains(exc, *msgs):
"""Check whether SSL exception contains either of messages provided."""
Expand Down Expand Up @@ -272,62 +262,35 @@ def bind(self, sock):

def wrap(self, sock):
"""Wrap and return the given socket, plus WSGI environ entries."""
EMPTY_RESULT = None, {}
try:
s = self.context.wrap_socket(
sock, do_handshake_on_connect=True, server_side=True,
)
except ssl.SSLError as ex:
if ex.errno == ssl.SSL_ERROR_EOF:
# This is almost certainly due to the cherrypy engine
# 'pinging' the socket to assert it's connectable;
# the 'ping' isn't SSL.
return EMPTY_RESULT
elif ex.errno == ssl.SSL_ERROR_SSL:
if _assert_ssl_exc_contains(ex, 'http request'):
# The client is speaking HTTP to an HTTPS server.
raise errors.NoSSLError

# Check if it's one of the known errors
# Errors that are caught by PyOpenSSL, but thrown by
# built-in ssl
_block_errors = (
'unknown protocol', 'unknown ca', 'unknown_ca',
'unknown error',
'https proxy request', 'inappropriate fallback',
'wrong version number',
'no shared cipher', 'certificate unknown',
'ccs received early',
'certificate verify failed', # client cert w/o trusted CA
'version too low', # caused by SSL3 connections
'unsupported protocol', # caused by TLS1 connections
)
if _assert_ssl_exc_contains(ex, *_block_errors):
# Accepted error, let's pass
return EMPTY_RESULT
elif _assert_ssl_exc_contains(ex, 'handshake operation timed out'):
# This error is thrown by builtin SSL after a timeout
# when client is speaking HTTP to an HTTPS server.
# The connection can safely be dropped.
return EMPTY_RESULT
raise
except generic_socket_error as exc:
"""It is unclear why exactly this happens.
It's reproducible only with openssl>1.0 and stdlib
:py:mod:`ssl` wrapper.
In CherryPy it's triggered by Checker plugin, which connects
to the app listening to the socket port in TLS mode via plain
HTTP during startup (from the same process).
Ref: https://github.com/cherrypy/cherrypy/issues/1618
"""
is_error0 = exc.args == (0, 'Error')

if is_error0 and IS_ABOVE_OPENSSL10:
return EMPTY_RESULT
raise
except (
ssl.SSLEOFError,
ssl.SSLZeroReturnError,
) as tls_connection_drop_error:
raise errors.FatalSSLAlert(
*tls_connection_drop_error.args,
) from tls_connection_drop_error
except ssl.SSLError as generic_tls_error:
peer_speaks_plain_http_over_https = (
generic_tls_error.errno == ssl.SSL_ERROR_SSL and
_assert_ssl_exc_contains(generic_tls_error, 'http request')
)
if peer_speaks_plain_http_over_https:
reraised_connection_drop_exc_cls = errors.NoSSLError
else:
reraised_connection_drop_exc_cls = errors.FatalSSLAlert

raise reraised_connection_drop_exc_cls(
*generic_tls_error.args,
) from generic_tls_error
except OSError as tcp_connection_drop_error:
raise errors.FatalSSLAlert(
*tcp_connection_drop_error.args,
) from tcp_connection_drop_error

return s, self.get_environ(s)

def get_environ(self, sock):
Expand Down
Loading

0 comments on commit 5f2162f

Please sign in to comment.