Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 89 additions & 1 deletion cheroot/ssl/builtin.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ def wrap(self, sock):
"""Wrap and return the given socket, plus WSGI environ entries."""
try:
s = self.context.wrap_socket(
sock, do_handshake_on_connect=True, server_side=True,
sock, do_handshake_on_connect=False, server_side=True,
)
except (
ssl.SSLEOFError,
Expand Down Expand Up @@ -293,6 +293,94 @@ def wrap(self, sock):

return s, self.get_environ(s)

def _handle_plain_http_error(self, wfile, buf):
try:
wfile.write(buf)
except OSError as ex:
if ex.args[0] not in errors.socket_errors_to_ignore:
raise

def _handle_handshake_failure(self, conn):
try:
conn.socket.shutdown(socket.SHUT_RDWR)
except Exception:
# pass
return

def do_handshake(self, conn):
"""Process SSL handshake on connection if needed.

Args:
conn (:py:class:`~cheroot.server.HTTPConnection`): HTTP connection
"""
ssl_handshake_must_be_done = (
conn and
getattr(conn, 'socket', None) and
getattr(conn.socket, 'do_handshake', None) and
not getattr(conn.socket, 'cheroot_handshake_done', False)
)
if ssl_handshake_must_be_done:
conn.socket.cheroot_handshake_done = True
do_shutdown = False
try:
conn.socket.do_handshake()
except ssl.SSLError as generic_tls_error:
do_shutdown = True

# Try to send HTTP 400 status for plain text queries
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:
msg = (
'The client %s:%s sent a plain HTTP request, but '
'this server only speaks HTTPS on this port.'
)
msg = msg % (conn.remote_addr, conn.remote_port)
buf = [
'%s 400 Bad Request\r\n' % conn.server.protocol,
'Content-Length: %s\r\n' % len(msg),
'Content-Type: text/plain\r\n\r\n',
msg,
]
conn.server.error_log(msg)

# - writing directly on conn.socket attempt to use
# non-initialized SSL layer and raises exception:
# EOF occurred in violation of protocol (_ssl.c:2427)
# - conn.socket.unwrap fails raising exception:
# [SSL: SHUTDOWN_WHILE_IN_INIT] shutdown while in
# init (_ssl.c:2706)
# - we create a non SSL socket to send plain text reply
conn.socket = socket.socket(fileno=conn.socket.detach())
wfile = StreamWriter(
conn.socket,
'wb',
DEFAULT_BUFFER_SIZE,
)
buf = ''.join(buf).encode('ISO-8859-1')
self._handle_plain_http_error(wfile, buf)
else:
msg = 'SSL handshake for %s:%s failed with SSL error:%s'
msg = msg % (
conn.remote_addr,
conn.remote_port,
generic_tls_error,
)
conn.server.error_log(msg)

except Exception as generic_error:
do_shutdown = True

msg = 'SSL handshake for %s:%s failed with error:%s'
msg = msg % (conn.remote_addr, conn.remote_port, generic_error)
conn.server.error_log(msg)

finally:
if do_shutdown:
self._handle_handshake_failure(conn)

def get_environ(self, sock):
"""Create WSGI environ entries to be merged into each request."""
cipher = sock.cipher()
Expand Down
1 change: 1 addition & 0 deletions cheroot/ssl/builtin.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ class BuiltinSSLAdapter(Adapter):
def context(self, context) -> None: ...
def bind(self, sock): ...
def wrap(self, sock): ...
def do_handshake(self, conn) -> None: ...
def get_environ(self, sock): ...
def makefile(self, sock, mode: str = ..., bufsize: int = ...): ...
19 changes: 18 additions & 1 deletion cheroot/workers/threadpool.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,6 @@ def __init__(
self._threads = []
self._queue = queue.Queue(maxsize=accepted_queue_size)
self._queue_put_timeout = accepted_queue_timeout
self.get = self._queue.get
self._pending_shutdowns = collections.deque()

def start(self):
Expand All @@ -291,6 +290,24 @@ def idle(self): # noqa: D401; irrelevant for properties
idles = len([t for t in self._threads if t.conn is None])
return max(idles - len(self._pending_shutdowns), 0)

def get(self):
"""Get request from queue, and process SSL handshake is needed.

Return:
conn (:py:class:`~cheroot.server.HTTPConnection`): HTTP connection
ready to be processed

"""
conn = self._queue.get()
ssl_adapter = self.server.ssl_adapter
check_for_ssl_handshake = (
ssl_adapter is not None and
getattr(ssl_adapter, 'do_handshake', None) is not None
)
if check_for_ssl_handshake:
ssl_adapter.do_handshake(conn)
return conn

def put(self, obj):
"""Put request into queue.

Expand Down
2 changes: 1 addition & 1 deletion cheroot/workers/threadpool.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@ class ThreadPool:
server: Any
min: Any
max: Any
get: Any
def __init__(self, server, min: int = ..., max: int = ..., accepted_queue_size: int = ..., accepted_queue_timeout: int = ...) -> None: ...
def start(self) -> None: ...
@property
def idle(self): ...
def get(self) -> Any: ...
def put(self, obj) -> None: ...
def grow(self, amount) -> None: ...
def shrink(self, amount) -> None: ...
Expand Down
Loading