MEDIUM: checks: Implement MySQL check using tcp-check rules

A share tcp-check ruleset is now created to support MySQL checks. This way no
extra memory is used if several backends use a MySQL check.

One for the following sequence is used :

    ## If no extra params are set
    tcp-check connect default linger
    tcp-check expect custom  ## will test the initial handshake

    ## If the username is defined
    tcp-check connect default linger
    tcp-check send-binary MYSQL_REQ log-format
    tcp-check expect custom  ## will test the initial handshake
    tcp-check expect custom  ## will test the reply to the client message

The log-format hexa string MYSQL_REQ depends on 2 preset variables, the packet
header containing the packet length and the sequence ID (check.header) and the
username (check.username). If is also different if the "post-41" option is set
or not. Expect rules relies on custom functions to check MySQL server packets.
diff --git a/include/proto/checks.h b/include/proto/checks.h
index bb275c8..322f9dd 100644
--- a/include/proto/checks.h
+++ b/include/proto/checks.h
@@ -77,6 +77,8 @@
 			const char *file, int line);
 int proxy_parse_pgsql_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
 				const char *file, int line);
+int proxy_parse_mysql_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
+			    const char *file, int line);
 
 #endif /* _PROTO_CHECKS_H */
 
diff --git a/include/types/checks.h b/include/types/checks.h
index 9b34cbe..5b3fda4 100644
--- a/include/types/checks.h
+++ b/include/types/checks.h
@@ -315,6 +315,7 @@
 #define TCPCHK_RULES_PGSQL_CHK   0x00000010
 #define TCPCHK_RULES_REDIS_CHK   0x00000020
 #define TCPCHK_RULES_SMTP_CHK    0x00000030
+#define TCPCHK_RULES_MYSQL_CHK   0x00000050
 #define TCPCHK_RULES_SSL3_CHK    0x00000070
 
 /* A list of tcp-check vars, to be registered before executing a ruleset */
diff --git a/include/types/proxy.h b/include/types/proxy.h
index be5b5c9..6e84037 100644
--- a/include/types/proxy.h
+++ b/include/types/proxy.h
@@ -171,7 +171,7 @@
 #define PR_O2_CHK_NONE  0x00000000      /* no L7 health checks configured (TCP by default) */
 /* unused: 0x10000000..0x30000000 */
 #define PR_O2_HTTP_CHK  0x40000000      /* use HTTP 'OPTIONS' method to check server health */
-#define PR_O2_MYSQL_CHK 0x50000000      /* use MYSQL check for server health */
+/* unused 0x50000000 */
 #define PR_O2_LDAP_CHK  0x60000000      /* use LDAP check for server health */
 /* unused: 0x70000000 */
 #define PR_O2_LB_AGENT_CHK 0x80000000   /* use a TCP connection to obtain a metric of server health */
diff --git a/src/cfgparse-listen.c b/src/cfgparse-listen.c
index e0b1bc3..b731a29 100644
--- a/src/cfgparse-listen.c
+++ b/src/cfgparse-listen.c
@@ -2416,123 +2416,10 @@
 			if (err_code & ERR_FATAL)
 				goto out;
 		}
-
 		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->options2 &= ~PR_O2_CHK_ANY;
-			curproxy->options2 |= PR_O2_MYSQL_CHK;
-
-			/* This is an example of a 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
-			 * };
-			 */
-
-			/* This is an example of a MySQL >=4.1  client Authentication packet provided by Nenad Merdanovic.
-			 * const char mysql41_client_auth_pkt[] = {
-			 * 	"\x0e\x00\x00\"		// packet length
-			 * 	"\x01"			// packet number
-			 * 	"\x00\x00\x00\x00"	// client capabilities
-			 * 	"\x00\x00\x00\x01"	// max packet
-			 *	"\x21"			// character set (UTF-8)
-			 *	char[23]		// All zeroes
-			 * 	"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) {
-							ha_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);
-
-						if (*(args[cur_arg+2])) {
-							if (!strcmp(args[cur_arg+2], "post-41")) {
-		                                                packetlen = userlen + 7 + 27;
-								reqlen    = packetlen + 9;
-
-								free(curproxy->check_req);
-								curproxy->check_req = 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[5] = 0x82; // 130
-								curproxy->check_req[11] = 1;
-								curproxy->check_req[12] = 33;
-								memcpy(&curproxy->check_req[36], mysqluser, userlen);
-								curproxy->check_req[36 + userlen + 1 + 1]     = 1;
-								curproxy->check_req[36 + userlen + 1 + 1 + 4] = 1;
-								cur_arg += 3;
-							} else {
-								ha_alert("parsing [%s:%d] : keyword '%s' only supports option 'post-41'.\n", file, linenum, args[cur_arg+2]);
-								err_code |= ERR_ALERT | ERR_FATAL;
-								goto out;
-							}
-						} else {
-							packetlen = userlen + 7;
-							reqlen    = packetlen + 9;
-
-							free(curproxy->check_req);
-							curproxy->check_req = 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[5] = 0x80;
-							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 */
-						ha_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 */
-			}
+			err_code |= proxy_parse_mysql_check_opt(args, 0, curproxy, &defproxy, file, linenum);
+			if (err_code & ERR_FATAL)
+				goto out;
 		}
 		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 d0452a9..0fcf915 100644
--- a/src/checks.c
+++ b/src/checks.c
@@ -1156,100 +1156,6 @@
 		break;
 	}
 
-	case PR_O2_MYSQL_CHK:
-		if (!done && b_data(&check->bi) < 5)
-			goto wait_more_data;
-
-		/* do not reset when closing, servers don't like this */
-		if (conn_ctrl_ready(cs->conn))
-			fdtab[cs->conn->handle.fd].linger_risk = 0;
-
-		if (s->proxy->check_len == 0) { // old mode
-			if (*(b_head(&check->bi) + 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 (b_data(&check->bi) > 51) {
-					desc = ltrim(b_head(&check->bi) + 5, ' ');
-					set_server_check_status(check, 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(check, HCHK_STATUS_L7RSP, b_head(&check->bi));
-				}
-			}
-			else {
-				/* An error message is attached in the Error packet */
-				desc = ltrim(b_head(&check->bi) + 7, ' ');
-				set_server_check_status(check, HCHK_STATUS_L7STS, desc);
-			}
-		} else {
-			unsigned int first_packet_len = ((unsigned int) *b_head(&check->bi)) +
-			                                (((unsigned int) *(b_head(&check->bi) + 1)) << 8) +
-			                                (((unsigned int) *(b_head(&check->bi) + 2)) << 16);
-
-			if (b_data(&check->bi) == first_packet_len + 4) {
-				/* MySQL Error packet always begin with field_count = 0xff */
-				if (*(b_head(&check->bi) + 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 && b_data(&check->bi) < 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 = &b_head(&check->bi)[7];
-					//ha_warning("onlyoneERR: %s\n", desc);
-					set_server_check_status(check, HCHK_STATUS_L7STS, desc);
-				}
-			} else if (b_data(&check->bi) > first_packet_len + 4) {
-				unsigned int second_packet_len = ((unsigned int) *(b_head(&check->bi) + first_packet_len + 4)) +
-				                                 (((unsigned int) *(b_head(&check->bi) + first_packet_len + 5)) << 8) +
-				                                 (((unsigned int) *(b_head(&check->bi) + first_packet_len + 6)) << 16);
-
-				if (b_data(&check->bi) == 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 (*(b_head(&check->bi) + first_packet_len + 8) != '\xff') {
-						/* No error packet */
-						/* We set the MySQL Version in description for information purpose */
-						desc = &b_head(&check->bi)[5];
-						//ha_warning("2packetOK: %s\n", desc);
-						set_server_check_status(check, HCHK_STATUS_L7OKD, desc);
-					}
-					else {
-						/* An error message is attached in the Error packet
-						* so we can display it ! :)
-						*/
-						desc = &b_head(&check->bi)[first_packet_len+11];
-						//ha_warning("2packetERR: %s\n", desc);
-						set_server_check_status(check, HCHK_STATUS_L7STS, desc);
-					}
-				}
-			}
-			else {
-				if (!done)
-					goto wait_more_data;
-
-				/* it seems we have a Handshake Initialization packet but without a valid length,
-				 * it must be a protocol error
-				 */
-				desc = &b_head(&check->bi)[5];
-				//ha_warning("protoerr: %s\n", desc);
-				set_server_check_status(check, HCHK_STATUS_L7RSP, desc);
-			}
-		}
-		break;
-
 	case PR_O2_LDAP_CHK:
 		if (!done && b_data(&check->bi) < 14)
 			goto wait_more_data;
@@ -2776,6 +2682,95 @@
 	*(b_tail(msg)) = '\0';
 }
 
+static enum tcpcheck_eval_ret tcpcheck_mysql_expect_packet(struct check *check, struct tcpcheck_rule *rule,
+							   unsigned int offset, int last_read)
+{
+	enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
+	enum healthcheck_status status;
+	struct buffer *msg = NULL;
+	struct ist desc = ist(NULL);
+	unsigned int err = 0, plen = 0;
+
+
+	/* 3 Bytes for the packet length and 1 byte for the sequence id */
+	if (!last_read && b_data(&check->bi) < offset+4) {
+		if (!last_read)
+			goto wait_more_data;
+
+		/* invalid length or truncated response */
+		status = HCHK_STATUS_L7RSP;
+		goto error;
+	}
+
+	plen = ((unsigned char) *b_peek(&check->bi, offset)) +
+		(((unsigned char) *(b_peek(&check->bi, offset+1))) << 8) +
+		(((unsigned char) *(b_peek(&check->bi, offset+2))) << 16);
+
+	if (b_data(&check->bi) < offset+plen+4) {
+		if (!last_read)
+			goto wait_more_data;
+
+		/* invalid length or truncated response */
+		status = HCHK_STATUS_L7RSP;
+		goto error;
+	}
+
+	if (*b_peek(&check->bi, offset+4) == '\xff') {
+		/* MySQL Error packet always begin with field_count = 0xff */
+		status = HCHK_STATUS_L7STS;
+		err = ((unsigned char) *b_peek(&check->bi, offset+5)) +
+			(((unsigned char) *(b_peek(&check->bi, offset+6))) << 8);
+		desc = ist2(b_peek(&check->bi, offset+7), b_data(&check->bi) - offset - 7);
+		goto error;
+	}
+
+	if (get_next_tcpcheck_rule(check->tcpcheck_rules, rule) != NULL) {
+		/* Not the last rule, continue */
+		goto out;
+	}
+
+	/* 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.
+	 */
+	set_server_check_status(check, HCHK_STATUS_L7OKD, b_peek(&check->bi, 5));
+
+  out:
+	free_trash_chunk(msg);
+	return ret;
+
+  error:
+	ret = TCPCHK_EVAL_STOP;
+	check->code = err;
+	msg = alloc_trash_chunk();
+	if (msg)
+		tcpcheck_onerror_message(msg, check, rule, 0, desc);
+	set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
+	goto out;
+
+  wait_more_data:
+	ret = TCPCHK_EVAL_WAIT;
+	goto out;
+}
+
+
+static enum tcpcheck_eval_ret tcpcheck_mysql_expect_iniths(struct check *check, struct tcpcheck_rule *rule, int last_read)
+{
+	return tcpcheck_mysql_expect_packet(check, rule, 0, last_read);
+}
+
+static enum tcpcheck_eval_ret tcpcheck_mysql_expect_ok(struct check *check, struct tcpcheck_rule *rule, int last_read)
+{
+	unsigned int hslen = 0;
+
+	hslen = 4 + ((unsigned char) *b_head(&check->bi)) +
+		(((unsigned char) *(b_peek(&check->bi, 1))) << 8) +
+		(((unsigned char) *(b_peek(&check->bi, 2))) << 16);
+
+	return tcpcheck_mysql_expect_packet(check, rule, hslen, last_read);
+}
+
+
 /* Evaluate a TCPCHK_ACT_CONNECT rule. It returns 1 to evaluate the next rule, 0
  * to wait and -1 to stop the check. */
 static enum tcpcheck_eval_ret tcpcheck_eval_connect(struct check *check, struct tcpcheck_rule *rule)
@@ -5526,6 +5521,240 @@
 	return err_code;
 
   error:
+	free(user);
+	free(var);
+	free_tcpcheck_vars(&rules->preset_vars);
+	tcpcheck_ruleset_release(rs);
+	err_code |= ERR_ALERT | ERR_FATAL;
+	goto out;
+}
+
+
+/* Parses the "option mysql-check" proxy keyword */
+int proxy_parse_mysql_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
+				const char *file, int line)
+{
+	/* This is an example of a 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
+	 * };
+	 */
+	static char mysql40_rsname[] = "*mysql40-check";
+	static char mysql40_req[] = {
+		"%[var(check.header),hex]"     /* 3 bytes for the packet length and 1 byte for the sequence ID */
+		"0080"                         /* client capabilities */
+		"000001"                       /* max packet */
+		"%[var(check.username),hex]00" /* the username */
+		"00"                           /* filler (always 0x00) */
+		"010000"                       /* packet length*/
+		"00"                           /* sequence ID */
+		"01"                           /* COM_QUIT command */
+	};
+
+	/* This is an example of a MySQL >=4.1  client Authentication packet provided by Nenad Merdanovic.
+	 * const char mysql41_client_auth_pkt[] = {
+	 * 	"\x0e\x00\x00\"		// packet length
+	 * 	"\x01"			// packet number
+	 * 	"\x00\x00\x00\x00"	// client capabilities
+	 * 	"\x00\x00\x00\x01"	// max packet
+	 *	"\x21"			// character set (UTF-8)
+	 *	char[23]		// All zeroes
+	 * 	"haproxy\x00"		// username (null terminated string)
+	 * 	"\x00"			// filler (always 0x00)
+	 * 	"\x01\x00\x00"		// packet length
+	 * 	"\x00"			// packet number
+	 * 	"\x01"			// COM_QUIT command
+	 * };
+	 */
+	static char mysql41_rsname[] = "*mysql41-check";
+	static char mysql41_req[] = {
+		"%[var(check.header),hex]"     /* 3 bytes for the packet length and 1 byte for the sequence ID */
+		"00820000"                     /* client capabilities */
+		"00800001"                     /* max packet */
+		"21"                           /* character set (UTF-8) */
+		"000000000000000000000000"     /* 23 bytes, al zeroes */
+		"0000000000000000000000"
+		"%[var(check.username),hex]00" /* the username */
+		"00"                           /* filler (always 0x00) */
+		"010000"                       /* packet length*/
+		"00"                           /* sequence ID */
+		"01"                           /* COM_QUIT command */
+	};
+
+	struct tcpcheck_ruleset *rs = NULL;
+	struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
+	struct tcpcheck_rule *chk;
+	struct tcpcheck_var *var = NULL;
+	char *mysql_rsname = "*mysql-check";
+	char *mysql_req = NULL, *hdr = NULL, *user = NULL, *errmsg = NULL;
+	int index = 0, err_code = 0;
+
+	if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
+		err_code |= ERR_WARN;
+
+	if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
+		goto out;
+
+	if (rules->list && !(rules->flags & TCPCHK_RULES_SHARED)) {
+		ha_alert("parsing [%s:%d] : A custom tcp-check ruleset is already configured.\n",
+			 file, line);
+		err_code |= ERR_ALERT | ERR_FATAL;
+		goto out;
+	}
+
+	curpx->options2 &= ~PR_O2_CHK_ANY;
+	curpx->options2 |= PR_O2_TCPCHK_CHK;
+
+	free_tcpcheck_vars(&rules->preset_vars);
+	rules->list  = NULL;
+	rules->flags = 0;
+
+	cur_arg += 2;
+	if (*args[cur_arg]) {
+		char *user;
+		int packetlen, userlen;
+
+		if (strcmp(args[cur_arg], "user") != 0) {
+			ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user' (got '%s').\n",
+				 file, line, args[0], args[1], args[cur_arg]);
+			goto error;
+		}
+
+		if (*(args[cur_arg+1]) == 0) {
+			ha_alert("parsing [%s:%d] : '%s %s %s' expects <username> as argument.\n",
+				 file, line, args[0], args[1], args[cur_arg]);
+			goto error;
+		}
+
+		hdr     = calloc(4, sizeof(*hdr));
+		user    = strdup(args[cur_arg+1]);
+		userlen = strlen(args[cur_arg+1]);
+
+		if (hdr == NULL || user == NULL) {
+			ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
+			goto error;
+		}
+
+		if (*args[cur_arg+2]) {
+			if (strcmp(args[cur_arg+2], "post-41") != 0) {
+				ha_alert("parsing [%s:%d] : keyword '%s' only supports option 'post-41' (got '%s').\n",
+					 file, line, args[cur_arg], args[cur_arg+2]);
+				goto error;
+			}
+			packetlen = userlen + 7 + 27;
+			mysql_req = mysql41_req;
+			mysql_rsname  = mysql41_rsname;
+		}
+		else {
+			packetlen = userlen + 7;
+			mysql_req = mysql40_req;
+			mysql_rsname  = mysql40_rsname;
+		}
+
+		hdr[0] = (unsigned char)(packetlen & 0xff);
+		hdr[1] = (unsigned char)((packetlen >> 8) & 0xff);
+		hdr[2] = (unsigned char)((packetlen >> 16) & 0xff);
+		hdr[3] = 1;
+
+		var = tcpcheck_var_create("check.header");
+		if (var == NULL) {
+			ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
+			goto error;
+		}
+		var->data.type = SMP_T_STR;
+		var->data.u.str.area = hdr;
+		var->data.u.str.data = 4;
+		LIST_INIT(&var->list);
+		LIST_ADDQ(&rules->preset_vars, &var->list);
+		hdr = NULL;
+		var = NULL;
+
+		var = tcpcheck_var_create("check.username");
+		if (var == NULL) {
+			ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
+			goto error;
+		}
+		var->data.type = SMP_T_STR;
+		var->data.u.str.area = user;
+		var->data.u.str.data = strlen(user);
+		LIST_INIT(&var->list);
+		LIST_ADDQ(&rules->preset_vars, &var->list);
+		user = NULL;
+		var = NULL;
+	}
+
+	rs = tcpcheck_ruleset_lookup(mysql_rsname);
+	if (rs)
+		goto ruleset_found;
+
+	rs = tcpcheck_ruleset_create(mysql_rsname);
+	if (rs == NULL) {
+		ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
+		goto error;
+	}
+
+	chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
+				     1, curpx, &rs->rules, file, line, &errmsg);
+	if (!chk) {
+		ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
+		goto error;
+	}
+	chk->index = index++;
+	LIST_ADDQ(&rs->rules, &chk->list);
+
+	if (mysql_req) {
+		chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", mysql_req, "log-format", ""},
+					  1, curpx, &rs->rules, file, line, &errmsg);
+		if (!chk) {
+			ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
+			goto error;
+		}
+		chk->index = index++;
+		LIST_ADDQ(&rs->rules, &chk->list);
+	}
+
+	chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
+		                    1, curpx, &rs->rules, file, line, &errmsg);
+	if (!chk) {
+		ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
+		goto error;
+	}
+	chk->expect.custom = tcpcheck_mysql_expect_iniths;
+	chk->index = index++;
+	LIST_ADDQ(&rs->rules, &chk->list);
+
+	if (mysql_req) {
+		chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
+					    1, curpx, &rs->rules, file, line, &errmsg);
+		if (!chk) {
+			ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
+			goto error;
+		}
+		chk->expect.custom = tcpcheck_mysql_expect_ok;
+		chk->index = index++;
+		LIST_ADDQ(&rs->rules, &chk->list);
+	}
+
+	LIST_ADDQ(&tcpchecks_list, &rs->list);
+
+  ruleset_found:
+	rules->list = &rs->rules;
+	rules->flags |= (TCPCHK_RULES_SHARED|TCPCHK_RULES_MYSQL_CHK);
+
+  out:
+	free(errmsg);
+	return err_code;
+
+  error:
+	free(hdr);
 	free(user);
 	free(var);
 	free_tcpcheck_vars(&rules->preset_vars);