From 5321c427225732802c6b25a37e6bc10be5b9dce8 Mon Sep 17 00:00:00 2001 From: Willy Tarreau Date: Thu, 28 Jan 2010 20:35:13 +0100 Subject: [PATCH] [MEDIUM] http: add support for conditional request filter execution All the req* rules except the reqadd rules can now be specified with an if/unless condition. If a condition is specified and does not match, the filter is ignored. This is particularly useful with reqidel, reqirep and reqtarpit. --- doc/configuration.txt | 72 ++++++++++++++++++++++++++++--------------- src/cfgparse.c | 50 +++++++++++++++++++++--------- 2 files changed, 82 insertions(+), 40 deletions(-) diff --git a/doc/configuration.txt b/doc/configuration.txt index b77156d581..e3d38f13bf 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -3597,8 +3597,8 @@ reqadd See also: "rspadd" and section 6 about HTTP header manipulation -reqallow -reqiallow (ignore case) +reqallow [{if | unless} ] +reqiallow [{if | unless} ] (ignore case) Definitely allow an HTTP request if a line matches a regular expression May be used in sections : defaults | frontend | listen | backend no | yes | yes | yes @@ -3611,6 +3611,9 @@ reqiallow (ignore case) "reqallow" keyword strictly matches case while "reqiallow" ignores case. + is an optional matching condition built from ACLs. It makes it + possible to ignore this rule when other conditions are not met. + A request containing any line which matches extended regular expression will mark the request as allowed, even if any later test would result in a deny. The test applies both to the request line and to request @@ -3625,12 +3628,12 @@ reqiallow (ignore case) reqiallow ^Host:\ www\. reqideny ^Host:\ .*\.local - See also: "reqdeny", "acl", "block" and section 6 about HTTP header - manipulation + See also: "reqdeny", "block", section 6 about HTTP header manipulation, and + section 7 about ACLs. -reqdel -reqidel (ignore case) +reqdel [{if | unless} ] +reqidel [{if | unless} ] (ignore case) Delete all headers matching a regular expression in an HTTP request May be used in sections : defaults | frontend | listen | backend no | yes | yes | yes @@ -3642,6 +3645,9 @@ reqidel (ignore case) ('\'). The pattern applies to a full line at a time. The "reqdel" keyword strictly matches case while "reqidel" ignores case. + is an optional matching condition built from ACLs. It makes it + possible to ignore this rule when other conditions are not met. + Any header line matching extended regular expression in the request will be completely deleted. Most common use of this is to remove unwanted and/or dangerous headers or cookies from a request before passing it to the @@ -3656,12 +3662,12 @@ reqidel (ignore case) reqidel ^X-Forwarded-For:.* reqidel ^Cookie:.*SERVER= - See also: "reqadd", "reqrep", "rspdel" and section 6 about HTTP header - manipulation + See also: "reqadd", "reqrep", "rspdel", section 6 about HTTP header + manipulation, and section 7 about ACLs. -reqdeny -reqideny (ignore case) +reqdeny [{if | unless} ] +reqideny [{if | unless} ] (ignore case) Deny an HTTP request if a line matches a regular expression May be used in sections : defaults | frontend | listen | backend no | yes | yes | yes @@ -3674,6 +3680,9 @@ reqideny (ignore case) "reqdeny" keyword strictly matches case while "reqideny" ignores case. + is an optional matching condition built from ACLs. It makes it + possible to ignore this rule when other conditions are not met. + A request containing any line which matches extended regular expression will mark the request as denied, even if any later test would result in an allow. The test applies both to the request line and to request @@ -3692,12 +3701,12 @@ reqideny (ignore case) reqideny ^Host:\ .*\.local reqiallow ^Host:\ www\. - See also: "reqallow", "rspdeny", "acl", "block" and section 6 about HTTP - header manipulation + See also: "reqallow", "rspdeny", "block", section 6 about HTTP header + manipulation, and section 7 about ACLs. -reqpass -reqipass (ignore case) +reqpass [{if | unless} ] +reqipass [{if | unless} ] (ignore case) Ignore any HTTP request line matching a regular expression in next rules May be used in sections : defaults | frontend | listen | backend no | yes | yes | yes @@ -3710,6 +3719,9 @@ reqipass (ignore case) "reqpass" keyword strictly matches case while "reqipass" ignores case. + is an optional matching condition built from ACLs. It makes it + possible to ignore this rule when other conditions are not met. + A request containing any line which matches extended regular expression will skip next rules, without assigning any deny or allow verdict. The test applies both to the request line and to request headers. Keep in @@ -3724,12 +3736,12 @@ reqipass (ignore case) reqideny ^Host:\ .*\.local reqiallow ^Host:\ www\. - See also: "reqallow", "reqdeny", "acl", "block" and section 6 about HTTP - header manipulation + See also: "reqallow", "reqdeny", "block", section 6 about HTTP header + manipulation, and section 7 about ACLs. -reqrep -reqirep (ignore case) +reqrep [{if | unless} ] +reqirep [{if | unless} ] (ignore case) Replace a regular expression with a string in an HTTP request line May be used in sections : defaults | frontend | listen | backend no | yes | yes | yes @@ -3747,6 +3759,9 @@ reqirep (ignore case) being a single digit between 0 and 9. Please refer to section 6 about HTTP header manipulation for more information. + is an optional matching condition built from ACLs. It makes it + possible to ignore this rule when other conditions are not met. + Any line matching extended regular expression in the request (both the request line and header lines) will be completely replaced with . Most common use of this is to rewrite URLs or domain names in "Host" headers. @@ -3763,12 +3778,12 @@ reqirep (ignore case) # replace "www.mydomain.com" with "www" in the host name. reqirep ^Host:\ www.mydomain.com Host:\ www - See also: "reqadd", "reqdel", "rsprep" and section 6 about HTTP header - manipulation + See also: "reqadd", "reqdel", "rsprep", section 6 about HTTP header + manipulation, and section 7 about ACLs. -reqtarpit -reqitarpit (ignore case) +reqtarpit [{if | unless} ] +reqitarpit [{if | unless} ] (ignore case) Tarpit an HTTP request containing a line matching a regular expression May be used in sections : defaults | frontend | listen | backend no | yes | yes | yes @@ -3781,6 +3796,9 @@ reqitarpit (ignore case) "reqtarpit" keyword strictly matches case while "reqitarpit" ignores case. + is an optional matching condition built from ACLs. It makes it + possible to ignore this rule when other conditions are not met. + A request containing any line which matches extended regular expression will be tarpitted, which means that it will connect to nowhere, will be kept open for a pre-defined time, then will return an HTTP error 500 so @@ -3795,14 +3813,18 @@ reqitarpit (ignore case) come. Depending on the environment and attack, it may be particularly efficient at reducing the load on the network and firewalls. - Example : + Examples : # ignore user-agents reporting any flavour of "Mozilla" or "MSIE", but # block all others. reqipass ^User-Agent:\.*(Mozilla|MSIE) reqitarpit ^User-Agent: - See also: "reqallow", "reqdeny", "reqpass", and section 6 about HTTP header - manipulation + # block bad guys + acl badguys src 10.1.0.3 172.16.13.20/28 + reqitarpit . if badguys + + See also: "reqallow", "reqdeny", "reqpass", section 6 about HTTP header + manipulation, and section 7 about ACLs. retries diff --git a/src/cfgparse.c b/src/cfgparse.c index 78fb95b01f..4e724b2e05 100644 --- a/src/cfgparse.c +++ b/src/cfgparse.c @@ -912,6 +912,7 @@ static int create_cond_regex_rule(const char *file, int line, regex_t *preg = NULL; const char *err; int err_code = 0; + struct acl_cond *cond = NULL; if (px == &defproxy) { Alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, line, cmd); @@ -928,6 +929,25 @@ static int create_cond_regex_rule(const char *file, int line, if (warnifnotcap(px, PR_CAP_RS, file, line, cmd, NULL)) err_code |= ERR_WARN; + if (cond_start && + (strcmp(*cond_start, "if") == 0 || strcmp(*cond_start, "unless") == 0)) { + if ((cond = build_acl_cond(file, line, px, cond_start)) == NULL) { + Alert("parsing [%s:%d] : error detected while parsing a '%s' condition.\n", + file, line, cmd); + err_code |= ERR_ALERT | ERR_FATAL; + goto err; + } + } + else if (cond_start && **cond_start) { + Alert("parsing [%s:%d] : '%s' : Expecting nothing, 'if', or 'unless', got '%s'.\n", + file, line, cmd, *cond_start); + err_code |= ERR_ALERT | ERR_FATAL; + goto err; + } + + if (dir == ACL_DIR_REQ) + err_code |= warnif_cond_requires_resp(cond, file, line); + preg = calloc(1, sizeof(regex_t)); if (!preg) { Alert("parsing [%s:%d] : '%s' : not enough memory to build regex.\n", file, line, cmd); @@ -942,7 +962,7 @@ static int create_cond_regex_rule(const char *file, int line, } err = chain_regex((dir == ACL_DIR_REQ) ? &px->req_exp : &px->rsp_exp, - preg, action, repl ? strdup(repl) : NULL, NULL); + preg, action, repl ? strdup(repl) : NULL, cond); if (repl && err) { Alert("parsing [%s:%d] : '%s' : invalid character or unterminated sequence in replacement string near '%c'.\n", file, line, cmd, *err); @@ -3599,56 +3619,56 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm) err_code |= create_cond_regex_rule(file, linenum, curproxy, ACL_DIR_REQ, ACT_REPLACE, 0, - args[0], args[1], args[2], NULL); + args[0], args[1], args[2], (const char **)args+3); if (err_code & ERR_FATAL) goto out; } else if (!strcmp(args[0], "reqdel")) { /* delete request header from a regex */ err_code |= create_cond_regex_rule(file, linenum, curproxy, ACL_DIR_REQ, ACT_REMOVE, 0, - args[0], args[1], NULL, NULL); + args[0], args[1], NULL, (const char **)args+2); if (err_code & ERR_FATAL) goto out; } else if (!strcmp(args[0], "reqdeny")) { /* deny a request if a header matches this regex */ err_code |= create_cond_regex_rule(file, linenum, curproxy, ACL_DIR_REQ, ACT_DENY, 0, - args[0], args[1], NULL, NULL); + args[0], args[1], NULL, (const char **)args+2); if (err_code & ERR_FATAL) goto out; } else if (!strcmp(args[0], "reqpass")) { /* pass this header without allowing or denying the request */ err_code |= create_cond_regex_rule(file, linenum, curproxy, ACL_DIR_REQ, ACT_PASS, 0, - args[0], args[1], NULL, NULL); + args[0], args[1], NULL, (const char **)args+2); if (err_code & ERR_FATAL) goto out; } else if (!strcmp(args[0], "reqallow")) { /* allow a request if a header matches this regex */ err_code |= create_cond_regex_rule(file, linenum, curproxy, ACL_DIR_REQ, ACT_ALLOW, 0, - args[0], args[1], NULL, NULL); + args[0], args[1], NULL, (const char **)args+2); if (err_code & ERR_FATAL) goto out; } else if (!strcmp(args[0], "reqtarpit")) { /* tarpit a request if a header matches this regex */ err_code |= create_cond_regex_rule(file, linenum, curproxy, ACL_DIR_REQ, ACT_TARPIT, 0, - args[0], args[1], NULL, NULL); + args[0], args[1], NULL, (const char **)args+2); if (err_code & ERR_FATAL) goto out; } else if (!strcmp(args[0], "reqsetbe")) { /* switch the backend from a regex, respecting case */ err_code |= create_cond_regex_rule(file, linenum, curproxy, ACL_DIR_REQ, ACT_SETBE, 0, - args[0], args[1], args[2], NULL); + args[0], args[1], args[2], (const char **)args+3); if (err_code & ERR_FATAL) goto out; } else if (!strcmp(args[0], "reqisetbe")) { /* switch the backend from a regex, ignoring case */ err_code |= create_cond_regex_rule(file, linenum, curproxy, ACL_DIR_REQ, ACT_SETBE, REG_ICASE, - args[0], args[1], args[2], NULL); + args[0], args[1], args[2], (const char **)args+3); if (err_code & ERR_FATAL) goto out; } @@ -3662,42 +3682,42 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm) err_code |= create_cond_regex_rule(file, linenum, curproxy, ACL_DIR_REQ, ACT_REPLACE, REG_ICASE, - args[0], args[1], args[2], NULL); + args[0], args[1], args[2], (const char **)args+3); if (err_code & ERR_FATAL) goto out; } else if (!strcmp(args[0], "reqidel")) { /* delete request header from a regex ignoring case */ err_code |= create_cond_regex_rule(file, linenum, curproxy, ACL_DIR_REQ, ACT_REMOVE, REG_ICASE, - args[0], args[1], NULL, NULL); + args[0], args[1], NULL, (const char **)args+2); if (err_code & ERR_FATAL) goto out; } else if (!strcmp(args[0], "reqideny")) { /* deny a request if a header matches this regex ignoring case */ err_code |= create_cond_regex_rule(file, linenum, curproxy, ACL_DIR_REQ, ACT_DENY, REG_ICASE, - args[0], args[1], NULL, NULL); + args[0], args[1], NULL, (const char **)args+2); if (err_code & ERR_FATAL) goto out; } else if (!strcmp(args[0], "reqipass")) { /* pass this header without allowing or denying the request */ err_code |= create_cond_regex_rule(file, linenum, curproxy, ACL_DIR_REQ, ACT_PASS, REG_ICASE, - args[0], args[1], NULL, NULL); + args[0], args[1], NULL, (const char **)args+2); if (err_code & ERR_FATAL) goto out; } else if (!strcmp(args[0], "reqiallow")) { /* allow a request if a header matches this regex ignoring case */ err_code |= create_cond_regex_rule(file, linenum, curproxy, ACL_DIR_REQ, ACT_ALLOW, REG_ICASE, - args[0], args[1], NULL, NULL); + args[0], args[1], NULL, (const char **)args+2); if (err_code & ERR_FATAL) goto out; } else if (!strcmp(args[0], "reqitarpit")) { /* tarpit a request if a header matches this regex ignoring case */ err_code |= create_cond_regex_rule(file, linenum, curproxy, ACL_DIR_REQ, ACT_TARPIT, REG_ICASE, - args[0], args[1], NULL, NULL); + args[0], args[1], NULL, (const char **)args+2); if (err_code & ERR_FATAL) goto out; }