BUG/MINOR: tcpcheck: Improve LDAP response parsing to fix LDAP check

When the LDAP response is parsed, the message length is not properly
decoded. While it works for LDAP servers encoding it on 1 byte, it does not
work for those using a multi-bytes encoding. Among others, Active Directory
servers seems to encode messages or elements length on 4 bytes.

In this patch, we only handle length of BindResponse messages encoded on 1,
2 or 4 bytes. In theory, it may be encoded on any bytes number less than 127
bytes. But it is useless to make this part too complex. It should be ok this
way.

This patch should fix the issue #1390. It should be backported to all stable
versions. While it should be easy to backport it as far as 2.2, the patch
will have to be totally rewritten for lower versions.

(cherry picked from commit 8a0e5f822b84c984681547eb0e9ee00d8a19ce56)
Signed-off-by: Christopher Faulet <cfaulet@haproxy.com>
diff --git a/src/tcpcheck.c b/src/tcpcheck.c
index bbc3bbd..1c20f5e 100644
--- a/src/tcpcheck.c
+++ b/src/tcpcheck.c
@@ -48,6 +48,7 @@
 #include <haproxy/istbuf.h>
 #include <haproxy/list.h>
 #include <haproxy/log.h>
+#include <haproxy/net_helper.h>
 #include <haproxy/protocol.h>
 #include <haproxy/proxy-t.h>
 #include <haproxy/regex.h>
@@ -656,7 +657,9 @@
 	enum healthcheck_status status;
 	struct buffer *msg = NULL;
 	struct ist desc = IST_NULL;
-	unsigned short msglen = 0;
+	char *ptr;
+	unsigned short nbytes = 0;
+	size_t msglen = 0;
 
 	TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
 
@@ -664,35 +667,65 @@
 	 * http://en.wikipedia.org/wiki/Basic_Encoding_Rules
 	 * http://tools.ietf.org/html/rfc4511
 	 */
+	ptr = b_head(&check->bi) + 1;
+
 	/* size of LDAPMessage */
-	msglen = (*(b_head(&check->bi) + 1) & 0x80) ? (*(b_head(&check->bi) + 1) & 0x7f) : 0;
+	if (*ptr & 0x80) {
+		/* For message size encoded on several bytes, we only handle
+		 * size encoded on 2 or 4 bytes. There is no reason to make this
+		 * part to complex because only Active Directory is known to
+		 * encode BindReponse length on 4 bytes.
+		 */
+		nbytes = (*ptr & 0x7f);
+		if (b_data(&check->bi) < 1 + nbytes)
+			goto too_short;
+		switch (nbytes) {
+			case 4: msglen = read_n32(ptr+1); break;
+			case 2: msglen = read_n16(ptr+1); break;
+			default:
+				status = HCHK_STATUS_L7RSP;
+				desc = ist("Not LDAPv3 protocol");
+				goto error;
+		}
+	}
+	else
+		msglen = *ptr;
+	ptr += 1 + nbytes;
+
+	if (b_data(&check->bi) < 2 + nbytes + msglen)
+		goto too_short;
 
 	/* http://tools.ietf.org/html/rfc4511#section-4.2.2
 	 *   messageID: 0x02 0x01 0x01: INTEGER 1
 	 *   protocolOp: 0x61: bindResponse
 	 */
-	if ((msglen > 2) || (memcmp(b_head(&check->bi) + 2 + msglen, "\x02\x01\x01\x61", 4) != 0)) {
+	if (memcmp(ptr, "\x02\x01\x01\x61", 4) != 0) {
 		status = HCHK_STATUS_L7RSP;
 		desc = ist("Not LDAPv3 protocol");
 		goto error;
 	}
+	ptr += 4;
 
-	/* size of bindResponse */
-	msglen += (*(b_head(&check->bi) + msglen + 6) & 0x80) ? (*(b_head(&check->bi) + msglen + 6) & 0x7f) : 0;
+	/* skip size of bindResponse */
+	nbytes = 0;
+	if (*ptr & 0x80)
+		nbytes = (*ptr & 0x7f);
+	ptr += 1 + nbytes;
 
 	/* http://tools.ietf.org/html/rfc4511#section-4.1.9
 	 *   ldapResult: 0x0a 0x01: ENUMERATION
 	 */
-	if ((msglen > 4) || (memcmp(b_head(&check->bi) + 7 + msglen, "\x0a\x01", 2) != 0)) {
+	if (memcmp(ptr, "\x0a\x01", 2) != 0) {
 		status = HCHK_STATUS_L7RSP;
 		desc = ist("Not LDAPv3 protocol");
 		goto error;
 	}
+	ptr += 2;
 
 	/* http://tools.ietf.org/html/rfc4511#section-4.1.9
 	 *   resultCode
 	 */
-	check->code = *(b_head(&check->bi) + msglen + 9);
+	check->code = *ptr;
 	if (check->code) {
 		status = HCHK_STATUS_L7STS;
 		desc = ist("See RFC: http://tools.ietf.org/html/rfc4511#section-4.1.9");
@@ -714,6 +747,18 @@
 		tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
 	set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
 	goto out;
+
+  too_short:
+	if (!last_read)
+		goto wait_more_data;
+	/* invalid length or truncated response */
+	status = HCHK_STATUS_L7RSP;
+	goto error;
+
+  wait_more_data:
+	TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
+	ret = TCPCHK_EVAL_WAIT;
+	goto out;
 }
 
 /* Custom tcp-check expect function to parse and validate the SPOP hello agent