MEDIUM: proxy/checks: Register a keyword to parse tcp-check rules
The keyword 'tcp-check' is now parsed in a dedicated callback function. Thus the
code to parse these rules is now located in checks.c. In addition, a deinit
function have been added to release proxy tcp-check rules, on error or when
HAProxy is stopped.
This patch is based on Gaetan Rivet work. It uses a callback function registerd
on the 'tcp-check' keyword instead, but the spirit is the same.
diff --git a/src/cfgparse-listen.c b/src/cfgparse-listen.c
index e922c09..00b1423 100644
--- a/src/cfgparse-listen.c
+++ b/src/cfgparse-listen.c
@@ -162,55 +162,6 @@
warnif_misplaced_tcp_sess(proxy, file, line, arg);
}
-/* Parse a comment string for an expect check rule to find a potential
- * regex backreference. If so, check that it is valid.
- * returns:
- * 0 if none found.
- * 1 if at least one found and all are valid.
- * -1 if at least one found and at least one is invalid.
- */
-static int find_and_check_backreferences(const char *str, char **err)
-{
- static char *errors[] = {
- "invalid backreference value",
- "backreference is not within range [1, 9]",
- };
- char *backslash;
- unsigned long int ref;
- int found = 0;
-
- while ((backslash = strchr(str, '\\'))) {
- char *next, *end;
-
- next = backslash + 1;
- if (!isdigit(*next)) {
- str = next;
- continue;
- }
-
- errno = 0;
- ref = strtoul(next, &end, 10);
- if (errno == EINVAL) {
- *err = errors[0];
- return -1;
- }
- else if (errno == ERANGE) {
- *err = errors[1];
- return -1;
- }
-
- if (ref == 0 || ref > 9) {
- *err = errors[1];
- return -1;
- }
-
- found = 1;
- str = end;
- }
-
- return found;
-}
-
int cfg_parse_listen(const char *file, int linenum, char **args, int kwm)
{
static struct proxy *curproxy = NULL;
@@ -3084,358 +3035,6 @@
goto out;
}
}
- else if (!strcmp(args[0], "tcp-check")) {
- if (warnifnotcap(curproxy, PR_CAP_BE, file, linenum, args[0], NULL))
- err_code |= ERR_WARN;
-
- if (curproxy == &defproxy) {
- ha_alert("parsing [%s:%d]: '%s' not allowed in 'defaults' section.\n",
- file, linenum, args[0]);
- err_code |= ERR_ALERT | ERR_FATAL;
- goto out;
- }
-
- if (curproxy->tcpcheck_rules == NULL) {
- curproxy->tcpcheck_rules = calloc(1, sizeof(*curproxy->tcpcheck_rules));
- if (curproxy->tcpcheck_rules == NULL) {
- ha_alert("parsing [%s:%d] : out of memory.\n", file, linenum);
- err_code |= ERR_ALERT | ERR_ABORT;
- goto out;
- }
- LIST_INIT(curproxy->tcpcheck_rules);
- }
-
- if (strcmp(args[1], "comment") == 0) {
- int cur_arg;
- struct tcpcheck_rule *tcpcheck;
-
- cur_arg = 1;
- tcpcheck = calloc(1, sizeof(*tcpcheck));
- tcpcheck->action = TCPCHK_ACT_COMMENT;
-
- if (!*args[cur_arg + 1]) {
- ha_alert("parsing [%s:%d] : '%s' expects a comment string.\n",
- file, linenum, args[cur_arg]);
- err_code |= ERR_ALERT | ERR_FATAL;
- goto out;
- }
-
- tcpcheck->comment = strdup(args[cur_arg + 1]);
-
- LIST_ADDQ(curproxy->tcpcheck_rules, &tcpcheck->list);
- if (alertif_too_many_args_idx(1, 1, file, linenum, args, &err_code))
- goto out;
- }
- else if (strcmp(args[1], "connect") == 0) {
- const char *ptr_arg;
- int cur_arg;
- struct tcpcheck_rule *tcpcheck;
-
- /* check if first rule is also a 'connect' action */
- tcpcheck = LIST_NEXT(curproxy->tcpcheck_rules, struct tcpcheck_rule *, list);
- while (&tcpcheck->list != curproxy->tcpcheck_rules &&
- tcpcheck->action == TCPCHK_ACT_COMMENT) {
- tcpcheck = LIST_NEXT(&tcpcheck->list, struct tcpcheck_rule *, list);
- }
-
- if (&tcpcheck->list != curproxy->tcpcheck_rules
- && tcpcheck->action != TCPCHK_ACT_CONNECT) {
- ha_alert("parsing [%s:%d] : first step MUST also be a 'connect' when there is a 'connect' step in the tcp-check ruleset.\n",
- file, linenum);
- err_code |= ERR_ALERT | ERR_FATAL;
- goto out;
- }
-
- cur_arg = 2;
- tcpcheck = calloc(1, sizeof(*tcpcheck));
- tcpcheck->action = TCPCHK_ACT_CONNECT;
-
- /* parsing each parameters to fill up the rule */
- while (*(ptr_arg = args[cur_arg])) {
- /* tcp port */
- if (strcmp(args[cur_arg], "port") == 0) {
- if ( (atol(args[cur_arg + 1]) > 65535) ||
- (atol(args[cur_arg + 1]) < 1) ){
- ha_alert("parsing [%s:%d] : '%s %s %s' expects a valid TCP port (from range 1 to 65535), got %s.\n",
- file, linenum, args[0], args[1], "port", args[cur_arg + 1]);
- err_code |= ERR_ALERT | ERR_FATAL;
- goto out;
- }
- tcpcheck->port = atol(args[cur_arg + 1]);
- cur_arg += 2;
- }
- /* send proxy protocol */
- else if (strcmp(args[cur_arg], "send-proxy") == 0) {
- tcpcheck->conn_opts |= TCPCHK_OPT_SEND_PROXY;
- cur_arg++;
- }
-#ifdef USE_OPENSSL
- else if (strcmp(args[cur_arg], "ssl") == 0) {
- curproxy->options |= PR_O_TCPCHK_SSL;
- tcpcheck->conn_opts |= TCPCHK_OPT_SSL;
- cur_arg++;
- }
-#endif /* USE_OPENSSL */
- else if (strcmp(args[cur_arg], "linger") == 0) {
- tcpcheck->conn_opts |= TCPCHK_OPT_LINGER;
- cur_arg++;
- }
- /* comment for this tcpcheck line */
- else if (strcmp(args[cur_arg], "comment") == 0) {
- if (!*args[cur_arg + 1]) {
- ha_alert("parsing [%s:%d] : '%s' expects a comment string.\n",
- file, linenum, args[cur_arg]);
- err_code |= ERR_ALERT | ERR_FATAL;
- goto out;
- }
- tcpcheck->comment = strdup(args[cur_arg + 1]);
- cur_arg += 2;
- }
- else {
-#ifdef USE_OPENSSL
- ha_alert("parsing [%s:%d] : '%s %s' expects 'comment', 'port', 'send-proxy', 'ssl' or 'linger' but got '%s' as argument.\n",
-#else /* USE_OPENSSL */
- ha_alert("parsing [%s:%d] : '%s %s' expects 'comment', 'port', 'send-proxy' or 'linger' but got '%s' as argument.\n",
-#endif /* USE_OPENSSL */
- file, linenum, args[0], args[1], args[cur_arg]);
- err_code |= ERR_ALERT | ERR_FATAL;
- goto out;
- }
-
- }
-
- LIST_ADDQ(curproxy->tcpcheck_rules, &tcpcheck->list);
- }
- else if (strcmp(args[1], "send") == 0) {
- if (! *(args[2]) ) {
- /* SEND string expected */
- ha_alert("parsing [%s:%d] : '%s %s %s' expects <STRING> as argument.\n",
- file, linenum, args[0], args[1], args[2]);
- err_code |= ERR_ALERT | ERR_FATAL;
- goto out;
- } else {
- struct tcpcheck_rule *tcpcheck;
-
- tcpcheck = calloc(1, sizeof(*tcpcheck));
-
- tcpcheck->action = TCPCHK_ACT_SEND;
- tcpcheck->string_len = strlen(args[2]);
- tcpcheck->string = strdup(args[2]);
-
- /* comment for this tcpcheck line */
- if (strcmp(args[3], "comment") == 0) {
- if (!*args[4]) {
- ha_alert("parsing [%s:%d] : '%s' expects a comment string.\n",
- file, linenum, args[3]);
- err_code |= ERR_ALERT | ERR_FATAL;
- goto out;
- }
- tcpcheck->comment = strdup(args[4]);
- }
-
- LIST_ADDQ(curproxy->tcpcheck_rules, &tcpcheck->list);
- }
- }
- else if (strcmp(args[1], "send-binary") == 0) {
- if (! *(args[2]) ) {
- /* SEND binary string expected */
- ha_alert("parsing [%s:%d] : '%s %s %s' expects <BINARY STRING> as argument.\n",
- file, linenum, args[0], args[1], args[2]);
- err_code |= ERR_ALERT | ERR_FATAL;
- goto out;
- } else {
- struct tcpcheck_rule *tcpcheck;
- char *err = NULL;
-
- tcpcheck = calloc(1, sizeof(*tcpcheck));
-
- tcpcheck->action = TCPCHK_ACT_SEND;
- if (parse_binary(args[2], &tcpcheck->string, &tcpcheck->string_len, &err) == 0) {
- ha_alert("parsing [%s:%d] : '%s %s %s' expects <BINARY STRING> as argument, but %s\n",
- file, linenum, args[0], args[1], args[2], err);
- err_code |= ERR_ALERT | ERR_FATAL;
- goto out;
- }
-
- /* comment for this tcpcheck line */
- if (strcmp(args[3], "comment") == 0) {
- if (!*args[4]) {
- ha_alert("parsing [%s:%d] : '%s' expects a comment string.\n",
- file, linenum, args[3]);
- err_code |= ERR_ALERT | ERR_FATAL;
- goto out;
- }
- tcpcheck->comment = strdup(args[4]);
- }
-
- LIST_ADDQ(curproxy->tcpcheck_rules, &tcpcheck->list);
- }
- }
- else if (strcmp(args[1], "expect") == 0) {
- struct tcpcheck_rule *tcpcheck, *prev_check;
- struct tcpcheck_expect *expect;
- long min_recv = -1;
- const char *ptr_arg;
- int cur_arg;
- int inverse = 0;
-
- if (curproxy->options2 & PR_O2_EXP_TYPE) {
- ha_alert("parsing [%s:%d] : '%s %s' already specified.\n", file, linenum, args[0], args[1]);
- err_code |= ERR_ALERT | ERR_FATAL;
- goto out;
- }
-
- cur_arg = 2;
-
- /* Parse potential the minimum amount of data
- * required before proceeding with the match.
- */
- if (strcmp(args[cur_arg], "min-recv") == 0) {
- if (!*(args[cur_arg + 1])) {
- ha_alert("parsing [%s:%d] : '%s %s %s' expects an integer as an argument.\n",
- file, linenum, args[0], args[1], args[2]);
- err_code |= ERR_ALERT | ERR_FATAL;
- goto out;
- }
-
- /* Use an signed integer here because of chksize */
- min_recv = atol(args[cur_arg + 1]);
- if (min_recv < -1 || min_recv > INT_MAX) {
- ha_alert("parsing [%s:%d] : '%s %s %s' expects -1 or an integer from 0 to INT_MAX.\n",
- file, linenum, args[0], args[1], args[2]);
- err_code |= ERR_ALERT | ERR_FATAL;
- goto out;
- }
-
- cur_arg += 2;
- }
-
- /* consider exclamation marks, sole or at the beginning of a word */
- while (*(ptr_arg = args[cur_arg])) {
- while (*ptr_arg == '!') {
- inverse = !inverse;
- ptr_arg++;
- }
- if (*ptr_arg)
- break;
- cur_arg++;
- }
-
- /* now ptr_arg points to the beginning of a word past any possible
- * exclamation mark, and cur_arg is the argument which holds this word.
- */
-
- tcpcheck = calloc(1, sizeof(*tcpcheck));
- tcpcheck->action = TCPCHK_ACT_EXPECT;
- expect = &tcpcheck->expect;
- expect->inverse = inverse;
- expect->min_recv = min_recv;
-
- if (strcmp(ptr_arg, "binary") == 0) {
- char *err = NULL;
-
- if (!*(args[cur_arg + 1])) {
- ha_alert("parsing [%s:%d] : '%s %s %s' expects <binary string> as an argument.\n",
- file, linenum, args[0], args[1], ptr_arg);
- err_code |= ERR_ALERT | ERR_FATAL;
- goto out;
- }
-
- expect->type = TCPCHK_EXPECT_BINARY;
- if (parse_binary(args[cur_arg + 1], &expect->string, &expect->length, &err) == 0) {
- ha_alert("parsing [%s:%d] : '%s %s %s' expects <BINARY STRING> as argument, but %s\n",
- file, linenum, args[0], args[1], args[2], err);
- err_code |= ERR_ALERT | ERR_FATAL;
- goto out;
- }
- }
- else if (strcmp(ptr_arg, "string") == 0) {
- if (!*(args[cur_arg + 1])) {
- ha_alert("parsing [%s:%d] : '%s %s %s' expects <string> as an argument.\n",
- file, linenum, args[0], args[1], ptr_arg);
- err_code |= ERR_ALERT | ERR_FATAL;
- goto out;
- }
-
- expect->type = TCPCHK_EXPECT_STRING;
- expect->string = strdup(args[cur_arg + 1]);
- expect->length = strlen(expect->string);
- }
- else if (strcmp(ptr_arg, "rstring") == 0 ||
- strcmp(ptr_arg, "rbinary") == 0) {
- if (!*(args[cur_arg + 1])) {
- ha_alert("parsing [%s:%d] : '%s %s %s' expects <regex> as an argument.\n",
- file, linenum, args[0], args[1], ptr_arg);
- err_code |= ERR_ALERT | ERR_FATAL;
- goto out;
- }
-
- expect->type = ((strcmp(ptr_arg, "rbinary") == 0) ? TCPCHK_EXPECT_REGEX_BINARY : TCPCHK_EXPECT_REGEX);
- error = NULL;
- if (!(expect->regex = regex_comp(args[cur_arg + 1], 1, 1, &error))) {
- ha_alert("parsing [%s:%d] : '%s %s %s' : regular expression '%s': %s.\n",
- file, linenum, args[0], args[1], ptr_arg, args[cur_arg + 1], error);
- free(error);
- err_code |= ERR_ALERT | ERR_FATAL;
- goto out;
- }
- }
- else {
- ha_alert("parsing [%s:%d] : '%s %s' only supports [!] 'binary', 'string', 'rstring', 'rbinary', found '%s'.\n",
- file, linenum, args[0], args[1], ptr_arg);
- err_code |= ERR_ALERT | ERR_FATAL;
- goto out;
- }
-
- /* tcpcheck comment */
- cur_arg += 2;
- if (strcmp(args[cur_arg], "comment") == 0) {
- if (!*args[cur_arg + 1]) {
- ha_alert("parsing [%s:%d] : '%s' expects a comment string.\n",
- file, linenum, args[cur_arg + 1]);
- err_code |= ERR_ALERT | ERR_FATAL;
- goto out;
- }
- tcpcheck->comment = strdup(args[cur_arg + 1]);
- rc = find_and_check_backreferences(tcpcheck->comment, &error);
- if (rc > 0) {
- if (!inverse) {
- ha_warning("parsing [%s:%d] : "
- "using backreference in a positive expect comment is useless.\n",
- file, linenum);
- err_code |= ERR_WARN;
- }
- expect->with_capture = 1;
- }
- else if (rc < 0) {
- ha_alert("parsing [%s:%d] : %s.\n",
- file, linenum, error);
- err_code |= ERR_ALERT | ERR_FATAL;
- goto out;
- }
- }
-
- /* All tcp-check expect points back to the first inverse expect rule
- * in a chain of one or more expect rule, potentially itself.
- */
- tcpcheck->expect.head = tcpcheck;
- list_for_each_entry_rev(prev_check, curproxy->tcpcheck_rules, list) {
- if (prev_check->action == TCPCHK_ACT_EXPECT) {
- if (prev_check->expect.inverse)
- tcpcheck->expect.head = prev_check;
- continue;
- }
- if (prev_check->action != TCPCHK_ACT_COMMENT)
- break;
- }
- LIST_ADDQ(curproxy->tcpcheck_rules, &tcpcheck->list);
- }
- else {
- ha_alert("parsing [%s:%d] : '%s' only supports 'comment', 'connect', 'send' or 'expect'.\n", file, linenum, args[0]);
- err_code |= ERR_ALERT | ERR_FATAL;
- goto out;
- }
- }
else if (!strcmp(args[0], "monitor")) {
if (curproxy == &defproxy) {
ha_alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]);
diff --git a/src/checks.c b/src/checks.c
index 35d8f65..7700871 100644
--- a/src/checks.c
+++ b/src/checks.c
@@ -30,6 +30,7 @@
#include <netinet/tcp.h>
#include <arpa/inet.h>
+#include <common/cfgparse.h>
#include <common/chunk.h>
#include <common/compat.h>
#include <common/config.h>
@@ -3366,6 +3367,31 @@
}
}
+static void free_tcpcheck(struct tcpcheck_rule *rule, int in_pool)
+{
+ if (!rule)
+ return;
+
+ free(rule->comment);
+ free(rule->string);
+ switch (rule->expect.type) {
+ case TCPCHK_EXPECT_STRING:
+ case TCPCHK_EXPECT_BINARY:
+ free(rule->expect.string);
+ break;
+ case TCPCHK_EXPECT_REGEX:
+ case TCPCHK_EXPECT_REGEX_BINARY:
+ regex_free(rule->expect.regex);
+ break;
+ case TCPCHK_EXPECT_UNDEF:
+ break;
+ }
+ if (in_pool)
+ pool_free(pool_head_tcpcheck_rule, rule);
+ else
+ free(rule);
+}
+
void email_alert_free(struct email_alert *alert)
{
struct tcpcheck_rule *rule, *back;
@@ -3375,20 +3401,7 @@
list_for_each_entry_safe(rule, back, &alert->tcpcheck_rules, list) {
LIST_DEL(&rule->list);
- free(rule->comment);
- switch (rule->expect.type) {
- case TCPCHK_EXPECT_STRING:
- case TCPCHK_EXPECT_BINARY:
- free(rule->expect.string);
- break;
- case TCPCHK_EXPECT_REGEX:
- case TCPCHK_EXPECT_REGEX_BINARY:
- regex_free(rule->expect.regex);
- break;
- case TCPCHK_EXPECT_UNDEF:
- break;
- }
- pool_free(pool_head_tcpcheck_rule, rule);
+ free_tcpcheck(rule, 1);
}
pool_free(pool_head_email_alert, alert);
}
@@ -3863,6 +3876,21 @@
return ret;
}
+static void deinit_proxy_tcpcheck(struct proxy *px)
+{
+ struct tcpcheck_rule *chk, *back;
+
+ if (!px->tcpcheck_rules)
+ return;
+
+ list_for_each_entry_safe(chk, back, px->tcpcheck_rules, list) {
+ LIST_DEL(&chk->list);
+ free_tcpcheck(chk, 0);
+ }
+ free(px->tcpcheck_rules);
+ px->tcpcheck_rules = NULL;
+}
+
static void deinit_srv_check(struct server *srv)
{
if (srv->do_check)
@@ -3880,9 +3908,413 @@
REGISTER_POST_SERVER_CHECK(init_srv_check);
REGISTER_POST_SERVER_CHECK(init_srv_agent_check);
+REGISTER_PROXY_DEINIT(deinit_proxy_tcpcheck);
REGISTER_SERVER_DEINIT(deinit_srv_check);
REGISTER_SERVER_DEINIT(deinit_srv_agent_check);
+static struct tcpcheck_rule *parse_tcpcheck_connect(char **args, int cur_arg, struct proxy *px, struct list *rules,
+ char **errmsg)
+{
+ struct tcpcheck_rule *chk = NULL;
+ char *comment = NULL;
+ unsigned short conn_opts = 0;
+ long port = 0;
+
+ list_for_each_entry(chk, rules, list) {
+ if (chk->action != TCPCHK_ACT_COMMENT)
+ break;
+ }
+ if (&chk->list != rules && chk->action != TCPCHK_ACT_CONNECT) {
+ memprintf(errmsg, "first step MUST also be a 'connect' when there is a 'connect' step in the tcp-check ruleset");
+ goto error;
+ }
+
+ cur_arg++;
+ while (*(args[cur_arg])) {
+ if (strcmp(args[cur_arg], "port") == 0) {
+ if (!*(args[cur_arg+1])) {
+ memprintf(errmsg, "'%s' expects a port number as argument.", args[cur_arg]);
+ goto error;
+ }
+ cur_arg++;
+ port = atol(args[cur_arg]);
+ if (port > 65535 || port < 1) {
+ memprintf(errmsg, "expects a valid TCP port (from range 1 to 65535), got %s.", args[cur_arg]);
+ goto error;
+ }
+ }
+ else if (strcmp(args[cur_arg], "comment") == 0) {
+ if (!*(args[cur_arg+1])) {
+ memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
+ goto error;
+ }
+ cur_arg++;
+ free(comment);
+ comment = strdup(args[cur_arg]);
+ if (!comment) {
+ memprintf(errmsg, "out of memory");
+ goto error;
+ }
+ }
+ else if (strcmp(args[cur_arg], "send-proxy") == 0)
+ conn_opts |= TCPCHK_OPT_SEND_PROXY;
+ else if (strcmp(args[cur_arg], "linger") == 0)
+ conn_opts |= TCPCHK_OPT_LINGER;
+#ifdef USE_OPENSSL
+ else if (strcmp(args[cur_arg], "ssl") == 0) {
+ px->options |= PR_O_TCPCHK_SSL;
+ conn_opts |= TCPCHK_OPT_SSL;
+ }
+#endif /* USE_OPENSSL */
+
+ else {
+ memprintf(errmsg, "expects 'comment', 'port', 'send-proxy'"
+#ifdef USE_OPENSSL
+ ", 'ssl'"
+#endif /* USE_OPENSSL */
+ " or 'linger' but got '%s' as argument.",
+ args[cur_arg]);
+ goto error;
+ }
+ cur_arg++;
+ }
+
+ chk = calloc(1, sizeof(*chk));
+ if (!chk) {
+ memprintf(errmsg, "out of memory");
+ goto error;
+ }
+ chk->action = TCPCHK_ACT_CONNECT;
+ chk->port = port;
+ chk->conn_opts = conn_opts;
+ chk->comment = comment;
+ return chk;
+
+ error:
+ free(comment);
+ return NULL;
+}
+
+static struct tcpcheck_rule *parse_tcpcheck_send(char **args, int cur_arg, struct list *rules, char **errmsg)
+{
+ struct tcpcheck_rule *chk = NULL;
+ char *str = NULL, *comment = NULL;
+ int len, is_binary;
+
+ is_binary = (strcmp(args[cur_arg], "send-binary") == 0);
+ if (!*(args[cur_arg+1])) {
+ memprintf(errmsg, "'%s' expects a %s as argument", (is_binary ? "binary string": "string"), args[cur_arg]);
+ goto error;
+ }
+
+ if (is_binary) {
+ if (parse_binary(args[cur_arg+1], &str, &len, errmsg) == 0) {
+ memprintf(errmsg, "'%s' invalid binary string (%s).\n", args[cur_arg], *errmsg);
+ goto error;
+ }
+ }
+ else {
+ str = strdup(args[cur_arg+1]);
+ len = strlen(args[cur_arg+1]);
+ if (!str) {
+ memprintf(errmsg, "out of memory");
+ goto error;
+ }
+ }
+ cur_arg++;
+
+ if (strcmp(args[cur_arg], "comment") == 0) {
+ if (!*(args[cur_arg+1])) {
+ memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
+ goto error;
+ }
+ cur_arg++;
+ comment = strdup(args[cur_arg]);
+ if (!comment) {
+ memprintf(errmsg, "out of memory");
+ goto error;
+ }
+ }
+
+ chk = calloc(1, sizeof(*chk));
+ if (!chk) {
+ memprintf(errmsg, "out of memory");
+ goto error;
+ }
+ chk->action = TCPCHK_ACT_SEND;
+ chk->string = str;
+ chk->string_len = len;
+ chk->comment = comment;
+ return chk;
+
+ error:
+ free(str);
+ free(comment);
+ return NULL;
+}
+
+static struct tcpcheck_rule *parse_tcpcheck_comment(char **args, int cur_arg, struct list *rules, char **errmsg)
+{
+ struct tcpcheck_rule *chk = NULL;
+ char *comment = NULL;
+
+ if (!*(args[cur_arg+1])) {
+ memprintf(errmsg, "expects a string as argument");
+ goto error;
+ }
+ cur_arg++;
+ comment = strdup(args[cur_arg]);
+ if (!comment) {
+ memprintf(errmsg, "out of memory");
+ goto error;
+ }
+
+ chk = calloc(1, sizeof(*chk));
+ if (!chk) {
+ memprintf(errmsg, "out of memory");
+ goto error;
+ }
+ chk->action = TCPCHK_ACT_COMMENT;
+ chk->comment = comment;
+ return chk;
+
+ error:
+ free(comment);
+ return NULL;
+}
+
+static struct tcpcheck_rule *parse_tcpcheck_expect(char **args, int cur_arg, struct list *rules, char **errmsg)
+{
+ struct tcpcheck_rule *prev_check, *chk = NULL;
+ char *str = NULL, *comment = NULL, *pattern = NULL;
+ enum tcpcheck_expect_type type = TCPCHK_EXPECT_UNDEF;
+ long min_recv = -1;
+ int inverse = 0, with_capture = 0;
+
+ if (!*(args[cur_arg+1]) || !*(args[cur_arg+2])) {
+ memprintf(errmsg, "expects a pattern (type+string) as arguments");
+ goto error;
+ }
+
+ cur_arg++;
+ while (*(args[cur_arg])) {
+ int in_pattern = 0;
+
+ rescan:
+ if (strcmp(args[cur_arg], "min-recv") == 0) {
+ if (in_pattern) {
+ memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
+ goto error;
+ }
+ if (!*(args[cur_arg+1])) {
+ memprintf(errmsg, "'%s' expects a integer as argument", args[cur_arg]);
+ goto error;
+ }
+ /* Use an signed integer here because of chksize */
+ cur_arg++;
+ min_recv = atol(args[cur_arg]);
+ if (min_recv < -1 || min_recv > INT_MAX) {
+ memprintf(errmsg, "'%s' expects -1 or an integer from 0 to INT_MAX" , args[cur_arg-1]);
+ goto error;
+ }
+ }
+ else if (*(args[cur_arg]) == '!') {
+ in_pattern = 1;
+ while (*(args[cur_arg]) == '!') {
+ inverse = !inverse;
+ args[cur_arg]++;
+ }
+ if (!*(args[cur_arg]))
+ cur_arg++;
+ goto rescan;
+ }
+ else if (strcmp(args[cur_arg], "string") == 0 || strcmp(args[cur_arg], "binary") == 0 ||
+ strcmp(args[cur_arg], "rstring") == 0 || strcmp(args[cur_arg], "rbinary") == 0) {
+ if (type != TCPCHK_EXPECT_UNDEF) {
+ memprintf(errmsg, "only on pattern expected");
+ goto error;
+ }
+ type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_STRING :
+ ((*(args[cur_arg]) == 'b') ? TCPCHK_EXPECT_BINARY :
+ ((*(args[cur_arg]+1) == 's') ? TCPCHK_EXPECT_REGEX : TCPCHK_EXPECT_REGEX_BINARY)));
+
+ if (!*(args[cur_arg+1])) {
+ memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
+ goto error;
+ }
+ cur_arg++;
+ pattern = args[cur_arg];
+ }
+ else if (strcmp(args[cur_arg], "comment") == 0) {
+ if (in_pattern) {
+ memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
+ goto error;
+ }
+ if (!*(args[cur_arg+1])) {
+ memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
+ goto error;
+ }
+ cur_arg++;
+ free(comment);
+ comment = strdup(args[cur_arg]);
+ if (!comment) {
+ memprintf(errmsg, "out of memory");
+ goto error;
+ }
+ }
+ else {
+ memprintf(errmsg, "'only supports min-recv, '[!]binary', '[!]string', '[!]rstring', '[!]rbinary'"
+ " or comment but got '%s' as argument.", args[cur_arg]);
+ goto error;
+ }
+
+ cur_arg++;
+ }
+
+ if (comment) {
+ char *p = comment;
+
+ while (*p) {
+ if (*p == '\\') {
+ p++;
+ if (!*p || !isdigit((unsigned char)*p) ||
+ (*p == 'x' && (!*(p+1) || !*(p+2) || !ishex(*(p+1)) || !ishex(*(p+2))))) {
+ memprintf(errmsg, "invalid backreference in 'comment' argument");
+ goto error;
+ }
+ with_capture = 1;
+ }
+ p++;
+ }
+ if (with_capture && !inverse)
+ memprintf(errmsg, "using backreference in a positive expect comment is useless");
+ }
+
+ chk = calloc(1, sizeof(*chk));
+ if (!chk) {
+ memprintf(errmsg, "out of memory");
+ goto error;
+ }
+ chk->action = TCPCHK_ACT_EXPECT;
+ chk->comment = comment;
+ chk->expect.type = type;
+ chk->expect.min_recv = min_recv;
+ chk->expect.inverse = inverse;
+ chk->expect.with_capture = with_capture;
+
+ switch (chk->expect.type) {
+ case TCPCHK_EXPECT_STRING:
+ chk->expect.string = strdup(pattern);
+ chk->expect.length = strlen(pattern);
+ if (!chk->expect.string) {
+ memprintf(errmsg, "out of memory");
+ goto error;
+ }
+ break;
+ case TCPCHK_EXPECT_BINARY:
+ if (parse_binary(pattern, &chk->expect.string, &chk->expect.length, errmsg) == 0) {
+ memprintf(errmsg, "invalid binary string (%s)", *errmsg);
+ goto error;
+ }
+ case TCPCHK_EXPECT_REGEX:
+ case TCPCHK_EXPECT_REGEX_BINARY:
+ chk->expect.regex = regex_comp(pattern, 1, with_capture, errmsg);
+ if (!chk->expect.regex)
+ goto error;
+ break;
+ case TCPCHK_EXPECT_UNDEF:
+ free(chk);
+ memprintf(errmsg, "pattern not found");
+ goto error;
+ }
+
+ /* All tcp-check expect points back to the first inverse expect rule in
+ * a chain of one or more expect rule, potentially itself.
+ */
+ chk->expect.head = chk;
+ list_for_each_entry_rev(prev_check, rules, list) {
+ if (prev_check->action == TCPCHK_ACT_EXPECT) {
+ if (prev_check->expect.inverse)
+ chk->expect.head = prev_check;
+ continue;
+ }
+ if (prev_check->action != TCPCHK_ACT_COMMENT)
+ break;
+ }
+ return chk;
+
+ error:
+ free(chk);
+ free(str);
+ free(comment);
+ return NULL;
+}
+
+/* Parses the "tcp-check" proxy keyword */
+static int proxy_parse_tcpcheck(char **args, int section, struct proxy *curpx,
+ struct proxy *defpx, const char *file, int line,
+ char **errmsg)
+{
+ struct list *rules = curpx->tcpcheck_rules;
+ struct tcpcheck_rule *chk = NULL;
+ int cur_arg, ret = 0;
+
+ if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
+ ret = 1;
+
+ if (curpx == defpx) {
+ memprintf(errmsg, "'%s' not allowed in 'defaults' section.", args[0]);
+ goto error;
+ }
+
+ if (!rules) {
+ rules = calloc(1, sizeof(*rules));
+ if (!rules) {
+ memprintf(errmsg, "%s : out of memory.", args[0]);
+ goto error;
+ }
+ LIST_INIT(rules);
+ curpx->tcpcheck_rules = rules;
+ }
+
+ cur_arg = 1;
+ if (strcmp(args[cur_arg], "connect") == 0)
+ chk = parse_tcpcheck_connect(args, cur_arg, curpx, rules, errmsg);
+ else if (strcmp(args[cur_arg], "send") == 0 || strcmp(args[cur_arg], "send-binary") == 0)
+ chk = parse_tcpcheck_send(args, cur_arg, rules, errmsg);
+ else if (strcmp(args[cur_arg], "expect") == 0)
+ chk = parse_tcpcheck_expect(args, cur_arg, rules, errmsg);
+ else if (strcmp(args[cur_arg], "comment") == 0)
+ chk = parse_tcpcheck_comment(args, cur_arg, rules, errmsg);
+ else {
+ memprintf(errmsg, "'%s %s' only supports 'comment', 'connect', 'send', 'send-binary' or 'expect'.",
+ args[0], args[1]);
+ goto error;
+ }
+
+ if (!chk) {
+ memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
+ goto error;
+ }
+ ret = (*errmsg != NULL); /* Handle warning */
+
+ /* No error: add the tcp-check rule in the list */
+ LIST_ADDQ(rules, &chk->list);
+ return ret;
+
+ error:
+ if (rules)
+ deinit_proxy_tcpcheck(curpx);
+ return -1;
+}
+
+static struct cfg_kw_list cfg_kws = {ILH, {
+ { CFG_LISTEN, "tcp-check", proxy_parse_tcpcheck },
+ { 0, NULL, NULL },
+}};
+
+INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws);
+
/*
* Local variables:
* c-indent-level: 8