[MINOR] add better support to "mysql-check"

The MySQL check has been revamped to be able to send real MySQL data,
and to avoid Aborted connects on MySQL side.
It is however backward compatible with older version, but it is highly
recommended to use the new mode, by adding "user <username>" on the
"mysql-check" line.

The new check consists in sending two MySQL packet, one Client
Authentication packet, with "haproxy" username (by default), and one
QUIT packet, to correctly close MySQL session. We then parse the Mysql
Handshake Initialisation packet and/or Error packet. It is a basic but
useful test which does not produce error nor aborted connect on the
server.
(cherry picked from commit a1e4dcfe5718311b7653d7dabfad65c005d0439b)
diff --git a/doc/configuration.txt b/doc/configuration.txt
index c0b04bf..d96a498 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -3350,17 +3350,39 @@
              logging.
 
 
-option mysql-check
-  Use Mysql health checks for server testing
+option mysql-check [ user <username> ]
+  Use MySQL health checks for server testing
   May be used in sections :   defaults | frontend | listen | backend
                                  yes   |    no    |   yes  |   yes
-  Arguments : none
+  Arguments :
+    user <username> This is the username which will be used when connecting
+    to MySQL server.
+
+  If you specify a username, the check consists of sending two MySQL packet,
+  one Client Authentication packet, and one QUIT packet, to correctly close
+  MySQL session. We then parse the MySQL Handshake Initialisation packet and/or
+  Error packet. It is a basic but useful test which does not produce error nor
+  aborted connect on the server. However, it requires adding an authorization
+  in the MySQL table, like this :
+
+      USE mysql;
+      INSERT INTO user (Host,User) values ('<ip_of_haproxy>','<username>');
+      FLUSH PRIVILEGES;
+
+  If you don't specify a username (it is deprecated and not recommended), the
+  check only consists in parsing the Mysql Handshake Initialisation packet or
+  Error packet, we don't send anything in this mode. It was reported that it
+  can generate lockout if check is too frequent and/or if there is not enough
+  traffic. In fact, you need in this case to check MySQL "max_connect_errors"
+  value as if a connection is established successfully within fewer than MySQL
+  "max_connect_errors" attempts after a previous connection was interrupted,
+  the error count for the host is cleared to zero. If HAProxy's server get
+  blocked, the "FLUSH HOSTS" statement is the only way to unblock it.
+
+  Remember that this does not check database presence nor database consistency.
+  To do this, you can use an external check with xinetd for example.
 
-  The check consists in parsing Mysql Handshake Initialisation packet or Error
-  packet, which is sent by MySQL server on connect. It is a basic but useful
-  test which does not produce any logging on the server. However, it does not
-  check database presence nor database consistency, nor user permission to
-  access. To do this, you can use an external check with xinetd for example.
+  The check requires MySQL >=4.0, for older version, please use TCP check.
 
   Most often, an incoming MySQL server needs to see the client's IP address for
   various purposes, including IP privilege matching and connection logging.
diff --git a/src/cfgparse.c b/src/cfgparse.c
index 617bb41..58c5db8 100644
--- a/src/cfgparse.c
+++ b/src/cfgparse.c
@@ -2904,6 +2904,9 @@
 		}
 		else if (!strcmp(args[1], "mysql-check")) {
 			/* use MYSQL request to check servers' health */
+			if (warnifnotcap(curproxy, PR_CAP_BE, file, linenum, args[1], NULL))
+				err_code |= ERR_WARN;
+
 			free(curproxy->check_req);
 			curproxy->check_req = NULL;
 			curproxy->options &= ~PR_O_HTTP_CHK;
@@ -2911,6 +2914,65 @@
 			curproxy->options2 &= ~PR_O2_SSL3_CHK;
 			curproxy->options2 &= ~PR_O2_LDAP_CHK;
 			curproxy->options2 |= PR_O2_MYSQL_CHK;
+
+			/* This is an exemple of an MySQL >=4.0 client Authentication packet kindly provided by Cyril Bonte.
+			 * const char mysql40_client_auth_pkt[] = {
+			 * 	"\x0e\x00\x00"	// packet length
+			 * 	"\x01"		// packet number
+			 * 	"\x00\x00"	// client capabilities
+			 * 	"\x00\x00\x01"	// max packet
+			 * 	"haproxy\x00"	// username (null terminated string)
+			 * 	"\x00"		// filler (always 0x00)
+			 * 	"\x01\x00\x00"	// packet length
+			 * 	"\x00"		// packet number
+			 * 	"\x01"		// COM_QUIT command
+			 * };
+			 */
+
+			if (*(args[2])) {
+				int cur_arg = 2;
+
+				while (*(args[cur_arg])) {
+					if (strcmp(args[cur_arg], "user") == 0) {
+						char *mysqluser;
+						int packetlen, reqlen, userlen;
+
+						/* suboption header - needs additional argument for it */
+						if (*(args[cur_arg+1]) == 0) {
+							Alert("parsing [%s:%d] : '%s %s %s' expects <username> as argument.\n",
+							      file, linenum, args[0], args[1], args[cur_arg]);
+							err_code |= ERR_ALERT | ERR_FATAL;
+							goto out;
+						}
+						mysqluser = args[cur_arg + 1];
+						userlen   = strlen(mysqluser);
+						packetlen = userlen + 7;
+						reqlen    = packetlen + 9;
+
+						free(curproxy->check_req);
+						curproxy->check_req = (char *)calloc(1, reqlen);
+						curproxy->check_len = reqlen;
+
+						snprintf(curproxy->check_req, 4, "%c%c%c",
+							((unsigned char) packetlen & 0xff),
+							((unsigned char) (packetlen >> 8) & 0xff),
+							((unsigned char) (packetlen >> 16) & 0xff));
+
+						curproxy->check_req[3] = 1;
+						curproxy->check_req[8] = 1;
+						memcpy(&curproxy->check_req[9], mysqluser, userlen);
+						curproxy->check_req[9 + userlen + 1 + 1]     = 1;
+						curproxy->check_req[9 + userlen + 1 + 1 + 4] = 1;
+						cur_arg += 2;
+					} else {
+						/* unknown suboption - catchall */
+						Alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user'.\n",
+						      file, linenum, args[0], args[1]);
+						err_code |= ERR_ALERT | ERR_FATAL;
+						goto out;
+					}
+				} /* end while loop */
+			}
 		}
 		else if (!strcmp(args[1], "ldap-check")) {
 			/* use LDAP request to check servers' health */
diff --git a/src/checks.c b/src/checks.c
index 4132f5e..a23fd41 100644
--- a/src/checks.c
+++ b/src/checks.c
@@ -848,8 +848,9 @@
 
 /*
  * This function is used only for server health-checks. It handles the server's
- * reply to an HTTP request or SSL HELLO. It calls set_server_check_status() to
- * update s->check_status, s->check_duration and s->result.
+ * reply to an HTTP request, SSL HELLO or MySQL client Auth. It calls
+ * set_server_check_status() to update s->check_status, s->check_duration
+ * and s->result.
 
  * The set_server_check_status function is called with HCHK_STATUS_L7OKD if
  * an HTTP server replies HTTP 2xx or 3xx (valid responses), if an SMTP server
@@ -1001,36 +1002,91 @@
 			set_server_check_status(s, HCHK_STATUS_L7STS, desc);
 	}
 	else if (s->proxy->options2 & PR_O2_MYSQL_CHK) {
-		/* MySQL Error packet always begin with field_count = 0xff
-		 * contrary to OK Packet who always begin whith 0x00 */
 		if (!done && s->check_data_len < 5)
 			goto wait_more_data;
 
-		if (*(s->check_data + 4) != '\xff') {
-			/* We set the MySQL Version in description for information purpose
-			 * FIXME : it can be cool to use MySQL Version for other purpose,
-			 * like mark as down old MySQL server.
-			 */
-			if (s->check_data_len > 51) {
-				desc = ltrim(s->check_data + 5, ' ');
-				set_server_check_status(s, HCHK_STATUS_L7OKD, desc);
+		if (s->proxy->check_len == 0) { // old mode
+			if (*(s->check_data + 4) != '\xff') {
+				/* We set the MySQL Version in description for information purpose
+				 * FIXME : it can be cool to use MySQL Version for other purpose,
+				 * like mark as down old MySQL server.
+				 */
+				if (s->check_data_len > 51) {
+					desc = ltrim(s->check_data + 5, ' ');
+					set_server_check_status(s, HCHK_STATUS_L7OKD, desc);
+				}
+				else {
+					if (!done)
+						goto wait_more_data;
+					/* it seems we have a OK packet but without a valid length,
+					 * it must be a protocol error
+					 */
+					set_server_check_status(s, HCHK_STATUS_L7RSP, s->check_data);
+				}
 			}
 			else {
+				/* An error message is attached in the Error packet */
+				desc = ltrim(s->check_data + 7, ' ');
+				set_server_check_status(s, HCHK_STATUS_L7STS, desc);
+			}
+		} else {
+			unsigned int first_packet_len = ((unsigned int) *s->check_data) +
+			                                (((unsigned int) *(s->check_data + 1)) << 8) +
+			                                (((unsigned int) *(s->check_data + 2)) << 16);
+
+			if (s->check_data_len == first_packet_len + 4) {
+				/* MySQL Error packet always begin with field_count = 0xff */
+				if (*(s->check_data + 4) != '\xff') {
+					/* We have only one MySQL packet and it is a Handshake Initialization packet
+					* but we need to have a second packet to know if it is alright
+					*/
+					if (!done && s->check_data_len < first_packet_len + 5)
+						goto wait_more_data;
+				}
+				else {
+					/* We have only one packet and it is an Error packet,
+					* an error message is attached, so we can display it
+					*/
+					desc = &s->check_data[7];
+					//Warning("onlyoneERR: %s\n", desc);
+					set_server_check_status(s, HCHK_STATUS_L7STS, desc);
+				}
+			} else if (s->check_data_len > first_packet_len + 4) {
+				unsigned int second_packet_len = ((unsigned int) *(s->check_data + first_packet_len + 4)) +
+				                                 (((unsigned int) *(s->check_data + first_packet_len + 5)) << 8) +
+				                                 (((unsigned int) *(s->check_data + first_packet_len + 6)) << 16);
+
+				if (s->check_data_len == first_packet_len + 4 + second_packet_len + 4 ) {
+					/* We have 2 packets and that's good */
+					/* Check if the second packet is a MySQL Error packet or not */
+					if (*(s->check_data + first_packet_len + 8) != '\xff') {
+						/* No error packet */
+						/* We set the MySQL Version in description for information purpose */
+						desc = &s->check_data[5];
+						//Warning("2packetOK: %s\n", desc);
+						set_server_check_status(s, HCHK_STATUS_L7OKD, desc);
+					}
+					else {
+						/* An error message is attached in the Error packet
+						* so we can display it ! :)
+						*/
+						desc = &s->check_data[first_packet_len+11];
+						//Warning("2packetERR: %s\n", desc);
+						set_server_check_status(s, HCHK_STATUS_L7STS, desc);
+					}
+				}
+			}
+			else {
 				if (!done)
 					goto wait_more_data;
-				/* it seems we have a OK packet but without a valid length,
+				/* it seems we have a Handshake Initialization packet but without a valid length,
 				 * it must be a protocol error
 				 */
-				set_server_check_status(s, HCHK_STATUS_L7RSP, s->check_data);
+				desc = &s->check_data[5];
+				//Warning("protoerr: %s\n", desc);
+				set_server_check_status(s, HCHK_STATUS_L7RSP, desc);
 			}
 		}
-		else {
-			/* An error message is attached in the Error packet,
-			 * so we can display it ! :)
-			 */
-			desc = ltrim(s->check_data + 7, ' ');
-			set_server_check_status(s, HCHK_STATUS_L7STS, desc);
-		}
 	}
 	else if (s->proxy->options2 & PR_O2_LDAP_CHK) {
 		if (!done && s->check_data_len < 14)
diff --git a/tests/test-sql.cfg b/tests/test-sql.cfg
index 52dfefd..c2aebd9 100644
--- a/tests/test-sql.cfg
+++ b/tests/test-sql.cfg
@@ -21,7 +21,7 @@
         bind :3307
         mode tcp
         balance roundrobin
-        option mysql-check
+        option mysql-check user haproxy
         server  srv1 127.0.0.1:3306 check port 3306 inter 1000 fall 1
 #        server  srv2 127.0.0.2:3306 check port 3306 inter 1000 fall 1
 #        server  srv3 127.0.0.3:3306 check port 3306 inter 1000 fall 1