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.
This commit is contained in:
Christopher Faulet 2020-03-25 18:20:15 +01:00
parent 1d22d7ec0e
commit fd6c2291bb
2 changed files with 446 additions and 415 deletions

View File

@ -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 <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]);

View File

@ -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 @@ 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 <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