BUG/MEDIUM: ssl: Fix sometimes reneg fails if requested by server.
SSL_do_handshake is not appropriate for reneg, it's only appropriate at the
beginning of a connection. OpenSSL correctly handles renegs using the data
functions, so we use SSL_peek() here to make its state machine progress if
SSL_renegotiate_pending() says a reneg is pending.
diff --git a/src/ssl_sock.c b/src/ssl_sock.c
index 8fec632..75f7b5d 100644
--- a/src/ssl_sock.c
+++ b/src/ssl_sock.c
@@ -855,6 +855,61 @@
if (!conn->xprt_ctx)
goto out_error;
+ /* If we use SSL_do_handshake to process a reneg initiated by
+ * the remote peer, it sometimes returns SSL_ERROR_SSL.
+ * Usually SSL_write and SSL_read are used and process implicitly
+ * the reneg handshake.
+ * Here we use SSL_peek as a workaround for reneg.
+ */
+ if ((conn->flags & CO_FL_CONNECTED) && SSL_renegotiate_pending(conn->xprt_ctx)) {
+ char c;
+
+ ret = SSL_peek(conn->xprt_ctx, &c, 1);
+ if (ret <= 0) {
+ /* handshake may have not been completed, let's find why */
+ ret = SSL_get_error(conn->xprt_ctx, ret);
+ if (ret == SSL_ERROR_WANT_WRITE) {
+ /* SSL handshake needs to write, L4 connection may not be ready */
+ __conn_sock_stop_recv(conn);
+ __conn_sock_poll_send(conn);
+ return 0;
+ }
+ else if (ret == SSL_ERROR_WANT_READ) {
+ /* handshake may have been completed but we have
+ * no more data to read.
+ */
+ if (!SSL_renegotiate_pending(conn->xprt_ctx)) {
+ ret = 1;
+ goto reneg_ok;
+ }
+ /* SSL handshake needs to read, L4 connection is ready */
+ if (conn->flags & CO_FL_WAIT_L4_CONN)
+ conn->flags &= ~CO_FL_WAIT_L4_CONN;
+ __conn_sock_stop_send(conn);
+ __conn_sock_poll_recv(conn);
+ return 0;
+ }
+ else if (ret == SSL_ERROR_SYSCALL) {
+ /* if errno is null, then connection was successfully established */
+ if (!errno && conn->flags & CO_FL_WAIT_L4_CONN)
+ conn->flags &= ~CO_FL_WAIT_L4_CONN;
+ goto out_error;
+ }
+ else {
+ /* Fail on all other handshake errors */
+ /* Note: OpenSSL may leave unread bytes in the socket's
+ * buffer, causing an RST to be emitted upon close() on
+ * TCP sockets. We first try to drain possibly pending
+ * data to avoid this as much as possible.
+ */
+ ret = recv(conn->t.sock.fd, trash.str, trash.size, MSG_NOSIGNAL|MSG_DONTWAIT);
+ goto out_error;
+ }
+ }
+ /* read some data: consider handshake completed */
+ goto reneg_ok;
+ }
+
ret = SSL_do_handshake(conn->xprt_ctx);
if (ret != 1) {
/* handshake did not complete, let's find why */
@@ -892,6 +947,8 @@
}
}
+reneg_ok:
+
/* Handshake succeeded */
if (objt_server(conn->target)) {
if (!SSL_session_reused(conn->xprt_ctx)) {