MEDIUM: connection: Implement and extented PROXY Protocol V2

This commit modifies the PROXY protocol V2 specification to support headers
longer than 255 bytes allowing for optional extensions.  It implements the
PROXY protocol V2 which is a binary representation of V1. This will make
parsing more efficient for clients who will know in advance exactly how
many bytes to read.  Also, it defines and implements some optional PROXY
protocol V2 extensions to send information about downstream SSL/TLS
connections.  Support for PROXY protocol V1 remains unchanged.
diff --git a/src/backend.c b/src/backend.c
index bc63903..24d8142 100644
--- a/src/backend.c
+++ b/src/backend.c
@@ -1059,7 +1059,7 @@
 
 		/* process the case where the server requires the PROXY protocol to be sent */
 		srv_conn->send_proxy_ofs = 0;
-		if (objt_server(s->target) && (objt_server(s->target)->state & SRV_SEND_PROXY)) {
+		if (objt_server(s->target) && objt_server(s->target)->pp_opts) {
 			srv_conn->send_proxy_ofs = 1; /* must compute size */
 			cli_conn = objt_conn(s->req->prod->end);
 			if (cli_conn)
diff --git a/src/connection.c b/src/connection.c
index 1483f18..91ad0b6 100644
--- a/src/connection.c
+++ b/src/connection.c
@@ -437,6 +437,23 @@
 	return 0;
 }
 
+int make_proxy_line(char *buf, int buf_len, struct server *srv, struct connection *remote)
+{
+	int ret = 0;
+
+	if (srv && (srv->pp_opts & SRV_PP_V2)) {
+		ret = make_proxy_line_v2(buf, buf_len, srv, remote);
+	}
+	else {
+		if (remote)
+			ret = make_proxy_line_v1(buf, buf_len, &remote->addr.from, &remote->addr.to);
+		else
+			ret = make_proxy_line_v1(buf, buf_len, NULL, NULL);
+	}
+
+	return ret;
+}
+
 /* Makes a PROXY protocol line from the two addresses. The output is sent to
  * buffer <buf> for a maximum size of <buf_len> (including the trailing zero).
  * It returns the number of bytes composing this line (including the trailing
@@ -444,7 +461,7 @@
  * TCP6 and "UNKNOWN" formats. If any of <src> or <dst> is null, UNKNOWN is
  * emitted as well.
  */
-int make_proxy_line(char *buf, int buf_len, struct sockaddr_storage *src, struct sockaddr_storage *dst)
+int make_proxy_line_v1(char *buf, int buf_len, struct sockaddr_storage *src, struct sockaddr_storage *dst)
 {
 	int ret = 0;
 
@@ -516,3 +533,113 @@
 	}
 	return ret;
 }
+
+#ifdef USE_OPENSSL
+static int make_tlv(char *dest, int dest_len, char type, uint16_t length, char *value)
+{
+	struct tlv *tlv;
+
+	if (!dest || (length + sizeof(*tlv) > dest_len))
+		return 0;
+
+	tlv = (struct tlv *)dest;
+
+	tlv->type = type;
+	tlv->length_hi = length >> 8;
+	tlv->length_lo = length & 0x00ff;
+	memcpy(tlv->value, value, length);
+	return length + sizeof(*tlv);
+}
+#endif
+
+int make_proxy_line_v2(char *buf, int buf_len, struct server *srv, struct connection *remote)
+{
+	const char pp2_signature[12] = {0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, 0x55, 0x49, 0x54, 0x0A};
+	int ret = 0;
+	struct proxy_hdr_v2 *hdr_p = (struct proxy_hdr_v2 *)buf;
+	union proxy_addr *addr_p = (union proxy_addr *)(buf + PP2_HEADER_LEN);
+	struct sockaddr_storage null_addr = {0};
+	struct sockaddr_storage *src = &null_addr;
+	struct sockaddr_storage *dst = &null_addr;
+#ifdef USE_OPENSSL
+	int tlv_len = 0;
+	char *value = NULL;
+	struct tlv_ssl *tlv;
+	int ssl_tlv_len = 0;
+#endif
+
+	if (buf_len < PP2_HEADER_LEN)
+		return 0;
+	memcpy(hdr_p->sig, pp2_signature, PP2_SIGNATURE_LEN);
+
+	if (remote) {
+		src = &remote->addr.from;
+		dst = &remote->addr.to;
+	}
+	if (src && dst && src->ss_family == dst->ss_family && src->ss_family == AF_INET) {
+		if (buf_len < PP2_HDR_LEN_INET)
+			return 0;
+		hdr_p->cmd = PP2_VERSION | PP2_CMD_PROXY;
+		hdr_p->fam = PP2_FAM_INET | PP2_TRANS_STREAM;
+		addr_p->ipv4_addr.src_addr = ((struct sockaddr_in *)src)->sin_addr.s_addr;
+		addr_p->ipv4_addr.dst_addr = ((struct sockaddr_in *)dst)->sin_addr.s_addr;
+		addr_p->ipv4_addr.src_port = ((struct sockaddr_in *)src)->sin_port;
+		addr_p->ipv4_addr.dst_port = ((struct sockaddr_in *)dst)->sin_port;
+		ret = PP2_HDR_LEN_INET;
+	}
+	else if (src && dst && src->ss_family == dst->ss_family && src->ss_family == AF_INET6) {
+		if (buf_len < PP2_HDR_LEN_INET6)
+			return 0;
+		hdr_p->cmd = PP2_VERSION | PP2_CMD_PROXY;
+		hdr_p->fam = PP2_FAM_INET6 | PP2_TRANS_STREAM;
+		memcpy(addr_p->ipv6_addr.src_addr, &((struct sockaddr_in6 *)src)->sin6_addr, 16);
+		memcpy(addr_p->ipv6_addr.dst_addr, &((struct sockaddr_in6 *)dst)->sin6_addr, 16);
+		addr_p->ipv6_addr.src_port = ((struct sockaddr_in6 *)src)->sin6_port;
+		addr_p->ipv6_addr.dst_port = ((struct sockaddr_in6 *)dst)->sin6_port;
+		ret = PP2_HDR_LEN_INET6;
+	}
+	else {
+		if (buf_len < PP2_HDR_LEN_UNSPEC)
+			return 0;
+		hdr_p->cmd = PP2_VERSION | PP2_CMD_LOCAL;
+		hdr_p->fam = PP2_FAM_UNSPEC | PP2_TRANS_UNSPEC;
+		ret = PP2_HDR_LEN_UNSPEC;
+	}
+
+#ifdef USE_OPENSSL
+	if (srv->pp_opts & SRV_PP_V2_SSL) {
+		if ((buf_len - ret) < sizeof(struct tlv_ssl))
+			return 0;
+		tlv = (struct tlv_ssl *)&buf[ret];
+		memset(tlv, 0, sizeof(struct tlv_ssl));
+		ssl_tlv_len += sizeof(struct tlv_ssl);
+		tlv->tlv.type = PP2_TYPE_SSL;
+		if (ssl_sock_is_ssl(remote)) {
+			tlv->client |= PP2_CLIENT_SSL;
+			value = ssl_sock_get_version(remote);
+			if (value) {
+				tlv_len = make_tlv(&buf[ret+ssl_tlv_len], (buf_len-ret-ssl_tlv_len), PP2_TYPE_SSL_VERSION, strlen(value), value);
+				ssl_tlv_len += tlv_len;
+			}
+			if (ssl_sock_get_cert_used(remote)) {
+				tlv->client |= PP2_CLIENT_CERT;
+				tlv->verify = htonl(ssl_sock_get_verify_result(remote));
+			}
+			if (srv->pp_opts & SRV_PP_V2_SSL_CN) {
+				value = ssl_sock_get_common_name(remote);
+				if (value) {
+					tlv_len = make_tlv(&buf[ret+ssl_tlv_len], (buf_len - ret - ssl_tlv_len), PP2_TYPE_SSL_CN, strlen(value), value);
+					ssl_tlv_len += tlv_len;
+				}
+			}
+		}
+		tlv->tlv.length_hi = (uint16_t)(ssl_tlv_len - sizeof(struct tlv)) >> 8;
+		tlv->tlv.length_lo = (uint16_t)(ssl_tlv_len - sizeof(struct tlv)) & 0x00ff;
+		ret += ssl_tlv_len;
+	}
+#endif
+
+	hdr_p->len = htons((uint16_t)(ret - PP2_HEADER_LEN));
+
+	return ret;
+}
diff --git a/src/server.c b/src/server.c
index 5ac5e37..565a108 100644
--- a/src/server.c
+++ b/src/server.c
@@ -612,9 +612,13 @@
 				cur_arg ++;
 			}
 			else if (!defsrv && !strcmp(args[cur_arg], "send-proxy")) {
-				newsrv->state |= SRV_SEND_PROXY;
+				newsrv->pp_opts |= SRV_PP_V1;
 				cur_arg ++;
 			}
+			else if (!defsrv && !strcmp(args[cur_arg], "send-proxy-v2")) {
+				newsrv->pp_opts |= SRV_PP_V2;
+				cur_arg ++;
+			}
 			else if (!defsrv && !strcmp(args[cur_arg], "check-send-proxy")) {
 				newsrv->check.send_proxy = 1;
 				cur_arg ++;
@@ -1043,7 +1047,7 @@
 #ifdef USE_OPENSSL
 				newsrv->check.use_ssl |= (newsrv->use_ssl || (newsrv->proxy->options & PR_O_TCPCHK_SSL));
 #endif
-				newsrv->check.send_proxy |= (newsrv->state & SRV_SEND_PROXY);
+				newsrv->check.send_proxy |= (newsrv->pp_opts);
 			}
 			/* try to get the port from check_core.addr if check.port not set */
 			if (!newsrv->check.port)
diff --git a/src/ssl_sock.c b/src/ssl_sock.c
index dae8a38..fd0b41f 100644
--- a/src/ssl_sock.c
+++ b/src/ssl_sock.c
@@ -1871,6 +1871,70 @@
 	return 1;
 }
 
+char *ssl_sock_get_version(struct connection *conn)
+{
+	if (!ssl_sock_is_ssl(conn))
+		return NULL;
+
+	return (char *)SSL_get_version(conn->xprt_ctx);
+}
+
+/* returns common name, NULL terminated, from client certificate, or NULL if none */
+char *ssl_sock_get_common_name(struct connection *conn)
+{
+	X509 *crt = NULL;
+	X509_NAME *name;
+	struct chunk *cn_trash;
+	const char find_cn[] = "CN";
+	const struct chunk find_cn_chunk = {
+		.str = (char *)&find_cn,
+		.len = sizeof(find_cn)-1
+	};
+	char *result = NULL;
+
+	if (!ssl_sock_is_ssl(conn))
+		return NULL;
+
+	/* SSL_get_peer_certificate, it increase X509 * ref count */
+	crt = SSL_get_peer_certificate(conn->xprt_ctx);
+	if (!crt)
+		goto out;
+
+	name = X509_get_subject_name(crt);
+	if (!name)
+		goto out;
+
+	cn_trash = get_trash_chunk();
+	if (ssl_sock_get_dn_entry(name, &find_cn_chunk, 1, cn_trash) <= 0)
+		goto out;
+	cn_trash->str[cn_trash->len] = '\0';
+	result = cn_trash->str;
+
+	out:
+	if (crt)
+		X509_free(crt);
+
+	return result;
+}
+
+/* returns 1 if client passed a certificate, 0 if not */
+int ssl_sock_get_cert_used(struct connection *conn)
+{
+	if (!ssl_sock_is_ssl(conn))
+		return 0;
+
+	return SSL_SOCK_ST_FL_VERIFY_DONE & conn->xprt_st ? 1 : 0;
+}
+
+/* returns result from SSL verify */
+unsigned int ssl_sock_get_verify_result(struct connection *conn)
+{
+	if (!ssl_sock_is_ssl(conn))
+		return (unsigned int)X509_V_ERR_APPLICATION_VERIFICATION;
+
+	return (unsigned int)SSL_get_verify_result(conn->xprt_ctx);
+}
+
 /***** Below are some sample fetching functions for ACL/patterns *****/
 
 /* boolean, returns true if client cert was present */
@@ -3349,6 +3413,22 @@
 	newsrv->ssl_ctx.options |= SRV_SSL_O_NO_TLS_TICKETS;
 	return 0;
 }
+/* parse the "send-proxy-v2-ssl" server keyword */
+static int srv_parse_send_proxy_ssl(char **args, int *cur_arg, struct proxy *px, struct server *newsrv, char **err)
+{
+	newsrv->pp_opts |= SRV_PP_V2;
+	newsrv->pp_opts |= SRV_PP_V2_SSL;
+	return 0;
+}
+
+/* parse the "send-proxy-v2-ssl-cn" server keyword */
+static int srv_parse_send_proxy_cn(char **args, int *cur_arg, struct proxy *px, struct server *newsrv, char **err)
+{
+	newsrv->pp_opts |= SRV_PP_V2;
+	newsrv->pp_opts |= SRV_PP_V2_SSL;
+	newsrv->pp_opts |= SRV_PP_V2_SSL_CN;
+	return 0;
+}
 
 /* parse the "ssl" server keyword */
 static int srv_parse_ssl(char **args, int *cur_arg, struct proxy *px, struct server *newsrv, char **err)
@@ -3513,6 +3593,8 @@
 	{ "no-tlsv11",             srv_parse_no_tlsv11,      0, 0 }, /* disable TLSv11 */
 	{ "no-tlsv12",             srv_parse_no_tlsv12,      0, 0 }, /* disable TLSv12 */
 	{ "no-tls-tickets",        srv_parse_no_tls_tickets, 0, 0 }, /* disable session resumption tickets */
+	{ "send-proxy-v2-ssl",     srv_parse_send_proxy_ssl, 0, 0 }, /* send PROXY protocol header v2 with SSL info */
+	{ "send-proxy-v2-ssl-cn",  srv_parse_send_proxy_cn,  0, 0 }, /* send PROXY protocol header v2 with CN */
 	{ "ssl",                   srv_parse_ssl,            0, 0 }, /* enable SSL processing */
 	{ "verify",                srv_parse_verify,         1, 0 }, /* set SSL verify method */
 	{ "verifyhost",            srv_parse_verifyhost,     1, 0 }, /* require that SSL cert verifies for hostname */
diff --git a/src/stream_interface.c b/src/stream_interface.c
index f23a9b0..67a5234 100644
--- a/src/stream_interface.c
+++ b/src/stream_interface.c
@@ -422,11 +422,7 @@
 		if (conn->data == &si_conn_cb) {
 			struct stream_interface *si = conn->owner;
 			struct connection *remote = objt_conn(si->ob->prod->end);
-
-			if (remote)
-				ret = make_proxy_line(trash.str, trash.size, &remote->addr.from, &remote->addr.to);
-			else
-				ret = make_proxy_line(trash.str, trash.size, NULL, NULL);
+			ret = make_proxy_line(trash.str, trash.size, objt_server(conn->target), remote);
 		}
 		else {
 			/* The target server expects a LOCAL line to be sent first. Retrieving
@@ -440,7 +436,7 @@
 			if (!(conn->flags & CO_FL_ADDR_TO_SET))
 				goto out_wait;
 
-			ret = make_proxy_line(trash.str, trash.size, &conn->addr.from, &conn->addr.to);
+			ret = make_proxy_line(trash.str, trash.size, objt_server(conn->target), conn);
 		}
 
 		if (!ret)