From: Christopher Faulet Date: Wed, 25 Mar 2020 17:20:15 +0000 (+0100) Subject: MEDIUM: proxy/checks: Register a keyword to parse tcp-check rules X-Git-Tag: v2.2-dev7~164 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=fd6c2291bb7e498c2b49bfaa3032e64bbdd78efb;p=thirdparty%2Fhaproxy.git 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 e922c09baf..00b1423991 100644 --- a/src/cfgparse-listen.c +++ b/src/cfgparse-listen.c @@ -162,55 +162,6 @@ int warnif_misplaced_tcp_conn(struct proxy *proxy, const char *file, int line, c 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 @@ stats_error_parsing: 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 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 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 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 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 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 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 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 35d8f655f4..77008713cf 100644 --- a/src/checks.c +++ b/src/checks.c @@ -30,6 +30,7 @@ #include #include +#include #include #include #include @@ -3366,6 +3367,31 @@ void free_check(struct check *check) } } +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 @@ void email_alert_free(struct email_alert *alert) 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 @@ static int init_srv_agent_check(struct server *srv) 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 @@ static void deinit_srv_agent_check(struct server *srv) 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 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