MEDIUM: session/ssl: return the SSL error string during a SSL handshake error

SSL hanshake error were unable to dump the OpenSSL error string by
default, to do so it was mandatory to configure a error-log-format with
the ssl_fc_err fetch.

This patch implements the session_build_err_string() function which creates
the error log to send during session_kill_embryonic(), a special case is
made with CO_ER_SSL_HANDSHAKE which is able to dump the error string
with ERR_error_string().

Before:
    <134>May 12 17:14:04 haproxy[183151]: 127.0.0.1:49346 [12/May/2023:17:14:04.571] frt2/1: SSL handshake failure

After:
    <134>May 12 17:14:04 haproxy[183151]: 127.0.0.1:49346 [12/May/2023:17:14:04.571] frt2/1: SSL handshake failure (error:0A000418:SSL routines::tlsv1 alert unknown ca)
diff --git a/src/session.c b/src/session.c
index f12f5a3..ef8a194 100644
--- a/src/session.c
+++ b/src/session.c
@@ -10,6 +10,8 @@
  *
  */
 
+#include <haproxy/ssl_sock-t.h>
+
 #include <haproxy/api.h>
 #include <haproxy/connection.h>
 #include <haproxy/global.h>
@@ -346,6 +348,46 @@
 		chunk_appendf(&trash, "] %s/%d", sess->fe->id, sess->listener->luid);
 }
 
+
+/* fill the trash buffer with the string to use for send_log during
+ * session_kill_embryonic(). Add log prefix and error string.
+ *
+ * The function is able to dump an SSL error string when CO_ER_SSL_HANDSHAKE
+ * is met.
+ */
+static void session_build_err_string(struct session *sess)
+{
+	struct connection *conn = __objt_conn(sess->origin);
+	const char *err_msg;
+	struct ssl_sock_ctx __maybe_unused *ssl_ctx;
+
+	err_msg	= conn_err_code_str(conn);
+	session_prepare_log_prefix(sess); /* use trash buffer */
+
+#ifdef USE_OPENSSL
+	ssl_ctx = conn_get_ssl_sock_ctx(conn);
+
+
+	if (conn->err_code == CO_ER_SSL_HANDSHAKE && ssl_ctx) {
+		const char *err_ssl_str = ERR_error_string(ssl_ctx->error_code, NULL);
+
+		chunk_appendf(&trash, ": SSL handshake failure (%s)\n", err_ssl_str);
+	}
+
+	else
+#endif /* ! USE_OPENSSL */
+
+	if (err_msg)
+		chunk_appendf(&trash, ": %s\n", err_msg);
+	else
+		chunk_appendf(&trash, ": unknown connection error (code=%d flags=%08x)\n",
+		              conn->err_code, conn->flags);
+
+	return;
+}
+
+
+
 /* This function kills an existing embryonic session. It stops the connection's
  * transport layer, releases assigned resources, resumes the listener if it was
  * disabled and finally kills the file descriptor. This function requires that
@@ -357,7 +399,6 @@
 	struct connection *conn = __objt_conn(sess->origin);
 	struct task *task = sess->task;
 	unsigned int log = sess->fe->to_log;
-	const char *err_msg;
 
 	if (sess->fe->options2 & PR_O2_LOGERRORS)
 		level = LOG_ERR;
@@ -386,14 +427,8 @@
 			sess_log(sess);
 		}
 		else {
-			session_prepare_log_prefix(sess);
-			err_msg = conn_err_code_str(conn);
-			if (err_msg)
-				send_log(sess->fe, level, "%s: %s\n", trash.area,
-					 err_msg);
-			else
-				send_log(sess->fe, level, "%s: unknown connection error (code=%d flags=%08x)\n",
-					 trash.area, conn->err_code, conn->flags);
+			session_build_err_string(sess);
+			send_log(sess->fe, level, "%s", trash.area);
 		}
 	}