mirror of
https://git.haproxy.org/git/haproxy.git/
synced 2025-08-05 22:56:57 +02:00
log format expressions are broadly used within the code: once they are parsed from input string, they are converted to a linked list of logformat nodes. We're starting to face some limitations because we're simply storing the converted expression as a generic logformat_node list. The first issue we're facing is that storing logformat expressions that way doesn't allow us to add metadata alongside the list, which is part of the prerequites for implementing log-profiles. Another issue with storing logformat expressions as generic lists of logformat_node elements is that it's starting to become really hard to tell when we rely on logformat expressions or not in the code given that there isn't always a comment near the list declaration or manipulation to indicate that it's relying on logformat expressions under the hood, so this adds some complexity for code maintenance. This patch looks quite impressive due to changes in a lot of header and source files (since logformat expressions are broadly used), but it does a simple thing: it defines the lf_expr structure which itself holds a generic list of logformat nodes, and then declares some helpers to manipulate lf_expr elements and fixes the code so that we now exclusively manipulate logformat_node lists as lf_expr elements outside of log.c. For now, lf_expr struct only contains the list of logformat nodes (no additional metadata), but now that we have dedicated type and helpers, doing so in the future won't be problematic at all and won't require extensive code changes.
2483 lines
76 KiB
C
2483 lines
76 KiB
C
/*
|
|
* HTTP actions
|
|
*
|
|
* Copyright 2000-2018 Willy Tarreau <w@1wt.eu>
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version
|
|
* 2 of the License, or (at your option) any later version.
|
|
*
|
|
*/
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <ctype.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
|
|
#include <haproxy/acl.h>
|
|
#include <haproxy/action.h>
|
|
#include <haproxy/api.h>
|
|
#include <haproxy/arg.h>
|
|
#include <haproxy/capture-t.h>
|
|
#include <haproxy/cfgparse.h>
|
|
#include <haproxy/chunk.h>
|
|
#include <haproxy/global.h>
|
|
#include <haproxy/http.h>
|
|
#include <haproxy/http_ana.h>
|
|
#include <haproxy/http_htx.h>
|
|
#include <haproxy/http_rules.h>
|
|
#include <haproxy/log.h>
|
|
#include <haproxy/pattern.h>
|
|
#include <haproxy/pool.h>
|
|
#include <haproxy/regex.h>
|
|
#include <haproxy/sample.h>
|
|
#include <haproxy/sc_strm.h>
|
|
#include <haproxy/stconn.h>
|
|
#include <haproxy/tools.h>
|
|
#include <haproxy/uri_auth-t.h>
|
|
#include <haproxy/uri_normalizer.h>
|
|
#include <haproxy/version.h>
|
|
|
|
|
|
/* Release memory allocated by most of HTTP actions. Concretly, it releases
|
|
* <arg.http>.
|
|
*/
|
|
static void release_http_action(struct act_rule *rule)
|
|
{
|
|
istfree(&rule->arg.http.str);
|
|
if (rule->arg.http.re)
|
|
regex_free(rule->arg.http.re);
|
|
lf_expr_deinit(&rule->arg.http.fmt);
|
|
}
|
|
|
|
/* Release memory allocated by HTTP actions relying on an http reply. Concretly,
|
|
* it releases <.arg.http_reply>
|
|
*/
|
|
static void release_act_http_reply(struct act_rule *rule)
|
|
{
|
|
release_http_reply(rule->arg.http_reply);
|
|
rule->arg.http_reply = NULL;
|
|
}
|
|
|
|
|
|
/* Check function for HTTP actions relying on an http reply. The function
|
|
* returns 1 in success case, otherwise, it returns 0 and err is filled.
|
|
*/
|
|
static int check_act_http_reply(struct act_rule *rule, struct proxy *px, char **err)
|
|
{
|
|
struct http_reply *reply = rule->arg.http_reply;
|
|
|
|
if (!http_check_http_reply(reply, px, err)) {
|
|
release_act_http_reply(rule);
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
|
|
/* This function executes one of the set-{method,path,query,uri} actions. It
|
|
* builds a string in the trash from the specified format string. It finds
|
|
* the action to be performed in <.action>, previously filled by function
|
|
* parse_set_req_line(). The replacement action is executed by the function
|
|
* http_action_set_req_line(). On success, it returns ACT_RET_CONT. If an error
|
|
* occurs while soft rewrites are enabled, the action is canceled, but the rule
|
|
* processing continue. Otherwsize ACT_RET_ERR is returned.
|
|
*/
|
|
static enum act_return http_action_set_req_line(struct act_rule *rule, struct proxy *px,
|
|
struct session *sess, struct stream *s, int flags)
|
|
{
|
|
struct buffer *replace;
|
|
enum act_return ret = ACT_RET_CONT;
|
|
|
|
replace = alloc_trash_chunk();
|
|
if (!replace)
|
|
goto fail_alloc;
|
|
|
|
/* If we have to create a query string, prepare a '?'. */
|
|
if (rule->action == 2) // set-query
|
|
replace->area[replace->data++] = '?';
|
|
replace->data += build_logline(s, replace->area + replace->data,
|
|
replace->size - replace->data,
|
|
&rule->arg.http.fmt);
|
|
|
|
if (http_req_replace_stline(rule->action, replace->area, replace->data, px, s) == -1)
|
|
goto fail_rewrite;
|
|
|
|
leave:
|
|
free_trash_chunk(replace);
|
|
return ret;
|
|
|
|
fail_alloc:
|
|
if (!(s->flags & SF_ERR_MASK))
|
|
s->flags |= SF_ERR_RESOURCE;
|
|
ret = ACT_RET_ERR;
|
|
goto leave;
|
|
|
|
fail_rewrite:
|
|
_HA_ATOMIC_INC(&sess->fe->fe_counters.failed_rewrites);
|
|
if (s->flags & SF_BE_ASSIGNED)
|
|
_HA_ATOMIC_INC(&s->be->be_counters.failed_rewrites);
|
|
if (sess->listener && sess->listener->counters)
|
|
_HA_ATOMIC_INC(&sess->listener->counters->failed_rewrites);
|
|
if (objt_server(s->target))
|
|
_HA_ATOMIC_INC(&__objt_server(s->target)->counters.failed_rewrites);
|
|
|
|
if (!(s->txn->req.flags & HTTP_MSGF_SOFT_RW)) {
|
|
ret = ACT_RET_ERR;
|
|
if (!(s->flags & SF_ERR_MASK))
|
|
s->flags |= SF_ERR_PRXCOND;
|
|
}
|
|
goto leave;
|
|
}
|
|
|
|
/* parse an http-request action among :
|
|
* set-method
|
|
* set-path
|
|
* set-pathq
|
|
* set-query
|
|
* set-uri
|
|
*
|
|
* All of them accept a single argument of type string representing a log-format.
|
|
* The resulting rule makes use of <http.fmt> to store the log-format list head,
|
|
* and <.action> to store the action type as an int (0=method, 1=path, 2=query,
|
|
* 3=uri). It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
|
|
*/
|
|
static enum act_parse_ret parse_set_req_line(const char **args, int *orig_arg, struct proxy *px,
|
|
struct act_rule *rule, char **err)
|
|
{
|
|
int cur_arg = *orig_arg;
|
|
int cap = 0;
|
|
|
|
switch (args[0][4]) {
|
|
case 'm' :
|
|
rule->action = 0; // set-method
|
|
break;
|
|
case 'p' :
|
|
if (args[0][8] == 'q')
|
|
rule->action = 4; // set-pathq
|
|
else
|
|
rule->action = 1; // set-path
|
|
break;
|
|
case 'q' :
|
|
rule->action = 2; // set-query
|
|
break;
|
|
case 'u' :
|
|
rule->action = 3; // set-uri
|
|
break;
|
|
default:
|
|
memprintf(err, "internal error: unhandled action '%s'", args[0]);
|
|
return ACT_RET_PRS_ERR;
|
|
}
|
|
rule->action_ptr = http_action_set_req_line;
|
|
rule->release_ptr = release_http_action;
|
|
lf_expr_init(&rule->arg.http.fmt);
|
|
|
|
if (!*args[cur_arg] ||
|
|
(*args[cur_arg + 1] && strcmp(args[cur_arg + 1], "if") != 0 && strcmp(args[cur_arg + 1], "unless") != 0)) {
|
|
memprintf(err, "expects exactly 1 argument <format>");
|
|
return ACT_RET_PRS_ERR;
|
|
}
|
|
|
|
px->conf.args.ctx = ARGC_HRQ;
|
|
if (px->cap & PR_CAP_FE)
|
|
cap |= SMP_VAL_FE_HRQ_HDR;
|
|
if (px->cap & PR_CAP_BE)
|
|
cap |= SMP_VAL_BE_HRQ_HDR;
|
|
if (!parse_logformat_string(args[cur_arg], px, &rule->arg.http.fmt, LOG_OPT_HTTP, cap, err)) {
|
|
return ACT_RET_PRS_ERR;
|
|
}
|
|
|
|
(*orig_arg)++;
|
|
return ACT_RET_PRS_OK;
|
|
}
|
|
|
|
/* This function executes the http-request normalize-uri action.
|
|
* `rule->action` is expected to be a value from `enum act_normalize_uri`.
|
|
*
|
|
* On success, it returns ACT_RET_CONT. If an error
|
|
* occurs while soft rewrites are enabled, the action is canceled, but the rule
|
|
* processing continue. Otherwsize ACT_RET_ERR is returned.
|
|
*/
|
|
static enum act_return http_action_normalize_uri(struct act_rule *rule, struct proxy *px,
|
|
struct session *sess, struct stream *s, int flags)
|
|
{
|
|
enum act_return ret = ACT_RET_CONT;
|
|
struct htx *htx = htxbuf(&s->req.buf);
|
|
const struct ist uri = htx_sl_req_uri(http_get_stline(htx));
|
|
struct buffer *replace = alloc_trash_chunk();
|
|
enum uri_normalizer_err err = URI_NORMALIZER_ERR_INTERNAL_ERROR;
|
|
|
|
if (!replace)
|
|
goto fail_alloc;
|
|
|
|
switch ((enum act_normalize_uri) rule->action) {
|
|
case ACT_NORMALIZE_URI_PATH_MERGE_SLASHES: {
|
|
struct http_uri_parser parser = http_uri_parser_init(uri);
|
|
const struct ist path = http_parse_path(&parser);
|
|
struct ist newpath = ist2(replace->area, replace->size);
|
|
|
|
if (!isttest(path))
|
|
goto leave;
|
|
|
|
err = uri_normalizer_path_merge_slashes(iststop(path, '?'), &newpath);
|
|
|
|
if (err != URI_NORMALIZER_ERR_NONE)
|
|
break;
|
|
|
|
if (!http_replace_req_path(htx, newpath, 0))
|
|
goto fail_rewrite;
|
|
|
|
break;
|
|
}
|
|
case ACT_NORMALIZE_URI_PATH_STRIP_DOT: {
|
|
struct http_uri_parser parser = http_uri_parser_init(uri);
|
|
const struct ist path = http_parse_path(&parser);
|
|
struct ist newpath = ist2(replace->area, replace->size);
|
|
|
|
if (!isttest(path))
|
|
goto leave;
|
|
|
|
err = uri_normalizer_path_dot(iststop(path, '?'), &newpath);
|
|
|
|
if (err != URI_NORMALIZER_ERR_NONE)
|
|
break;
|
|
|
|
if (!http_replace_req_path(htx, newpath, 0))
|
|
goto fail_rewrite;
|
|
|
|
break;
|
|
}
|
|
case ACT_NORMALIZE_URI_PATH_STRIP_DOTDOT:
|
|
case ACT_NORMALIZE_URI_PATH_STRIP_DOTDOT_FULL: {
|
|
struct http_uri_parser parser = http_uri_parser_init(uri);
|
|
const struct ist path = http_parse_path(&parser);
|
|
struct ist newpath = ist2(replace->area, replace->size);
|
|
|
|
if (!isttest(path))
|
|
goto leave;
|
|
|
|
err = uri_normalizer_path_dotdot(iststop(path, '?'), rule->action == ACT_NORMALIZE_URI_PATH_STRIP_DOTDOT_FULL, &newpath);
|
|
|
|
if (err != URI_NORMALIZER_ERR_NONE)
|
|
break;
|
|
|
|
if (!http_replace_req_path(htx, newpath, 0))
|
|
goto fail_rewrite;
|
|
|
|
break;
|
|
}
|
|
case ACT_NORMALIZE_URI_QUERY_SORT_BY_NAME: {
|
|
struct http_uri_parser parser = http_uri_parser_init(uri);
|
|
const struct ist path = http_parse_path(&parser);
|
|
struct ist newquery = ist2(replace->area, replace->size);
|
|
|
|
if (!isttest(path))
|
|
goto leave;
|
|
|
|
err = uri_normalizer_query_sort(istfind(path, '?'), '&', &newquery);
|
|
|
|
if (err != URI_NORMALIZER_ERR_NONE)
|
|
break;
|
|
|
|
if (!http_replace_req_query(htx, newquery))
|
|
goto fail_rewrite;
|
|
|
|
break;
|
|
}
|
|
case ACT_NORMALIZE_URI_PERCENT_TO_UPPERCASE:
|
|
case ACT_NORMALIZE_URI_PERCENT_TO_UPPERCASE_STRICT: {
|
|
struct http_uri_parser parser = http_uri_parser_init(uri);
|
|
const struct ist path = http_parse_path(&parser);
|
|
struct ist newpath = ist2(replace->area, replace->size);
|
|
|
|
if (!isttest(path))
|
|
goto leave;
|
|
|
|
err = uri_normalizer_percent_upper(path, rule->action == ACT_NORMALIZE_URI_PERCENT_TO_UPPERCASE_STRICT, &newpath);
|
|
|
|
if (err != URI_NORMALIZER_ERR_NONE)
|
|
break;
|
|
|
|
if (!http_replace_req_path(htx, newpath, 1))
|
|
goto fail_rewrite;
|
|
|
|
break;
|
|
}
|
|
case ACT_NORMALIZE_URI_PERCENT_DECODE_UNRESERVED:
|
|
case ACT_NORMALIZE_URI_PERCENT_DECODE_UNRESERVED_STRICT: {
|
|
struct http_uri_parser parser = http_uri_parser_init(uri);
|
|
const struct ist path = http_parse_path(&parser);
|
|
struct ist newpath = ist2(replace->area, replace->size);
|
|
|
|
if (!isttest(path))
|
|
goto leave;
|
|
|
|
err = uri_normalizer_percent_decode_unreserved(path, rule->action == ACT_NORMALIZE_URI_PERCENT_DECODE_UNRESERVED_STRICT, &newpath);
|
|
|
|
if (err != URI_NORMALIZER_ERR_NONE)
|
|
break;
|
|
|
|
if (!http_replace_req_path(htx, newpath, 1))
|
|
goto fail_rewrite;
|
|
|
|
break;
|
|
}
|
|
case ACT_NORMALIZE_URI_FRAGMENT_STRIP: {
|
|
struct http_uri_parser parser = http_uri_parser_init(uri);
|
|
const struct ist path = http_parse_path(&parser);
|
|
struct ist newpath = ist2(replace->area, replace->size);
|
|
|
|
if (!isttest(path))
|
|
goto leave;
|
|
|
|
err = uri_normalizer_fragment_strip(path, &newpath);
|
|
|
|
if (err != URI_NORMALIZER_ERR_NONE)
|
|
break;
|
|
|
|
if (!http_replace_req_path(htx, newpath, 1))
|
|
goto fail_rewrite;
|
|
|
|
break;
|
|
}
|
|
case ACT_NORMALIZE_URI_FRAGMENT_ENCODE: {
|
|
struct http_uri_parser parser = http_uri_parser_init(uri);
|
|
const struct ist path = http_parse_path(&parser);
|
|
struct ist newpath = ist2(replace->area, replace->size);
|
|
|
|
if (!isttest(path))
|
|
goto leave;
|
|
|
|
err = uri_normalizer_fragment_encode(path, &newpath);
|
|
|
|
if (err != URI_NORMALIZER_ERR_NONE)
|
|
break;
|
|
|
|
if (!http_replace_req_path(htx, newpath, 1))
|
|
goto fail_rewrite;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
switch (err) {
|
|
case URI_NORMALIZER_ERR_NONE:
|
|
break;
|
|
case URI_NORMALIZER_ERR_INTERNAL_ERROR:
|
|
ret = ACT_RET_ERR;
|
|
break;
|
|
case URI_NORMALIZER_ERR_INVALID_INPUT:
|
|
ret = ACT_RET_INV;
|
|
break;
|
|
case URI_NORMALIZER_ERR_ALLOC:
|
|
goto fail_alloc;
|
|
}
|
|
|
|
leave:
|
|
free_trash_chunk(replace);
|
|
return ret;
|
|
|
|
fail_alloc:
|
|
if (!(s->flags & SF_ERR_MASK))
|
|
s->flags |= SF_ERR_RESOURCE;
|
|
ret = ACT_RET_ERR;
|
|
goto leave;
|
|
|
|
fail_rewrite:
|
|
_HA_ATOMIC_ADD(&sess->fe->fe_counters.failed_rewrites, 1);
|
|
if (s->flags & SF_BE_ASSIGNED)
|
|
_HA_ATOMIC_ADD(&s->be->be_counters.failed_rewrites, 1);
|
|
if (sess->listener && sess->listener->counters)
|
|
_HA_ATOMIC_ADD(&sess->listener->counters->failed_rewrites, 1);
|
|
if (objt_server(s->target))
|
|
_HA_ATOMIC_ADD(&__objt_server(s->target)->counters.failed_rewrites, 1);
|
|
|
|
if (!(s->txn->req.flags & HTTP_MSGF_SOFT_RW)) {
|
|
ret = ACT_RET_ERR;
|
|
if (!(s->flags & SF_ERR_MASK))
|
|
s->flags |= SF_ERR_PRXCOND;
|
|
}
|
|
goto leave;
|
|
}
|
|
|
|
/* Parses the http-request normalize-uri action. It expects a single <normalizer>
|
|
* argument, corresponding too a value in `enum act_normalize_uri`.
|
|
*
|
|
* It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
|
|
*/
|
|
static enum act_parse_ret parse_http_normalize_uri(const char **args, int *orig_arg, struct proxy *px,
|
|
struct act_rule *rule, char **err)
|
|
{
|
|
int cur_arg = *orig_arg;
|
|
|
|
rule->action_ptr = http_action_normalize_uri;
|
|
rule->release_ptr = NULL;
|
|
|
|
if (!*args[cur_arg]) {
|
|
memprintf(err, "missing argument <normalizer>");
|
|
return ACT_RET_PRS_ERR;
|
|
}
|
|
|
|
if (strcmp(args[cur_arg], "path-merge-slashes") == 0) {
|
|
cur_arg++;
|
|
|
|
rule->action = ACT_NORMALIZE_URI_PATH_MERGE_SLASHES;
|
|
}
|
|
else if (strcmp(args[cur_arg], "path-strip-dot") == 0) {
|
|
cur_arg++;
|
|
|
|
rule->action = ACT_NORMALIZE_URI_PATH_STRIP_DOT;
|
|
}
|
|
else if (strcmp(args[cur_arg], "path-strip-dotdot") == 0) {
|
|
cur_arg++;
|
|
|
|
if (strcmp(args[cur_arg], "full") == 0) {
|
|
cur_arg++;
|
|
rule->action = ACT_NORMALIZE_URI_PATH_STRIP_DOTDOT_FULL;
|
|
}
|
|
else if (!*args[cur_arg]) {
|
|
rule->action = ACT_NORMALIZE_URI_PATH_STRIP_DOTDOT;
|
|
}
|
|
else if (strcmp(args[cur_arg], "if") != 0 && strcmp(args[cur_arg], "unless") != 0) {
|
|
memprintf(err, "unknown argument '%s' for 'path-strip-dotdot' normalizer", args[cur_arg]);
|
|
return ACT_RET_PRS_ERR;
|
|
}
|
|
}
|
|
else if (strcmp(args[cur_arg], "query-sort-by-name") == 0) {
|
|
cur_arg++;
|
|
|
|
rule->action = ACT_NORMALIZE_URI_QUERY_SORT_BY_NAME;
|
|
}
|
|
else if (strcmp(args[cur_arg], "percent-to-uppercase") == 0) {
|
|
cur_arg++;
|
|
|
|
if (strcmp(args[cur_arg], "strict") == 0) {
|
|
cur_arg++;
|
|
rule->action = ACT_NORMALIZE_URI_PERCENT_TO_UPPERCASE_STRICT;
|
|
}
|
|
else if (!*args[cur_arg]) {
|
|
rule->action = ACT_NORMALIZE_URI_PERCENT_TO_UPPERCASE;
|
|
}
|
|
else if (strcmp(args[cur_arg], "if") != 0 && strcmp(args[cur_arg], "unless") != 0) {
|
|
memprintf(err, "unknown argument '%s' for 'percent-to-uppercase' normalizer", args[cur_arg]);
|
|
return ACT_RET_PRS_ERR;
|
|
}
|
|
}
|
|
else if (strcmp(args[cur_arg], "percent-decode-unreserved") == 0) {
|
|
cur_arg++;
|
|
|
|
if (strcmp(args[cur_arg], "strict") == 0) {
|
|
cur_arg++;
|
|
rule->action = ACT_NORMALIZE_URI_PERCENT_DECODE_UNRESERVED_STRICT;
|
|
}
|
|
else if (!*args[cur_arg]) {
|
|
rule->action = ACT_NORMALIZE_URI_PERCENT_DECODE_UNRESERVED;
|
|
}
|
|
else if (strcmp(args[cur_arg], "if") != 0 && strcmp(args[cur_arg], "unless") != 0) {
|
|
memprintf(err, "unknown argument '%s' for 'percent-decode-unreserved' normalizer", args[cur_arg]);
|
|
return ACT_RET_PRS_ERR;
|
|
}
|
|
}
|
|
else if (strcmp(args[cur_arg], "fragment-strip") == 0) {
|
|
cur_arg++;
|
|
|
|
rule->action = ACT_NORMALIZE_URI_FRAGMENT_STRIP;
|
|
}
|
|
else if (strcmp(args[cur_arg], "fragment-encode") == 0) {
|
|
cur_arg++;
|
|
|
|
rule->action = ACT_NORMALIZE_URI_FRAGMENT_ENCODE;
|
|
}
|
|
else {
|
|
memprintf(err, "unknown normalizer '%s'", args[cur_arg]);
|
|
return ACT_RET_PRS_ERR;
|
|
}
|
|
|
|
*orig_arg = cur_arg;
|
|
return ACT_RET_PRS_OK;
|
|
}
|
|
|
|
/* This function executes a replace-uri action. It finds its arguments in
|
|
* <rule>.arg.http. It builds a string in the trash from the format string
|
|
* previously filled by function parse_replace_uri() and will execute the regex
|
|
* in <http.re> to replace the URI. It uses the format string present in
|
|
* <http.fmt>. The component to act on (path/uri) is taken from <.action> which
|
|
* contains 1 for the path or 3 for the URI (values used by
|
|
* http_req_replace_stline()). On success, it returns ACT_RET_CONT. If an error
|
|
* occurs while soft rewrites are enabled, the action is canceled, but the rule
|
|
* processing continue. Otherwsize ACT_RET_ERR is returned.
|
|
*/
|
|
static enum act_return http_action_replace_uri(struct act_rule *rule, struct proxy *px,
|
|
struct session *sess, struct stream *s, int flags)
|
|
{
|
|
enum act_return ret = ACT_RET_CONT;
|
|
struct buffer *replace, *output;
|
|
struct ist uri;
|
|
int len;
|
|
|
|
replace = alloc_trash_chunk();
|
|
output = alloc_trash_chunk();
|
|
if (!replace || !output)
|
|
goto fail_alloc;
|
|
uri = htx_sl_req_uri(http_get_stline(htxbuf(&s->req.buf)));
|
|
|
|
if (rule->action == 1) { // replace-path
|
|
struct http_uri_parser parser = http_uri_parser_init(uri);
|
|
uri = iststop(http_parse_path(&parser), '?');
|
|
}
|
|
else if (rule->action == 4) { // replace-pathq
|
|
struct http_uri_parser parser = http_uri_parser_init(uri);
|
|
uri = http_parse_path(&parser);
|
|
}
|
|
|
|
if (!istlen(uri))
|
|
goto leave;
|
|
|
|
if (!regex_exec_match2(rule->arg.http.re, uri.ptr, uri.len, MAX_MATCH, pmatch, 0))
|
|
goto leave;
|
|
|
|
replace->data = build_logline(s, replace->area, replace->size, &rule->arg.http.fmt);
|
|
|
|
/* note: uri.ptr doesn't need to be zero-terminated because it will
|
|
* only be used to pick pmatch references.
|
|
*/
|
|
len = exp_replace(output->area, output->size, uri.ptr, replace->area, pmatch);
|
|
if (len == -1)
|
|
goto fail_rewrite;
|
|
|
|
if (http_req_replace_stline(rule->action, output->area, len, px, s) == -1)
|
|
goto fail_rewrite;
|
|
|
|
leave:
|
|
free_trash_chunk(output);
|
|
free_trash_chunk(replace);
|
|
return ret;
|
|
|
|
fail_alloc:
|
|
if (!(s->flags & SF_ERR_MASK))
|
|
s->flags |= SF_ERR_RESOURCE;
|
|
ret = ACT_RET_ERR;
|
|
goto leave;
|
|
|
|
fail_rewrite:
|
|
_HA_ATOMIC_INC(&sess->fe->fe_counters.failed_rewrites);
|
|
if (s->flags & SF_BE_ASSIGNED)
|
|
_HA_ATOMIC_INC(&s->be->be_counters.failed_rewrites);
|
|
if (sess->listener && sess->listener->counters)
|
|
_HA_ATOMIC_INC(&sess->listener->counters->failed_rewrites);
|
|
if (objt_server(s->target))
|
|
_HA_ATOMIC_INC(&__objt_server(s->target)->counters.failed_rewrites);
|
|
|
|
if (!(s->txn->req.flags & HTTP_MSGF_SOFT_RW)) {
|
|
ret = ACT_RET_ERR;
|
|
if (!(s->flags & SF_ERR_MASK))
|
|
s->flags |= SF_ERR_PRXCOND;
|
|
}
|
|
goto leave;
|
|
}
|
|
|
|
/* parse a "replace-uri", "replace-path" or "replace-pathq"
|
|
* http-request action.
|
|
* This action takes 2 arguments (a regex and a replacement format string).
|
|
* The resulting rule makes use of <.action> to store the action (1/3 for now),
|
|
* <http.re> to store the compiled regex, and <http.fmt> to store the log-format
|
|
* list head. It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
|
|
*/
|
|
static enum act_parse_ret parse_replace_uri(const char **args, int *orig_arg, struct proxy *px,
|
|
struct act_rule *rule, char **err)
|
|
{
|
|
int cur_arg = *orig_arg;
|
|
int cap = 0;
|
|
char *error = NULL;
|
|
|
|
switch (args[0][8]) {
|
|
case 'p':
|
|
if (args[0][12] == 'q')
|
|
rule->action = 4; // replace-pathq, same as set-pathq
|
|
else
|
|
rule->action = 1; // replace-path, same as set-path
|
|
break;
|
|
case 'u':
|
|
rule->action = 3; // replace-uri, same as set-uri
|
|
break;
|
|
default:
|
|
memprintf(err, "internal error: unhandled action '%s'", args[0]);
|
|
return ACT_RET_PRS_ERR;
|
|
}
|
|
|
|
rule->action_ptr = http_action_replace_uri;
|
|
rule->release_ptr = release_http_action;
|
|
lf_expr_init(&rule->arg.http.fmt);
|
|
|
|
if (!*args[cur_arg] || !*args[cur_arg+1] ||
|
|
(*args[cur_arg+2] && strcmp(args[cur_arg+2], "if") != 0 && strcmp(args[cur_arg+2], "unless") != 0)) {
|
|
memprintf(err, "expects exactly 2 arguments <match-regex> and <replace-format>");
|
|
return ACT_RET_PRS_ERR;
|
|
}
|
|
|
|
if (!(rule->arg.http.re = regex_comp(args[cur_arg], 1, 1, &error))) {
|
|
memprintf(err, "failed to parse the regex : %s", error);
|
|
free(error);
|
|
return ACT_RET_PRS_ERR;
|
|
}
|
|
|
|
px->conf.args.ctx = ARGC_HRQ;
|
|
if (px->cap & PR_CAP_FE)
|
|
cap |= SMP_VAL_FE_HRQ_HDR;
|
|
if (px->cap & PR_CAP_BE)
|
|
cap |= SMP_VAL_BE_HRQ_HDR;
|
|
if (!parse_logformat_string(args[cur_arg + 1], px, &rule->arg.http.fmt, LOG_OPT_HTTP, cap, err)) {
|
|
regex_free(rule->arg.http.re);
|
|
return ACT_RET_PRS_ERR;
|
|
}
|
|
|
|
(*orig_arg) += 2;
|
|
return ACT_RET_PRS_OK;
|
|
}
|
|
|
|
/* This function is just a compliant action wrapper for "set-status". */
|
|
static enum act_return action_http_set_status(struct act_rule *rule, struct proxy *px,
|
|
struct session *sess, struct stream *s, int flags)
|
|
{
|
|
if (http_res_set_status(rule->arg.http.i, rule->arg.http.str, s) == -1) {
|
|
_HA_ATOMIC_INC(&sess->fe->fe_counters.failed_rewrites);
|
|
if (s->flags & SF_BE_ASSIGNED)
|
|
_HA_ATOMIC_INC(&s->be->be_counters.failed_rewrites);
|
|
if (sess->listener && sess->listener->counters)
|
|
_HA_ATOMIC_INC(&sess->listener->counters->failed_rewrites);
|
|
if (objt_server(s->target))
|
|
_HA_ATOMIC_INC(&__objt_server(s->target)->counters.failed_rewrites);
|
|
|
|
if (!(s->txn->req.flags & HTTP_MSGF_SOFT_RW)) {
|
|
if (!(s->flags & SF_ERR_MASK))
|
|
s->flags |= SF_ERR_PRXCOND;
|
|
return ACT_RET_ERR;
|
|
}
|
|
}
|
|
|
|
return ACT_RET_CONT;
|
|
}
|
|
|
|
/* parse set-status action:
|
|
* This action accepts a single argument of type int representing
|
|
* an http status code. It returns ACT_RET_PRS_OK on success,
|
|
* ACT_RET_PRS_ERR on error.
|
|
*/
|
|
static enum act_parse_ret parse_http_set_status(const char **args, int *orig_arg, struct proxy *px,
|
|
struct act_rule *rule, char **err)
|
|
{
|
|
char *error;
|
|
|
|
rule->action = ACT_CUSTOM;
|
|
rule->action_ptr = action_http_set_status;
|
|
rule->release_ptr = release_http_action;
|
|
lf_expr_init(&rule->arg.http.fmt);
|
|
|
|
/* Check if an argument is available */
|
|
if (!*args[*orig_arg]) {
|
|
memprintf(err, "expects 1 argument: <status>; or 3 arguments: <status> reason <fmt>");
|
|
return ACT_RET_PRS_ERR;
|
|
}
|
|
|
|
/* convert status code as integer */
|
|
rule->arg.http.i = strtol(args[*orig_arg], &error, 10);
|
|
if (*error != '\0' || rule->arg.http.i < 100 || rule->arg.http.i > 999) {
|
|
memprintf(err, "expects an integer status code between 100 and 999");
|
|
return ACT_RET_PRS_ERR;
|
|
}
|
|
|
|
(*orig_arg)++;
|
|
|
|
/* set custom reason string */
|
|
rule->arg.http.str = ist(NULL); // If null, we use the default reason for the status code.
|
|
if (*args[*orig_arg] && strcmp(args[*orig_arg], "reason") == 0 &&
|
|
(*args[*orig_arg + 1] && strcmp(args[*orig_arg + 1], "if") != 0 && strcmp(args[*orig_arg + 1], "unless") != 0)) {
|
|
(*orig_arg)++;
|
|
rule->arg.http.str = ist(strdup(args[*orig_arg]));
|
|
(*orig_arg)++;
|
|
}
|
|
|
|
return ACT_RET_PRS_OK;
|
|
}
|
|
|
|
/* This function executes the "reject" HTTP action. It clears the request and
|
|
* response buffer without sending any response. It can be useful as an HTTP
|
|
* alternative to the silent-drop action to defend against DoS attacks, and may
|
|
* also be used with HTTP/2 to close a connection instead of just a stream.
|
|
* The txn status is unchanged, indicating no response was sent. The termination
|
|
* flags will indicate "PR". It always returns ACT_RET_ABRT.
|
|
*/
|
|
static enum act_return http_action_reject(struct act_rule *rule, struct proxy *px,
|
|
struct session *sess, struct stream *s, int flags)
|
|
{
|
|
sc_must_kill_conn(s->scf);
|
|
stream_abort(s);
|
|
s->req.analysers &= AN_REQ_FLT_END;
|
|
s->res.analysers &= AN_RES_FLT_END;
|
|
|
|
_HA_ATOMIC_INC(&s->be->be_counters.denied_req);
|
|
_HA_ATOMIC_INC(&sess->fe->fe_counters.denied_req);
|
|
if (sess->listener && sess->listener->counters)
|
|
_HA_ATOMIC_INC(&sess->listener->counters->denied_req);
|
|
|
|
if (!(s->flags & SF_ERR_MASK))
|
|
s->flags |= SF_ERR_PRXCOND;
|
|
if (!(s->flags & SF_FINST_MASK))
|
|
s->flags |= SF_FINST_R;
|
|
|
|
return ACT_RET_ABRT;
|
|
}
|
|
|
|
/* parse the "reject" action:
|
|
* This action takes no argument and returns ACT_RET_PRS_OK on success,
|
|
* ACT_RET_PRS_ERR on error.
|
|
*/
|
|
static enum act_parse_ret parse_http_action_reject(const char **args, int *orig_arg, struct proxy *px,
|
|
struct act_rule *rule, char **err)
|
|
{
|
|
rule->action = ACT_CUSTOM;
|
|
rule->action_ptr = http_action_reject;
|
|
return ACT_RET_PRS_OK;
|
|
}
|
|
|
|
/* This function executes the "disable-l7-retry" HTTP action.
|
|
* It disables L7 retries (all retry except for a connection failure). This
|
|
* can be useful for example to avoid retrying on POST requests.
|
|
* It just removes the L7 retry flag on the HTTP transaction, and always
|
|
* return ACT_RET_CONT;
|
|
*/
|
|
static enum act_return http_req_disable_l7_retry(struct act_rule *rule, struct proxy *px,
|
|
struct session *sess, struct stream *s, int flags)
|
|
{
|
|
/* In theory, the TX_L7_RETRY flags isn't set at this point, but
|
|
* let's be future-proof and remove it anyway.
|
|
*/
|
|
s->txn->flags &= ~TX_L7_RETRY;
|
|
s->txn->flags |= TX_D_L7_RETRY;
|
|
return ACT_RET_CONT;
|
|
}
|
|
|
|
/* parse the "disable-l7-retry" action:
|
|
* This action takes no argument and returns ACT_RET_PRS_OK on success,
|
|
* ACT_RET_PRS_ERR on error.
|
|
*/
|
|
static enum act_parse_ret parse_http_req_disable_l7_retry(const char **args,
|
|
int *orig_args, struct proxy *px,
|
|
struct act_rule *rule, char **err)
|
|
{
|
|
rule->action = ACT_CUSTOM;
|
|
rule->action_ptr = http_req_disable_l7_retry;
|
|
return ACT_RET_PRS_OK;
|
|
}
|
|
|
|
/* This function executes the "capture" action. It executes a fetch expression,
|
|
* turns the result into a string and puts it in a capture slot. It always
|
|
* returns 1. If an error occurs the action is cancelled, but the rule
|
|
* processing continues.
|
|
*/
|
|
static enum act_return http_action_req_capture(struct act_rule *rule, struct proxy *px,
|
|
struct session *sess, struct stream *s, int flags)
|
|
{
|
|
struct sample *key;
|
|
struct cap_hdr *h = rule->arg.cap.hdr;
|
|
char **cap = s->req_cap;
|
|
int len;
|
|
|
|
key = sample_fetch_as_type(s->be, sess, s, SMP_OPT_DIR_REQ|SMP_OPT_FINAL, rule->arg.cap.expr, SMP_T_STR);
|
|
if (!key)
|
|
return ACT_RET_CONT;
|
|
|
|
if (cap[h->index] == NULL)
|
|
cap[h->index] = pool_alloc(h->pool);
|
|
|
|
if (cap[h->index] == NULL) /* no more capture memory */
|
|
return ACT_RET_CONT;
|
|
|
|
len = key->data.u.str.data;
|
|
if (len > h->len)
|
|
len = h->len;
|
|
|
|
memcpy(cap[h->index], key->data.u.str.area, len);
|
|
cap[h->index][len] = 0;
|
|
return ACT_RET_CONT;
|
|
}
|
|
|
|
/* This function executes the "capture" action and store the result in a
|
|
* capture slot if exists. It executes a fetch expression, turns the result
|
|
* into a string and puts it in a capture slot. It always returns 1. If an
|
|
* error occurs the action is cancelled, but the rule processing continues.
|
|
*/
|
|
static enum act_return http_action_req_capture_by_id(struct act_rule *rule, struct proxy *px,
|
|
struct session *sess, struct stream *s, int flags)
|
|
{
|
|
struct sample *key;
|
|
struct cap_hdr *h;
|
|
char **cap = s->req_cap;
|
|
struct proxy *fe = strm_fe(s);
|
|
int len;
|
|
int i;
|
|
|
|
/* Look for the original configuration. */
|
|
for (h = fe->req_cap, i = fe->nb_req_cap - 1;
|
|
h != NULL && i != rule->arg.capid.idx ;
|
|
i--, h = h->next);
|
|
if (!h)
|
|
return ACT_RET_CONT;
|
|
|
|
key = sample_fetch_as_type(s->be, sess, s, SMP_OPT_DIR_REQ|SMP_OPT_FINAL, rule->arg.capid.expr, SMP_T_STR);
|
|
if (!key)
|
|
return ACT_RET_CONT;
|
|
|
|
if (cap[h->index] == NULL)
|
|
cap[h->index] = pool_alloc(h->pool);
|
|
|
|
if (cap[h->index] == NULL) /* no more capture memory */
|
|
return ACT_RET_CONT;
|
|
|
|
len = key->data.u.str.data;
|
|
if (len > h->len)
|
|
len = h->len;
|
|
|
|
memcpy(cap[h->index], key->data.u.str.area, len);
|
|
cap[h->index][len] = 0;
|
|
return ACT_RET_CONT;
|
|
}
|
|
|
|
/* Check an "http-request capture" action.
|
|
*
|
|
* The function returns 1 in success case, otherwise, it returns 0 and err is
|
|
* filled.
|
|
*/
|
|
static int check_http_req_capture(struct act_rule *rule, struct proxy *px, char **err)
|
|
{
|
|
if (rule->action_ptr != http_action_req_capture_by_id)
|
|
return 1;
|
|
|
|
/* capture slots can only be declared in frontends, so we can't check their
|
|
* existence in backends at configuration parsing step
|
|
*/
|
|
if (px->cap & PR_CAP_FE && rule->arg.capid.idx >= px->nb_req_cap) {
|
|
memprintf(err, "unable to find capture id '%d' referenced by http-request capture rule",
|
|
rule->arg.capid.idx);
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* Release memory allocate by an http capture action */
|
|
static void release_http_capture(struct act_rule *rule)
|
|
{
|
|
if (rule->action_ptr == http_action_req_capture)
|
|
release_sample_expr(rule->arg.cap.expr);
|
|
else
|
|
release_sample_expr(rule->arg.capid.expr);
|
|
}
|
|
|
|
/* parse an "http-request capture" action. It takes a single argument which is
|
|
* a sample fetch expression. It stores the expression into arg->act.p[0] and
|
|
* the allocated hdr_cap struct or the preallocated "id" into arg->act.p[1].
|
|
* It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
|
|
*/
|
|
static enum act_parse_ret parse_http_req_capture(const char **args, int *orig_arg, struct proxy *px,
|
|
struct act_rule *rule, char **err)
|
|
{
|
|
struct sample_expr *expr;
|
|
struct cap_hdr *hdr;
|
|
int cur_arg;
|
|
int len = 0;
|
|
|
|
for (cur_arg = *orig_arg; cur_arg < *orig_arg + 3 && *args[cur_arg]; cur_arg++)
|
|
if (strcmp(args[cur_arg], "if") == 0 ||
|
|
strcmp(args[cur_arg], "unless") == 0)
|
|
break;
|
|
|
|
if (cur_arg < *orig_arg + 3) {
|
|
memprintf(err, "expects <expression> [ 'len' <length> | id <idx> ]");
|
|
return ACT_RET_PRS_ERR;
|
|
}
|
|
|
|
cur_arg = *orig_arg;
|
|
expr = sample_parse_expr((char **)args, &cur_arg, px->conf.args.file, px->conf.args.line, err, &px->conf.args, NULL);
|
|
if (!expr)
|
|
return ACT_RET_PRS_ERR;
|
|
|
|
if (!(expr->fetch->val & SMP_VAL_FE_HRQ_HDR)) {
|
|
memprintf(err,
|
|
"fetch method '%s' extracts information from '%s', none of which is available here",
|
|
args[cur_arg-1], sample_src_names(expr->fetch->use));
|
|
release_sample_expr(expr);
|
|
return ACT_RET_PRS_ERR;
|
|
}
|
|
|
|
if (!args[cur_arg] || !*args[cur_arg]) {
|
|
memprintf(err, "expects 'len or 'id'");
|
|
release_sample_expr(expr);
|
|
return ACT_RET_PRS_ERR;
|
|
}
|
|
|
|
if (strcmp(args[cur_arg], "len") == 0) {
|
|
cur_arg++;
|
|
|
|
if (!(px->cap & PR_CAP_FE)) {
|
|
memprintf(err, "proxy '%s' has no frontend capability", px->id);
|
|
release_sample_expr(expr);
|
|
return ACT_RET_PRS_ERR;
|
|
}
|
|
|
|
px->conf.args.ctx = ARGC_CAP;
|
|
|
|
if (!args[cur_arg]) {
|
|
memprintf(err, "missing length value");
|
|
release_sample_expr(expr);
|
|
return ACT_RET_PRS_ERR;
|
|
}
|
|
/* we copy the table name for now, it will be resolved later */
|
|
len = atoi(args[cur_arg]);
|
|
if (len <= 0) {
|
|
memprintf(err, "length must be > 0");
|
|
release_sample_expr(expr);
|
|
return ACT_RET_PRS_ERR;
|
|
}
|
|
cur_arg++;
|
|
|
|
hdr = calloc(1, sizeof(*hdr));
|
|
if (!hdr) {
|
|
memprintf(err, "out of memory");
|
|
release_sample_expr(expr);
|
|
return ACT_RET_PRS_ERR;
|
|
}
|
|
hdr->next = px->req_cap;
|
|
hdr->name = NULL; /* not a header capture */
|
|
hdr->namelen = 0;
|
|
hdr->len = len;
|
|
hdr->pool = create_pool("caphdr", hdr->len + 1, MEM_F_SHARED);
|
|
hdr->index = px->nb_req_cap++;
|
|
|
|
px->req_cap = hdr;
|
|
px->to_log |= LW_REQHDR;
|
|
|
|
rule->action = ACT_CUSTOM;
|
|
rule->action_ptr = http_action_req_capture;
|
|
rule->release_ptr = release_http_capture;
|
|
rule->arg.cap.expr = expr;
|
|
rule->arg.cap.hdr = hdr;
|
|
}
|
|
|
|
else if (strcmp(args[cur_arg], "id") == 0) {
|
|
int id;
|
|
char *error;
|
|
|
|
cur_arg++;
|
|
|
|
if (!args[cur_arg]) {
|
|
memprintf(err, "missing id value");
|
|
release_sample_expr(expr);
|
|
return ACT_RET_PRS_ERR;
|
|
}
|
|
|
|
id = strtol(args[cur_arg], &error, 10);
|
|
if (*error != '\0') {
|
|
memprintf(err, "cannot parse id '%s'", args[cur_arg]);
|
|
release_sample_expr(expr);
|
|
return ACT_RET_PRS_ERR;
|
|
}
|
|
cur_arg++;
|
|
|
|
px->conf.args.ctx = ARGC_CAP;
|
|
|
|
rule->action = ACT_CUSTOM;
|
|
rule->action_ptr = http_action_req_capture_by_id;
|
|
rule->check_ptr = check_http_req_capture;
|
|
rule->release_ptr = release_http_capture;
|
|
rule->arg.capid.expr = expr;
|
|
rule->arg.capid.idx = id;
|
|
}
|
|
|
|
else {
|
|
memprintf(err, "expects 'len' or 'id', found '%s'", args[cur_arg]);
|
|
release_sample_expr(expr);
|
|
return ACT_RET_PRS_ERR;
|
|
}
|
|
|
|
*orig_arg = cur_arg;
|
|
return ACT_RET_PRS_OK;
|
|
}
|
|
|
|
/* This function executes the "capture" action and store the result in a
|
|
* capture slot if exists. It executes a fetch expression, turns the result
|
|
* into a string and puts it in a capture slot. It always returns 1. If an
|
|
* error occurs the action is cancelled, but the rule processing continues.
|
|
*/
|
|
static enum act_return http_action_res_capture_by_id(struct act_rule *rule, struct proxy *px,
|
|
struct session *sess, struct stream *s, int flags)
|
|
{
|
|
struct sample *key;
|
|
struct cap_hdr *h;
|
|
char **cap = s->res_cap;
|
|
struct proxy *fe = strm_fe(s);
|
|
int len;
|
|
int i;
|
|
|
|
/* Look for the original configuration. */
|
|
for (h = fe->rsp_cap, i = fe->nb_rsp_cap - 1;
|
|
h != NULL && i != rule->arg.capid.idx ;
|
|
i--, h = h->next);
|
|
if (!h)
|
|
return ACT_RET_CONT;
|
|
|
|
key = sample_fetch_as_type(s->be, sess, s, SMP_OPT_DIR_RES|SMP_OPT_FINAL, rule->arg.capid.expr, SMP_T_STR);
|
|
if (!key)
|
|
return ACT_RET_CONT;
|
|
|
|
if (cap[h->index] == NULL)
|
|
cap[h->index] = pool_alloc(h->pool);
|
|
|
|
if (cap[h->index] == NULL) /* no more capture memory */
|
|
return ACT_RET_CONT;
|
|
|
|
len = key->data.u.str.data;
|
|
if (len > h->len)
|
|
len = h->len;
|
|
|
|
memcpy(cap[h->index], key->data.u.str.area, len);
|
|
cap[h->index][len] = 0;
|
|
return ACT_RET_CONT;
|
|
}
|
|
|
|
/* Check an "http-response capture" action.
|
|
*
|
|
* The function returns 1 in success case, otherwise, it returns 0 and err is
|
|
* filled.
|
|
*/
|
|
static int check_http_res_capture(struct act_rule *rule, struct proxy *px, char **err)
|
|
{
|
|
if (rule->action_ptr != http_action_res_capture_by_id)
|
|
return 1;
|
|
|
|
/* capture slots can only be declared in frontends, so we can't check their
|
|
* existence in backends at configuration parsing step
|
|
*/
|
|
if (px->cap & PR_CAP_FE && rule->arg.capid.idx >= px->nb_rsp_cap) {
|
|
memprintf(err, "unable to find capture id '%d' referenced by http-response capture rule",
|
|
rule->arg.capid.idx);
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* parse an "http-response capture" action. It takes a single argument which is
|
|
* a sample fetch expression. It stores the expression into arg->act.p[0] and
|
|
* the allocated hdr_cap struct of the preallocated id into arg->act.p[1].
|
|
* It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
|
|
*/
|
|
static enum act_parse_ret parse_http_res_capture(const char **args, int *orig_arg, struct proxy *px,
|
|
struct act_rule *rule, char **err)
|
|
{
|
|
struct sample_expr *expr;
|
|
int cur_arg;
|
|
int id;
|
|
char *error;
|
|
|
|
for (cur_arg = *orig_arg; cur_arg < *orig_arg + 3 && *args[cur_arg]; cur_arg++)
|
|
if (strcmp(args[cur_arg], "if") == 0 ||
|
|
strcmp(args[cur_arg], "unless") == 0)
|
|
break;
|
|
|
|
if (cur_arg < *orig_arg + 3) {
|
|
memprintf(err, "expects <expression> id <idx>");
|
|
return ACT_RET_PRS_ERR;
|
|
}
|
|
|
|
cur_arg = *orig_arg;
|
|
expr = sample_parse_expr((char **)args, &cur_arg, px->conf.args.file, px->conf.args.line, err, &px->conf.args, NULL);
|
|
if (!expr)
|
|
return ACT_RET_PRS_ERR;
|
|
|
|
if (!(expr->fetch->val & SMP_VAL_FE_HRS_HDR)) {
|
|
memprintf(err,
|
|
"fetch method '%s' extracts information from '%s', none of which is available here",
|
|
args[cur_arg-1], sample_src_names(expr->fetch->use));
|
|
release_sample_expr(expr);
|
|
return ACT_RET_PRS_ERR;
|
|
}
|
|
|
|
if (!args[cur_arg] || !*args[cur_arg]) {
|
|
memprintf(err, "expects 'id'");
|
|
release_sample_expr(expr);
|
|
return ACT_RET_PRS_ERR;
|
|
}
|
|
|
|
if (strcmp(args[cur_arg], "id") != 0) {
|
|
memprintf(err, "expects 'id', found '%s'", args[cur_arg]);
|
|
release_sample_expr(expr);
|
|
return ACT_RET_PRS_ERR;
|
|
}
|
|
|
|
cur_arg++;
|
|
|
|
if (!args[cur_arg]) {
|
|
memprintf(err, "missing id value");
|
|
release_sample_expr(expr);
|
|
return ACT_RET_PRS_ERR;
|
|
}
|
|
|
|
id = strtol(args[cur_arg], &error, 10);
|
|
if (*error != '\0') {
|
|
memprintf(err, "cannot parse id '%s'", args[cur_arg]);
|
|
release_sample_expr(expr);
|
|
return ACT_RET_PRS_ERR;
|
|
}
|
|
cur_arg++;
|
|
|
|
px->conf.args.ctx = ARGC_CAP;
|
|
|
|
rule->action = ACT_CUSTOM;
|
|
rule->action_ptr = http_action_res_capture_by_id;
|
|
rule->check_ptr = check_http_res_capture;
|
|
rule->release_ptr = release_http_capture;
|
|
rule->arg.capid.expr = expr;
|
|
rule->arg.capid.idx = id;
|
|
|
|
*orig_arg = cur_arg;
|
|
return ACT_RET_PRS_OK;
|
|
}
|
|
|
|
/* Parse a "allow" action for a request or a response rule. It takes no argument. It
|
|
* returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
|
|
*/
|
|
static enum act_parse_ret parse_http_allow(const char **args, int *orig_arg, struct proxy *px,
|
|
struct act_rule *rule, char **err)
|
|
{
|
|
rule->action = ACT_ACTION_ALLOW;
|
|
rule->flags |= ACT_FLAG_FINAL;
|
|
return ACT_RET_PRS_OK;
|
|
}
|
|
|
|
/* Parse "deny" or "tarpit" actions for a request rule or "deny" action for a
|
|
* response rule. It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on
|
|
* error. It relies on http_parse_http_reply() to set
|
|
* <.arg.http_reply>.
|
|
*/
|
|
static enum act_parse_ret parse_http_deny(const char **args, int *orig_arg, struct proxy *px,
|
|
struct act_rule *rule, char **err)
|
|
{
|
|
int default_status;
|
|
int cur_arg, arg = 0;
|
|
|
|
cur_arg = *orig_arg;
|
|
if (rule->from == ACT_F_HTTP_REQ) {
|
|
if (strcmp(args[cur_arg - 1], "tarpit") == 0) {
|
|
rule->action = ACT_HTTP_REQ_TARPIT;
|
|
default_status = 500;
|
|
}
|
|
else {
|
|
rule->action = ACT_ACTION_DENY;
|
|
default_status = 403;
|
|
}
|
|
}
|
|
else {
|
|
rule->action = ACT_ACTION_DENY;
|
|
default_status = 502;
|
|
}
|
|
|
|
/* If no args or only a deny_status specified, fallback on the legacy
|
|
* mode and use default error files despite the fact that
|
|
* default-errorfiles is not used. Otherwise, parse an http reply.
|
|
*/
|
|
|
|
/* Prepare parsing of log-format strings */
|
|
px->conf.args.ctx = ((rule->from == ACT_F_HTTP_REQ) ? ARGC_HRQ : ARGC_HRS);
|
|
|
|
if (!*(args[cur_arg]) || strcmp(args[cur_arg], "if") == 0 || strcmp(args[cur_arg], "unless") == 0) {
|
|
rule->arg.http_reply = http_parse_http_reply((const char *[]){"default-errorfiles", ""}, &arg, px, default_status, err);
|
|
goto end;
|
|
}
|
|
|
|
if (strcmp(args[cur_arg], "deny_status") == 0) {
|
|
if (!*(args[cur_arg+2]) || strcmp(args[cur_arg+2], "if") == 0 || strcmp(args[cur_arg+2], "unless") == 0) {
|
|
rule->arg.http_reply = http_parse_http_reply((const char *[]){"status", args[cur_arg+1], "default-errorfiles", ""},
|
|
&arg, px, default_status, err);
|
|
*orig_arg += 2;
|
|
goto end;
|
|
}
|
|
args[cur_arg] += 5; /* skip "deny_" for the parsing */
|
|
}
|
|
|
|
rule->arg.http_reply = http_parse_http_reply(args, orig_arg, px, default_status, err);
|
|
|
|
end:
|
|
if (!rule->arg.http_reply)
|
|
return ACT_RET_PRS_ERR;
|
|
|
|
rule->flags |= ACT_FLAG_FINAL;
|
|
rule->check_ptr = check_act_http_reply;
|
|
rule->release_ptr = release_act_http_reply;
|
|
return ACT_RET_PRS_OK;
|
|
}
|
|
|
|
|
|
/* This function executes a auth action. It builds an 401/407 HTX message using
|
|
* the corresponding proxy's error message. On success, it returns
|
|
* ACT_RET_ABRT. If an error occurs ACT_RET_ERR is returned.
|
|
*/
|
|
static enum act_return http_action_auth(struct act_rule *rule, struct proxy *px,
|
|
struct session *sess, struct stream *s, int flags)
|
|
{
|
|
struct channel *req = &s->req;
|
|
struct channel *res = &s->res;
|
|
struct htx *htx = htx_from_buf(&res->buf);
|
|
struct http_reply *reply;
|
|
const char *auth_realm;
|
|
struct http_hdr_ctx ctx;
|
|
struct ist hdr;
|
|
|
|
/* Auth might be performed on regular http-req rules as well as on stats */
|
|
auth_realm = rule->arg.http.str.ptr;
|
|
if (!auth_realm) {
|
|
if (px->uri_auth && s->current_rule_list == &px->uri_auth->http_req_rules)
|
|
auth_realm = STATS_DEFAULT_REALM;
|
|
else
|
|
auth_realm = px->id;
|
|
}
|
|
|
|
if (!(s->txn->flags & TX_USE_PX_CONN)) {
|
|
s->txn->status = 401;
|
|
hdr = ist("WWW-Authenticate");
|
|
}
|
|
else {
|
|
s->txn->status = 407;
|
|
hdr = ist("Proxy-Authenticate");
|
|
}
|
|
reply = http_error_message(s);
|
|
channel_htx_truncate(res, htx);
|
|
|
|
if (chunk_printf(&trash, "Basic realm=\"%s\"", auth_realm) == -1)
|
|
goto fail;
|
|
|
|
/* Write the generic 40x message */
|
|
if (http_reply_to_htx(s, htx, reply) == -1)
|
|
goto fail;
|
|
|
|
/* Remove all existing occurrences of the XXX-Authenticate header */
|
|
ctx.blk = NULL;
|
|
while (http_find_header(htx, hdr, &ctx, 1))
|
|
http_remove_header(htx, &ctx);
|
|
|
|
/* Now a the right XXX-Authenticate header */
|
|
if (!http_add_header(htx, hdr, ist2(b_orig(&trash), b_data(&trash))))
|
|
goto fail;
|
|
|
|
/* Finally forward the reply */
|
|
htx_to_buf(htx, &res->buf);
|
|
if (!http_forward_proxy_resp(s, 1))
|
|
goto fail;
|
|
|
|
/* Note: Only eval on the request */
|
|
s->logs.request_ts = now_ns;
|
|
req->analysers &= AN_REQ_FLT_END;
|
|
|
|
if (s->sess->fe == s->be) /* report it if the request was intercepted by the frontend */
|
|
_HA_ATOMIC_INC(&s->sess->fe->fe_counters.intercepted_req);
|
|
|
|
if (!(s->flags & SF_ERR_MASK))
|
|
s->flags |= SF_ERR_LOCAL;
|
|
if (!(s->flags & SF_FINST_MASK))
|
|
s->flags |= SF_FINST_R;
|
|
|
|
stream_inc_http_err_ctr(s);
|
|
return ACT_RET_ABRT;
|
|
|
|
fail:
|
|
/* If an error occurred, remove the incomplete HTTP response from the
|
|
* buffer */
|
|
channel_htx_truncate(res, htx);
|
|
return ACT_RET_ERR;
|
|
}
|
|
|
|
/* Parse a "auth" action. It may take 2 optional arguments to define a "realm"
|
|
* parameter. It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
|
|
*/
|
|
static enum act_parse_ret parse_http_auth(const char **args, int *orig_arg, struct proxy *px,
|
|
struct act_rule *rule, char **err)
|
|
{
|
|
int cur_arg;
|
|
|
|
rule->action = ACT_CUSTOM;
|
|
rule->flags |= ACT_FLAG_FINAL;
|
|
rule->action_ptr = http_action_auth;
|
|
rule->release_ptr = release_http_action;
|
|
lf_expr_init(&rule->arg.http.fmt);
|
|
|
|
cur_arg = *orig_arg;
|
|
if (strcmp(args[cur_arg], "realm") == 0) {
|
|
cur_arg++;
|
|
if (!*args[cur_arg]) {
|
|
memprintf(err, "missing realm value.\n");
|
|
return ACT_RET_PRS_ERR;
|
|
}
|
|
rule->arg.http.str = ist(strdup(args[cur_arg]));
|
|
cur_arg++;
|
|
}
|
|
|
|
*orig_arg = cur_arg;
|
|
return ACT_RET_PRS_OK;
|
|
}
|
|
|
|
/* This function executes a early-hint action. It adds an HTTP Early Hint HTTP
|
|
* 103 response header with <.arg.http.str> name and with a value built
|
|
* according to <.arg.http.fmt> log line format. If it is the first early-hint
|
|
* rule of series, the 103 response start-line is added first. At the end, if
|
|
* the next rule is not an early-hint rule or if it is the last rule, the EOH
|
|
* block is added to terminate the response. On success, it returns
|
|
* ACT_RET_CONT. If an error occurs while soft rewrites are enabled, the action
|
|
* is canceled, but the rule processing continue. Otherwsize ACT_RET_ERR is
|
|
* returned.
|
|
*/
|
|
static enum act_return http_action_early_hint(struct act_rule *rule, struct proxy *px,
|
|
struct session *sess, struct stream *s, int flags)
|
|
{
|
|
struct act_rule *next_rule;
|
|
struct channel *res = &s->res;
|
|
struct htx *htx = htx_from_buf(&res->buf);
|
|
struct buffer *value = alloc_trash_chunk();
|
|
enum act_return ret = ACT_RET_CONT;
|
|
|
|
if (!(s->txn->req.flags & HTTP_MSGF_VER_11))
|
|
goto leave;
|
|
|
|
if (!value) {
|
|
if (!(s->flags & SF_ERR_MASK))
|
|
s->flags |= SF_ERR_RESOURCE;
|
|
goto error;
|
|
}
|
|
|
|
/* if there is no pending 103 response, start a new response. Otherwise,
|
|
* continue to add link to a previously started response
|
|
*/
|
|
if (s->txn->status != 103) {
|
|
struct htx_sl *sl;
|
|
unsigned int flags = (HTX_SL_F_IS_RESP|HTX_SL_F_VER_11|
|
|
HTX_SL_F_XFER_LEN|HTX_SL_F_BODYLESS);
|
|
|
|
sl = htx_add_stline(htx, HTX_BLK_RES_SL, flags,
|
|
ist("HTTP/1.1"), ist("103"), ist("Early Hints"));
|
|
if (!sl)
|
|
goto error;
|
|
sl->info.res.status = 103;
|
|
s->txn->status = 103;
|
|
}
|
|
|
|
/* Add the HTTP Early Hint HTTP 103 response header */
|
|
value->data = build_logline(s, b_tail(value), b_room(value), &rule->arg.http.fmt);
|
|
if (!htx_add_header(htx, rule->arg.http.str, ist2(b_head(value), b_data(value))))
|
|
goto error;
|
|
|
|
/* if it is the last rule or the next one is not an early-hint or an
|
|
* conditional early-hint, terminate the current response.
|
|
*/
|
|
next_rule = LIST_NEXT(&rule->list, typeof(rule), list);
|
|
if (&next_rule->list == s->current_rule_list || next_rule->action_ptr != http_action_early_hint || next_rule->cond) {
|
|
if (!htx_add_endof(htx, HTX_BLK_EOH))
|
|
goto error;
|
|
if (!http_forward_proxy_resp(s, 0))
|
|
goto error;
|
|
s->txn->status = 0;
|
|
}
|
|
|
|
leave:
|
|
free_trash_chunk(value);
|
|
return ret;
|
|
|
|
error:
|
|
/* If an error occurred during an Early-hint rule, remove the incomplete
|
|
* HTTP 103 response from the buffer */
|
|
channel_htx_truncate(res, htx);
|
|
ret = ACT_RET_ERR;
|
|
s->txn->status = 0;
|
|
goto leave;
|
|
}
|
|
|
|
/* This function executes a set-header or add-header actions. It builds a string
|
|
* in the trash from the specified format string. It finds the action to be
|
|
* performed in <.action>, previously filled by function parse_set_header(). The
|
|
* replacement action is executed by the function http_action_set_header(). On
|
|
* success, it returns ACT_RET_CONT. If an error occurs while soft rewrites are
|
|
* enabled, the action is canceled, but the rule processing continue. Otherwsize
|
|
* ACT_RET_ERR is returned.
|
|
*/
|
|
static enum act_return http_action_set_header(struct act_rule *rule, struct proxy *px,
|
|
struct session *sess, struct stream *s, int flags)
|
|
{
|
|
struct http_msg *msg = ((rule->from == ACT_F_HTTP_REQ) ? &s->txn->req : &s->txn->rsp);
|
|
struct htx *htx = htxbuf(&msg->chn->buf);
|
|
enum act_return ret = ACT_RET_CONT;
|
|
struct buffer *replace;
|
|
struct http_hdr_ctx ctx;
|
|
struct ist n, v;
|
|
|
|
replace = alloc_trash_chunk();
|
|
if (!replace)
|
|
goto fail_alloc;
|
|
|
|
replace->data = build_logline(s, replace->area, replace->size, &rule->arg.http.fmt);
|
|
n = rule->arg.http.str;
|
|
v = ist2(replace->area, replace->data);
|
|
|
|
if (rule->action == 0) { // set-header
|
|
/* remove all occurrences of the header */
|
|
ctx.blk = NULL;
|
|
while (http_find_header(htx, n, &ctx, 1))
|
|
http_remove_header(htx, &ctx);
|
|
}
|
|
|
|
/* Now add header */
|
|
if (!http_add_header(htx, n, v))
|
|
goto fail_rewrite;
|
|
|
|
leave:
|
|
free_trash_chunk(replace);
|
|
return ret;
|
|
|
|
fail_alloc:
|
|
if (!(s->flags & SF_ERR_MASK))
|
|
s->flags |= SF_ERR_RESOURCE;
|
|
ret = ACT_RET_ERR;
|
|
goto leave;
|
|
|
|
fail_rewrite:
|
|
_HA_ATOMIC_INC(&sess->fe->fe_counters.failed_rewrites);
|
|
if (s->flags & SF_BE_ASSIGNED)
|
|
_HA_ATOMIC_INC(&s->be->be_counters.failed_rewrites);
|
|
if (sess->listener && sess->listener->counters)
|
|
_HA_ATOMIC_INC(&sess->listener->counters->failed_rewrites);
|
|
if (objt_server(s->target))
|
|
_HA_ATOMIC_INC(&__objt_server(s->target)->counters.failed_rewrites);
|
|
|
|
if (!(msg->flags & HTTP_MSGF_SOFT_RW)) {
|
|
ret = ACT_RET_ERR;
|
|
if (!(s->flags & SF_ERR_MASK))
|
|
s->flags |= SF_ERR_PRXCOND;
|
|
}
|
|
goto leave;
|
|
}
|
|
|
|
/* Parse a "set-header", "add-header" or "early-hint" actions. It takes an
|
|
* header name and a log-format string as arguments. It returns ACT_RET_PRS_OK
|
|
* on success, ACT_RET_PRS_ERR on error.
|
|
*
|
|
* Note: same function is used for the request and the response. However
|
|
* "early-hint" rules are only supported for request rules.
|
|
*/
|
|
static enum act_parse_ret parse_http_set_header(const char **args, int *orig_arg, struct proxy *px,
|
|
struct act_rule *rule, char **err)
|
|
{
|
|
int cap = 0, cur_arg;
|
|
const char *p;
|
|
|
|
if (args[*orig_arg-1][0] == 'e') {
|
|
rule->action = ACT_CUSTOM;
|
|
rule->action_ptr = http_action_early_hint;
|
|
}
|
|
else {
|
|
if (args[*orig_arg-1][0] == 's')
|
|
rule->action = 0; // set-header
|
|
else
|
|
rule->action = 1; // add-header
|
|
rule->action_ptr = http_action_set_header;
|
|
}
|
|
rule->release_ptr = release_http_action;
|
|
lf_expr_init(&rule->arg.http.fmt);
|
|
|
|
cur_arg = *orig_arg;
|
|
if (!*args[cur_arg] || !*args[cur_arg+1]) {
|
|
memprintf(err, "expects exactly 2 arguments");
|
|
return ACT_RET_PRS_ERR;
|
|
}
|
|
|
|
|
|
rule->arg.http.str = ist(strdup(args[cur_arg]));
|
|
|
|
if (rule->from == ACT_F_HTTP_REQ) {
|
|
px->conf.args.ctx = ARGC_HRQ;
|
|
if (px->cap & PR_CAP_FE)
|
|
cap |= SMP_VAL_FE_HRQ_HDR;
|
|
if (px->cap & PR_CAP_BE)
|
|
cap |= SMP_VAL_BE_HRQ_HDR;
|
|
}
|
|
else{
|
|
px->conf.args.ctx = ARGC_HRS;
|
|
if (px->cap & PR_CAP_FE)
|
|
cap |= SMP_VAL_FE_HRS_HDR;
|
|
if (px->cap & PR_CAP_BE)
|
|
cap |= SMP_VAL_BE_HRS_HDR;
|
|
}
|
|
|
|
cur_arg++;
|
|
if (!parse_logformat_string(args[cur_arg], px, &rule->arg.http.fmt, LOG_OPT_HTTP, cap, err)) {
|
|
istfree(&rule->arg.http.str);
|
|
return ACT_RET_PRS_ERR;
|
|
}
|
|
|
|
free(px->conf.lfs_file);
|
|
px->conf.lfs_file = strdup(px->conf.args.file);
|
|
px->conf.lfs_line = px->conf.args.line;
|
|
|
|
/* some characters are totally forbidden in header names and
|
|
* may happen by accident when writing configs, causing strange
|
|
* failures in field. Better catch these ones early, nobody will
|
|
* miss them. In particular, a colon at the end (or anywhere
|
|
* after the first char) or a space/cr anywhere due to misplaced
|
|
* quotes are hard to spot.
|
|
*/
|
|
for (p = istptr(rule->arg.http.str); p < istend(rule->arg.http.str); p++) {
|
|
if (HTTP_IS_TOKEN(*p))
|
|
continue;
|
|
if (p == istptr(rule->arg.http.str) && *p == ':')
|
|
continue;
|
|
/* we only report this as-is but it will not cause an error */
|
|
memprintf(err, "header name '%s' contains forbidden character '%c'", istptr(rule->arg.http.str), *p);
|
|
break;
|
|
}
|
|
|
|
*orig_arg = cur_arg + 1;
|
|
return ACT_RET_PRS_OK;
|
|
}
|
|
|
|
/* This function executes a replace-header or replace-value actions. It
|
|
* builds a string in the trash from the specified format string. It finds
|
|
* the action to be performed in <.action>, previously filled by function
|
|
* parse_replace_header(). The replacement action is executed by the function
|
|
* http_action_replace_header(). On success, it returns ACT_RET_CONT. If an error
|
|
* occurs while soft rewrites are enabled, the action is canceled, but the rule
|
|
* processing continue. Otherwsize ACT_RET_ERR is returned.
|
|
*/
|
|
static enum act_return http_action_replace_header(struct act_rule *rule, struct proxy *px,
|
|
struct session *sess, struct stream *s, int flags)
|
|
{
|
|
struct http_msg *msg = ((rule->from == ACT_F_HTTP_REQ) ? &s->txn->req : &s->txn->rsp);
|
|
struct htx *htx = htxbuf(&msg->chn->buf);
|
|
enum act_return ret = ACT_RET_CONT;
|
|
struct buffer *replace;
|
|
int r;
|
|
|
|
replace = alloc_trash_chunk();
|
|
if (!replace)
|
|
goto fail_alloc;
|
|
|
|
replace->data = build_logline(s, replace->area, replace->size, &rule->arg.http.fmt);
|
|
|
|
r = http_replace_hdrs(s, htx, rule->arg.http.str, replace->area, rule->arg.http.re, (rule->action == 0));
|
|
if (r == -1)
|
|
goto fail_rewrite;
|
|
|
|
leave:
|
|
free_trash_chunk(replace);
|
|
return ret;
|
|
|
|
fail_alloc:
|
|
if (!(s->flags & SF_ERR_MASK))
|
|
s->flags |= SF_ERR_RESOURCE;
|
|
ret = ACT_RET_ERR;
|
|
goto leave;
|
|
|
|
fail_rewrite:
|
|
_HA_ATOMIC_INC(&sess->fe->fe_counters.failed_rewrites);
|
|
if (s->flags & SF_BE_ASSIGNED)
|
|
_HA_ATOMIC_INC(&s->be->be_counters.failed_rewrites);
|
|
if (sess->listener && sess->listener->counters)
|
|
_HA_ATOMIC_INC(&sess->listener->counters->failed_rewrites);
|
|
if (objt_server(s->target))
|
|
_HA_ATOMIC_INC(&__objt_server(s->target)->counters.failed_rewrites);
|
|
|
|
if (!(msg->flags & HTTP_MSGF_SOFT_RW)) {
|
|
ret = ACT_RET_ERR;
|
|
if (!(s->flags & SF_ERR_MASK))
|
|
s->flags |= SF_ERR_PRXCOND;
|
|
}
|
|
goto leave;
|
|
}
|
|
|
|
/* Parse a "replace-header" or "replace-value" actions. It takes an header name,
|
|
* a regex and replacement string as arguments. It returns ACT_RET_PRS_OK on
|
|
* success, ACT_RET_PRS_ERR on error.
|
|
*/
|
|
static enum act_parse_ret parse_http_replace_header(const char **args, int *orig_arg, struct proxy *px,
|
|
struct act_rule *rule, char **err)
|
|
{
|
|
int cap = 0, cur_arg;
|
|
|
|
if (args[*orig_arg-1][8] == 'h')
|
|
rule->action = 0; // replace-header
|
|
else
|
|
rule->action = 1; // replace-value
|
|
rule->action_ptr = http_action_replace_header;
|
|
rule->release_ptr = release_http_action;
|
|
lf_expr_init(&rule->arg.http.fmt);
|
|
|
|
cur_arg = *orig_arg;
|
|
if (!*args[cur_arg] || !*args[cur_arg+1] || !*args[cur_arg+2]) {
|
|
memprintf(err, "expects exactly 3 arguments");
|
|
return ACT_RET_PRS_ERR;
|
|
}
|
|
|
|
rule->arg.http.str = ist(strdup(args[cur_arg]));
|
|
|
|
cur_arg++;
|
|
if (!(rule->arg.http.re = regex_comp(args[cur_arg], 1, 1, err))) {
|
|
istfree(&rule->arg.http.str);
|
|
return ACT_RET_PRS_ERR;
|
|
}
|
|
|
|
if (rule->from == ACT_F_HTTP_REQ) {
|
|
px->conf.args.ctx = ARGC_HRQ;
|
|
if (px->cap & PR_CAP_FE)
|
|
cap |= SMP_VAL_FE_HRQ_HDR;
|
|
if (px->cap & PR_CAP_BE)
|
|
cap |= SMP_VAL_BE_HRQ_HDR;
|
|
}
|
|
else{
|
|
px->conf.args.ctx = ARGC_HRS;
|
|
if (px->cap & PR_CAP_FE)
|
|
cap |= SMP_VAL_FE_HRS_HDR;
|
|
if (px->cap & PR_CAP_BE)
|
|
cap |= SMP_VAL_BE_HRS_HDR;
|
|
}
|
|
|
|
cur_arg++;
|
|
if (!parse_logformat_string(args[cur_arg], px, &rule->arg.http.fmt, LOG_OPT_HTTP, cap, err)) {
|
|
istfree(&rule->arg.http.str);
|
|
regex_free(rule->arg.http.re);
|
|
return ACT_RET_PRS_ERR;
|
|
}
|
|
|
|
free(px->conf.lfs_file);
|
|
px->conf.lfs_file = strdup(px->conf.args.file);
|
|
px->conf.lfs_line = px->conf.args.line;
|
|
|
|
*orig_arg = cur_arg + 1;
|
|
return ACT_RET_PRS_OK;
|
|
}
|
|
|
|
/* This function executes a del-header action with selected matching mode for
|
|
* header name. It finds the matching method to be performed in <.action>, previously
|
|
* filled by function parse_http_del_header(). On success, it returns ACT_RET_CONT.
|
|
* Otherwise ACT_RET_ERR is returned.
|
|
*/
|
|
static enum act_return http_action_del_header(struct act_rule *rule, struct proxy *px,
|
|
struct session *sess, struct stream *s, int flags)
|
|
{
|
|
struct http_hdr_ctx ctx;
|
|
struct http_msg *msg = ((rule->from == ACT_F_HTTP_REQ) ? &s->txn->req : &s->txn->rsp);
|
|
struct htx *htx = htxbuf(&msg->chn->buf);
|
|
enum act_return ret = ACT_RET_CONT;
|
|
|
|
/* remove all occurrences of the header */
|
|
ctx.blk = NULL;
|
|
switch (rule->action) {
|
|
case PAT_MATCH_STR:
|
|
while (http_find_header(htx, rule->arg.http.str, &ctx, 1))
|
|
http_remove_header(htx, &ctx);
|
|
break;
|
|
case PAT_MATCH_BEG:
|
|
while (http_find_pfx_header(htx, rule->arg.http.str, &ctx, 1))
|
|
http_remove_header(htx, &ctx);
|
|
break;
|
|
case PAT_MATCH_END:
|
|
while (http_find_sfx_header(htx, rule->arg.http.str, &ctx, 1))
|
|
http_remove_header(htx, &ctx);
|
|
break;
|
|
case PAT_MATCH_SUB:
|
|
while (http_find_sub_header(htx, rule->arg.http.str, &ctx, 1))
|
|
http_remove_header(htx, &ctx);
|
|
break;
|
|
case PAT_MATCH_REG:
|
|
while (http_match_header(htx, rule->arg.http.re, &ctx, 1))
|
|
http_remove_header(htx, &ctx);
|
|
break;
|
|
default:
|
|
return ACT_RET_ERR;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/* Parse a "del-header" action. It takes string as a required argument,
|
|
* optional flag (currently only -m) and optional matching method of input string
|
|
* with header name to be deleted. Default matching method is exact match (-m str).
|
|
* It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
|
|
*/
|
|
static enum act_parse_ret parse_http_del_header(const char **args, int *orig_arg, struct proxy *px,
|
|
struct act_rule *rule, char **err)
|
|
{
|
|
int cur_arg;
|
|
int pat_idx;
|
|
|
|
/* set exact matching (-m str) as default */
|
|
rule->action = PAT_MATCH_STR;
|
|
rule->action_ptr = http_action_del_header;
|
|
rule->release_ptr = release_http_action;
|
|
lf_expr_init(&rule->arg.http.fmt);
|
|
|
|
cur_arg = *orig_arg;
|
|
if (!*args[cur_arg]) {
|
|
memprintf(err, "expects at least 1 argument");
|
|
return ACT_RET_PRS_ERR;
|
|
}
|
|
|
|
rule->arg.http.str = ist(strdup(args[cur_arg]));
|
|
px->conf.args.ctx = (rule->from == ACT_F_HTTP_REQ ? ARGC_HRQ : ARGC_HRS);
|
|
|
|
if (strcmp(args[cur_arg+1], "-m") == 0) {
|
|
cur_arg++;
|
|
if (!*args[cur_arg+1]) {
|
|
memprintf(err, "-m flag expects exactly 1 argument");
|
|
return ACT_RET_PRS_ERR;
|
|
}
|
|
|
|
cur_arg++;
|
|
pat_idx = pat_find_match_name(args[cur_arg]);
|
|
switch (pat_idx) {
|
|
case PAT_MATCH_REG:
|
|
if (!(rule->arg.http.re = regex_comp(rule->arg.http.str.ptr, 1, 1, err)))
|
|
return ACT_RET_PRS_ERR;
|
|
__fallthrough;
|
|
case PAT_MATCH_STR:
|
|
case PAT_MATCH_BEG:
|
|
case PAT_MATCH_END:
|
|
case PAT_MATCH_SUB:
|
|
rule->action = pat_idx;
|
|
break;
|
|
default:
|
|
memprintf(err, "-m with unsupported matching method '%s'", args[cur_arg]);
|
|
return ACT_RET_PRS_ERR;
|
|
}
|
|
}
|
|
|
|
*orig_arg = cur_arg + 1;
|
|
return ACT_RET_PRS_OK;
|
|
}
|
|
|
|
/* Release memory allocated by an http redirect action. */
|
|
static void release_http_redir(struct act_rule *rule)
|
|
{
|
|
struct redirect_rule *redir;
|
|
|
|
redir = rule->arg.redir;
|
|
if (!redir)
|
|
return;
|
|
|
|
LIST_DELETE(&redir->list);
|
|
http_free_redirect_rule(redir);
|
|
}
|
|
|
|
/* Parse a "redirect" action. It returns ACT_RET_PRS_OK on success,
|
|
* ACT_RET_PRS_ERR on error.
|
|
*/
|
|
static enum act_parse_ret parse_http_redirect(const char **args, int *orig_arg, struct proxy *px,
|
|
struct act_rule *rule, char **err)
|
|
{
|
|
struct redirect_rule *redir;
|
|
int dir, cur_arg;
|
|
|
|
rule->action = ACT_HTTP_REDIR;
|
|
rule->release_ptr = release_http_redir;
|
|
|
|
cur_arg = *orig_arg;
|
|
|
|
dir = (rule->from == ACT_F_HTTP_REQ ? 0 : 1);
|
|
if ((redir = http_parse_redirect_rule(px->conf.args.file, px->conf.args.line, px, &args[cur_arg], err, 1, dir)) == NULL)
|
|
return ACT_RET_PRS_ERR;
|
|
|
|
if (!(redir->flags & REDIRECT_FLAG_IGNORE_EMPTY))
|
|
rule->flags |= ACT_FLAG_FINAL;
|
|
|
|
rule->arg.redir = redir;
|
|
rule->cond = redir->cond;
|
|
redir->cond = NULL;
|
|
|
|
/* skip all arguments */
|
|
while (*args[cur_arg])
|
|
cur_arg++;
|
|
|
|
*orig_arg = cur_arg;
|
|
return ACT_RET_PRS_OK;
|
|
}
|
|
|
|
/* This function executes a add-acl, del-acl, set-map or del-map actions. On
|
|
* success, it returns ACT_RET_CONT. Otherwsize ACT_RET_ERR is returned.
|
|
*/
|
|
static enum act_return http_action_set_map(struct act_rule *rule, struct proxy *px,
|
|
struct session *sess, struct stream *s, int flags)
|
|
{
|
|
struct pat_ref *ref;
|
|
struct buffer *key = NULL, *value = NULL;
|
|
enum act_return ret = ACT_RET_CONT;
|
|
|
|
/* collect reference */
|
|
ref = pat_ref_lookup(rule->arg.map.ref);
|
|
if (!ref)
|
|
goto leave;
|
|
|
|
/* allocate key */
|
|
key = alloc_trash_chunk();
|
|
if (!key)
|
|
goto fail_alloc;
|
|
|
|
/* collect key */
|
|
key->data = build_logline(s, key->area, key->size, &rule->arg.map.key);
|
|
key->area[key->data] = '\0';
|
|
|
|
switch (rule->action) {
|
|
case 0: // add-acl
|
|
/* add entry only if it does not already exist */
|
|
HA_RWLOCK_WRLOCK(PATREF_LOCK, &ref->lock);
|
|
if (pat_ref_find_elt(ref, key->area) == NULL)
|
|
pat_ref_add(ref, key->area, NULL, NULL);
|
|
HA_RWLOCK_WRUNLOCK(PATREF_LOCK, &ref->lock);
|
|
break;
|
|
|
|
case 1: // set-map
|
|
{
|
|
struct pat_ref_elt *elt;
|
|
|
|
/* allocate value */
|
|
value = alloc_trash_chunk();
|
|
if (!value)
|
|
goto fail_alloc;
|
|
|
|
/* collect value */
|
|
value->data = build_logline(s, value->area, value->size, &rule->arg.map.value);
|
|
value->area[value->data] = '\0';
|
|
|
|
HA_RWLOCK_WRLOCK(PATREF_LOCK, &ref->lock);
|
|
elt = pat_ref_find_elt(ref, key->area);
|
|
if (elt) {
|
|
/* update entry if it exists */
|
|
pat_ref_set(ref, key->area, value->area, NULL, elt);
|
|
}
|
|
else {
|
|
/* insert a new entry */
|
|
pat_ref_add(ref, key->area, value->area, NULL);
|
|
}
|
|
HA_RWLOCK_WRUNLOCK(PATREF_LOCK, &ref->lock);
|
|
break;
|
|
}
|
|
|
|
case 2: // del-acl
|
|
case 3: // del-map
|
|
/* returned code: 1=ok, 0=ko */
|
|
HA_RWLOCK_WRLOCK(PATREF_LOCK, &ref->lock);
|
|
pat_ref_delete(ref, key->area);
|
|
HA_RWLOCK_WRUNLOCK(PATREF_LOCK, &ref->lock);
|
|
break;
|
|
|
|
default:
|
|
ret = ACT_RET_ERR;
|
|
}
|
|
|
|
|
|
leave:
|
|
free_trash_chunk(key);
|
|
free_trash_chunk(value);
|
|
return ret;
|
|
|
|
fail_alloc:
|
|
if (!(s->flags & SF_ERR_MASK))
|
|
s->flags |= SF_ERR_RESOURCE;
|
|
ret = ACT_RET_ERR;
|
|
goto leave;
|
|
}
|
|
|
|
/* Release memory allocated by an http map/acl action. */
|
|
static void release_http_map(struct act_rule *rule)
|
|
{
|
|
free(rule->arg.map.ref);
|
|
lf_expr_deinit(&rule->arg.map.key);
|
|
if (rule->action == 1)
|
|
lf_expr_deinit(&rule->arg.map.value);
|
|
}
|
|
|
|
/* Parse a "add-acl", "del-acl", "set-map" or "del-map" actions. It takes one or
|
|
* two log-format string as argument depending on the action. The action is
|
|
* stored in <.action> as an int (0=add-acl, 1=set-map, 2=del-acl,
|
|
* 3=del-map). It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
|
|
*/
|
|
static enum act_parse_ret parse_http_set_map(const char **args, int *orig_arg, struct proxy *px,
|
|
struct act_rule *rule, char **err)
|
|
{
|
|
int cap = 0, cur_arg;
|
|
|
|
if (args[*orig_arg-1][0] == 'a') // add-acl
|
|
rule->action = 0;
|
|
else if (args[*orig_arg-1][0] == 's') // set-map
|
|
rule->action = 1;
|
|
else if (args[*orig_arg-1][4] == 'a') // del-acl
|
|
rule->action = 2;
|
|
else if (args[*orig_arg-1][4] == 'm') // del-map
|
|
rule->action = 3;
|
|
else {
|
|
memprintf(err, "internal error: unhandled action '%s'", args[0]);
|
|
return ACT_RET_PRS_ERR;
|
|
}
|
|
rule->action_ptr = http_action_set_map;
|
|
rule->release_ptr = release_http_map;
|
|
|
|
cur_arg = *orig_arg;
|
|
if (rule->action == 1 && (!*args[cur_arg] || !*args[cur_arg+1])) {
|
|
/* 2 args for set-map */
|
|
memprintf(err, "expects exactly 2 arguments");
|
|
return ACT_RET_PRS_ERR;
|
|
}
|
|
else if (!*args[cur_arg]) {
|
|
/* only one arg for other actions */
|
|
memprintf(err, "expects exactly 1 arguments");
|
|
return ACT_RET_PRS_ERR;
|
|
}
|
|
|
|
/*
|
|
* '+ 8' for 'set-map(' (same for del-map)
|
|
* '- 9' for 'set-map(' + trailing ')' (same for del-map)
|
|
*/
|
|
rule->arg.map.ref = my_strndup(args[cur_arg-1] + 8, strlen(args[cur_arg-1]) - 9);
|
|
|
|
if (rule->from == ACT_F_HTTP_REQ) {
|
|
px->conf.args.ctx = ARGC_HRQ;
|
|
if (px->cap & PR_CAP_FE)
|
|
cap |= SMP_VAL_FE_HRQ_HDR;
|
|
if (px->cap & PR_CAP_BE)
|
|
cap |= SMP_VAL_BE_HRQ_HDR;
|
|
}
|
|
else{
|
|
px->conf.args.ctx = ARGC_HRS;
|
|
if (px->cap & PR_CAP_FE)
|
|
cap |= SMP_VAL_FE_HRS_HDR;
|
|
if (px->cap & PR_CAP_BE)
|
|
cap |= SMP_VAL_BE_HRS_HDR;
|
|
}
|
|
|
|
/* key pattern */
|
|
lf_expr_init(&rule->arg.map.key);
|
|
if (!parse_logformat_string(args[cur_arg], px, &rule->arg.map.key, LOG_OPT_HTTP, cap, err)) {
|
|
free(rule->arg.map.ref);
|
|
return ACT_RET_PRS_ERR;
|
|
}
|
|
|
|
if (rule->action == 1) {
|
|
/* value pattern for set-map only */
|
|
cur_arg++;
|
|
lf_expr_init(&rule->arg.map.value);
|
|
if (!parse_logformat_string(args[cur_arg], px, &rule->arg.map.value, LOG_OPT_HTTP, cap, err)) {
|
|
free(rule->arg.map.ref);
|
|
return ACT_RET_PRS_ERR;
|
|
}
|
|
}
|
|
|
|
free(px->conf.lfs_file);
|
|
px->conf.lfs_file = strdup(px->conf.args.file);
|
|
px->conf.lfs_line = px->conf.args.line;
|
|
|
|
*orig_arg = cur_arg + 1;
|
|
return ACT_RET_PRS_OK;
|
|
}
|
|
|
|
/* This function executes a track-sc* actions. On success, it returns
|
|
* ACT_RET_CONT. Otherwsize ACT_RET_ERR is returned.
|
|
*/
|
|
static enum act_return http_action_track_sc(struct act_rule *rule, struct proxy *px,
|
|
struct session *sess, struct stream *s, int flags)
|
|
{
|
|
struct stktable *t;
|
|
struct stksess *ts;
|
|
struct stktable_key *key;
|
|
void *ptr1, *ptr2, *ptr3, *ptr4, *ptr5, *ptr6;
|
|
int opt;
|
|
|
|
ptr1 = ptr2 = ptr3 = ptr4 = ptr5 = ptr6 = NULL;
|
|
opt = ((rule->from == ACT_F_HTTP_REQ) ? SMP_OPT_DIR_REQ : SMP_OPT_DIR_RES) | SMP_OPT_FINAL;
|
|
|
|
t = rule->arg.trk_ctr.table.t;
|
|
|
|
if (stkctr_entry(&s->stkctr[rule->action]))
|
|
goto end;
|
|
|
|
key = stktable_fetch_key(t, s->be, sess, s, opt, rule->arg.trk_ctr.expr, NULL);
|
|
|
|
if (!key)
|
|
goto end;
|
|
ts = stktable_get_entry(t, key);
|
|
if (!ts)
|
|
goto end;
|
|
|
|
stream_track_stkctr(&s->stkctr[rule->action], t, ts);
|
|
|
|
/* let's count a new HTTP request as it's the first time we do it */
|
|
ptr1 = stktable_data_ptr(t, ts, STKTABLE_DT_HTTP_REQ_CNT);
|
|
ptr2 = stktable_data_ptr(t, ts, STKTABLE_DT_HTTP_REQ_RATE);
|
|
|
|
/* When the client triggers a 4xx from the server, it's most often due
|
|
* to a missing object or permission. These events should be tracked
|
|
* because if they happen often, it may indicate a brute force or a
|
|
* vulnerability scan. Normally this is done when receiving the response
|
|
* but here we're tracking after this ought to have been done so we have
|
|
* to do it on purpose.
|
|
*/
|
|
if (rule->from == ACT_F_HTTP_RES &&
|
|
http_status_matches(http_err_status_codes, s->txn->status)) {
|
|
ptr3 = stktable_data_ptr(t, ts, STKTABLE_DT_HTTP_ERR_CNT);
|
|
ptr4 = stktable_data_ptr(t, ts, STKTABLE_DT_HTTP_ERR_RATE);
|
|
}
|
|
|
|
if (rule->from == ACT_F_HTTP_RES &&
|
|
http_status_matches(http_fail_status_codes, s->txn->status)) {
|
|
ptr5 = stktable_data_ptr(t, ts, STKTABLE_DT_HTTP_FAIL_CNT);
|
|
ptr6 = stktable_data_ptr(t, ts, STKTABLE_DT_HTTP_FAIL_RATE);
|
|
}
|
|
|
|
if (ptr1 || ptr2 || ptr3 || ptr4 || ptr5 || ptr6) {
|
|
HA_RWLOCK_WRLOCK(STK_SESS_LOCK, &ts->lock);
|
|
|
|
if (ptr1)
|
|
stktable_data_cast(ptr1, std_t_uint)++;
|
|
if (ptr2)
|
|
update_freq_ctr_period(&stktable_data_cast(ptr2, std_t_frqp),
|
|
t->data_arg[STKTABLE_DT_HTTP_REQ_RATE].u, 1);
|
|
if (ptr3)
|
|
stktable_data_cast(ptr3, std_t_uint)++;
|
|
if (ptr4)
|
|
update_freq_ctr_period(&stktable_data_cast(ptr4, std_t_frqp),
|
|
t->data_arg[STKTABLE_DT_HTTP_ERR_RATE].u, 1);
|
|
if (ptr5)
|
|
stktable_data_cast(ptr5, std_t_uint)++;
|
|
if (ptr6)
|
|
update_freq_ctr_period(&stktable_data_cast(ptr6, std_t_frqp),
|
|
t->data_arg[STKTABLE_DT_HTTP_FAIL_RATE].u, 1);
|
|
|
|
HA_RWLOCK_WRUNLOCK(STK_SESS_LOCK, &ts->lock);
|
|
|
|
/* If data was modified, we need to touch to re-schedule sync */
|
|
stktable_touch_local(t, ts, 0);
|
|
}
|
|
|
|
stkctr_set_flags(&s->stkctr[rule->action], STKCTR_TRACK_CONTENT);
|
|
if (sess->fe != s->be)
|
|
stkctr_set_flags(&s->stkctr[rule->action], STKCTR_TRACK_BACKEND);
|
|
|
|
end:
|
|
return ACT_RET_CONT;
|
|
}
|
|
|
|
static void release_http_track_sc(struct act_rule *rule)
|
|
{
|
|
release_sample_expr(rule->arg.trk_ctr.expr);
|
|
}
|
|
|
|
/* Parse a "track-sc*" actions. It returns ACT_RET_PRS_OK on success,
|
|
* ACT_RET_PRS_ERR on error.
|
|
*/
|
|
static enum act_parse_ret parse_http_track_sc(const char **args, int *orig_arg, struct proxy *px,
|
|
struct act_rule *rule, char **err)
|
|
{
|
|
struct sample_expr *expr;
|
|
unsigned int where;
|
|
unsigned int tsc_num;
|
|
const char *tsc_num_str;
|
|
int cur_arg;
|
|
|
|
tsc_num_str = &args[*orig_arg-1][8];
|
|
if (cfg_parse_track_sc_num(&tsc_num, tsc_num_str, tsc_num_str + strlen(tsc_num_str), err) == -1)
|
|
return ACT_RET_PRS_ERR;
|
|
|
|
cur_arg = *orig_arg;
|
|
expr = sample_parse_expr((char **)args, &cur_arg, px->conf.args.file, px->conf.args.line,
|
|
err, &px->conf.args, NULL);
|
|
if (!expr)
|
|
return ACT_RET_PRS_ERR;
|
|
|
|
where = 0;
|
|
if (px->cap & PR_CAP_FE)
|
|
where |= (rule->from == ACT_F_HTTP_REQ ? SMP_VAL_FE_HRQ_HDR : SMP_VAL_FE_HRS_HDR);
|
|
if (px->cap & PR_CAP_BE)
|
|
where |= (rule->from == ACT_F_HTTP_REQ ? SMP_VAL_BE_HRQ_HDR : SMP_VAL_BE_HRS_HDR);
|
|
|
|
if (!(expr->fetch->val & where)) {
|
|
memprintf(err, "fetch method '%s' extracts information from '%s', none of which is available here",
|
|
args[cur_arg-1], sample_src_names(expr->fetch->use));
|
|
release_sample_expr(expr);
|
|
return ACT_RET_PRS_ERR;
|
|
}
|
|
|
|
if (strcmp(args[cur_arg], "table") == 0) {
|
|
cur_arg++;
|
|
if (!*args[cur_arg]) {
|
|
memprintf(err, "missing table name");
|
|
release_sample_expr(expr);
|
|
return ACT_RET_PRS_ERR;
|
|
}
|
|
|
|
/* we copy the table name for now, it will be resolved later */
|
|
rule->arg.trk_ctr.table.n = strdup(args[cur_arg]);
|
|
cur_arg++;
|
|
}
|
|
|
|
rule->action = tsc_num;
|
|
rule->arg.trk_ctr.expr = expr;
|
|
rule->action_ptr = http_action_track_sc;
|
|
rule->release_ptr = release_http_track_sc;
|
|
rule->check_ptr = check_trk_action;
|
|
|
|
*orig_arg = cur_arg;
|
|
return ACT_RET_PRS_OK;
|
|
}
|
|
|
|
static enum act_return action_timeout_set_stream_timeout(struct act_rule *rule,
|
|
struct proxy *px,
|
|
struct session *sess,
|
|
struct stream *s,
|
|
int flags)
|
|
{
|
|
struct sample *key;
|
|
|
|
if (rule->arg.timeout.expr) {
|
|
key = sample_fetch_as_type(px, sess, s, SMP_OPT_FINAL, rule->arg.timeout.expr, SMP_T_SINT);
|
|
if (!key)
|
|
return ACT_RET_CONT;
|
|
|
|
stream_set_timeout(s, rule->arg.timeout.type, MS_TO_TICKS(key->data.u.sint));
|
|
}
|
|
else {
|
|
stream_set_timeout(s, rule->arg.timeout.type, MS_TO_TICKS(rule->arg.timeout.value));
|
|
}
|
|
|
|
return ACT_RET_CONT;
|
|
}
|
|
|
|
/* Parse a "set-timeout" action. Returns ACT_RET_PRS_ERR if parsing error.
|
|
*/
|
|
static enum act_parse_ret parse_http_set_timeout(const char **args,
|
|
int *orig_arg,
|
|
struct proxy *px,
|
|
struct act_rule *rule, char **err)
|
|
{
|
|
int cur_arg;
|
|
|
|
rule->action = ACT_CUSTOM;
|
|
rule->action_ptr = action_timeout_set_stream_timeout;
|
|
rule->release_ptr = release_timeout_action;
|
|
|
|
cur_arg = *orig_arg;
|
|
if (!*args[cur_arg] || !*args[cur_arg + 1]) {
|
|
memprintf(err, "expects exactly 2 arguments");
|
|
return ACT_RET_PRS_ERR;
|
|
}
|
|
|
|
if (cfg_parse_rule_set_timeout(args, cur_arg, rule, px, err) == -1) {
|
|
return ACT_RET_PRS_ERR;
|
|
}
|
|
|
|
*orig_arg = cur_arg + 2;
|
|
|
|
return ACT_RET_PRS_OK;
|
|
}
|
|
|
|
/* This function executes a strict-mode actions. On success, it always returns
|
|
* ACT_RET_CONT
|
|
*/
|
|
static enum act_return http_action_strict_mode(struct act_rule *rule, struct proxy *px,
|
|
struct session *sess, struct stream *s, int flags)
|
|
{
|
|
struct http_msg *msg = ((rule->from == ACT_F_HTTP_REQ) ? &s->txn->req : &s->txn->rsp);
|
|
|
|
if (rule->action == 0) // strict-mode on
|
|
msg->flags &= ~HTTP_MSGF_SOFT_RW;
|
|
else // strict-mode off
|
|
msg->flags |= HTTP_MSGF_SOFT_RW;
|
|
return ACT_RET_CONT;
|
|
}
|
|
|
|
/* Parse a "strict-mode" action. It returns ACT_RET_PRS_OK on success,
|
|
* ACT_RET_PRS_ERR on error.
|
|
*/
|
|
static enum act_parse_ret parse_http_strict_mode(const char **args, int *orig_arg, struct proxy *px,
|
|
struct act_rule *rule, char **err)
|
|
{
|
|
int cur_arg;
|
|
|
|
cur_arg = *orig_arg;
|
|
if (!*args[cur_arg]) {
|
|
memprintf(err, "expects exactly 1 arguments");
|
|
return ACT_RET_PRS_ERR;
|
|
}
|
|
|
|
if (strcasecmp(args[cur_arg], "on") == 0)
|
|
rule->action = 0; // strict-mode on
|
|
else if (strcasecmp(args[cur_arg], "off") == 0)
|
|
rule->action = 1; // strict-mode off
|
|
else {
|
|
memprintf(err, "Unexpected value '%s'. Only 'on' and 'off' are supported", args[cur_arg]);
|
|
return ACT_RET_PRS_ERR;
|
|
}
|
|
rule->action_ptr = http_action_strict_mode;
|
|
|
|
*orig_arg = cur_arg + 1;
|
|
return ACT_RET_PRS_OK;
|
|
}
|
|
|
|
/* This function executes a return action. It builds an HTX message from an
|
|
* errorfile, an raw file or a log-format string, depending on <.action>
|
|
* value. On success, it returns ACT_RET_ABRT. If an error occurs ACT_RET_ERR is
|
|
* returned.
|
|
*/
|
|
static enum act_return http_action_return(struct act_rule *rule, struct proxy *px,
|
|
struct session *sess, struct stream *s, int flags)
|
|
{
|
|
struct channel *req = &s->req;
|
|
|
|
s->txn->status = rule->arg.http_reply->status;
|
|
|
|
if (!(s->flags & SF_ERR_MASK))
|
|
s->flags |= SF_ERR_LOCAL;
|
|
if (!(s->flags & SF_FINST_MASK))
|
|
s->flags |= ((rule->from == ACT_F_HTTP_REQ) ? SF_FINST_R : SF_FINST_H);
|
|
|
|
if (http_reply_message(s, rule->arg.http_reply) == -1)
|
|
return ACT_RET_ERR;
|
|
|
|
if (rule->from == ACT_F_HTTP_REQ) {
|
|
/* let's log the request time */
|
|
s->logs.request_ts = now_ns;
|
|
req->analysers &= AN_REQ_FLT_END;
|
|
|
|
if (s->sess->fe == s->be) /* report it if the request was intercepted by the frontend */
|
|
_HA_ATOMIC_INC(&s->sess->fe->fe_counters.intercepted_req);
|
|
}
|
|
|
|
return ACT_RET_ABRT;
|
|
}
|
|
|
|
/* Parse a "return" action. It returns ACT_RET_PRS_OK on success,
|
|
* ACT_RET_PRS_ERR on error. It relies on http_parse_http_reply() to set
|
|
* <.arg.http_reply>.
|
|
*/
|
|
static enum act_parse_ret parse_http_return(const char **args, int *orig_arg, struct proxy *px,
|
|
struct act_rule *rule, char **err)
|
|
{
|
|
/* Prepare parsing of log-format strings */
|
|
px->conf.args.ctx = ((rule->from == ACT_F_HTTP_REQ) ? ARGC_HRQ : ARGC_HRS);
|
|
rule->arg.http_reply = http_parse_http_reply(args, orig_arg, px, 200, err);
|
|
if (!rule->arg.http_reply)
|
|
return ACT_RET_PRS_ERR;
|
|
|
|
rule->flags |= ACT_FLAG_FINAL;
|
|
rule->action = ACT_CUSTOM;
|
|
rule->check_ptr = check_act_http_reply;
|
|
rule->action_ptr = http_action_return;
|
|
rule->release_ptr = release_act_http_reply;
|
|
return ACT_RET_PRS_OK;
|
|
}
|
|
|
|
|
|
|
|
/* This function executes a wait-for-body action. It waits for the message
|
|
* payload for a max configured time (.arg.p[0]) and eventually for only first
|
|
* <arg.p[1]> bytes (0 means no limit). It relies on http_wait_for_msg_body()
|
|
* function. it returns ACT_RET_CONT when conditions are met to stop to wait.
|
|
* Otherwise ACT_RET_YIELD is returned to wait for more data. ACT_RET_INV is
|
|
* returned if a parsing error is raised by lower level and ACT_RET_ERR if an
|
|
* internal error occurred. Finally ACT_RET_ABRT is returned when a timeout
|
|
* occurred.
|
|
*/
|
|
static enum act_return http_action_wait_for_body(struct act_rule *rule, struct proxy *px,
|
|
struct session *sess, struct stream *s, int flags)
|
|
{
|
|
struct channel *chn = ((rule->from == ACT_F_HTTP_REQ) ? &s->req : &s->res);
|
|
unsigned int time = (uintptr_t)rule->arg.act.p[0];
|
|
unsigned int bytes = (uintptr_t)rule->arg.act.p[1];
|
|
|
|
switch (http_wait_for_msg_body(s, chn, time, bytes)) {
|
|
case HTTP_RULE_RES_CONT:
|
|
return ACT_RET_CONT;
|
|
case HTTP_RULE_RES_YIELD:
|
|
return ACT_RET_YIELD;
|
|
case HTTP_RULE_RES_BADREQ:
|
|
return ACT_RET_INV;
|
|
case HTTP_RULE_RES_ERROR:
|
|
return ACT_RET_ERR;
|
|
case HTTP_RULE_RES_ABRT:
|
|
return ACT_RET_ABRT;
|
|
default:
|
|
return ACT_RET_ERR;
|
|
}
|
|
}
|
|
|
|
/* Parse a "wait-for-body" action. It returns ACT_RET_PRS_OK on success,
|
|
* ACT_RET_PRS_ERR on error.
|
|
*/
|
|
static enum act_parse_ret parse_http_wait_for_body(const char **args, int *orig_arg, struct proxy *px,
|
|
struct act_rule *rule, char **err)
|
|
{
|
|
int cur_arg;
|
|
unsigned int time, bytes;
|
|
const char *res;
|
|
|
|
cur_arg = *orig_arg;
|
|
if (!*args[cur_arg]) {
|
|
memprintf(err, "expects time <time> [ at-least <bytes> ]");
|
|
return ACT_RET_PRS_ERR;
|
|
}
|
|
|
|
time = UINT_MAX; /* To be sure it is set */
|
|
bytes = 0; /* Default value, wait all the body */
|
|
while (*(args[cur_arg])) {
|
|
if (strcmp(args[cur_arg], "time") == 0) {
|
|
if (!*args[cur_arg + 1]) {
|
|
memprintf(err, "missing argument for '%s'", args[cur_arg]);
|
|
return ACT_RET_PRS_ERR;
|
|
}
|
|
res = parse_time_err(args[cur_arg+1], &time, TIME_UNIT_MS);
|
|
if (res == PARSE_TIME_OVER) {
|
|
memprintf(err, "time overflow (maximum value is 2147483647 ms or ~24.8 days)");
|
|
return ACT_RET_PRS_ERR;
|
|
}
|
|
if (res == PARSE_TIME_UNDER) {
|
|
memprintf(err, "time underflow (minimum non-null value is 1 ms)");
|
|
return ACT_RET_PRS_ERR;
|
|
}
|
|
if (res) {
|
|
memprintf(err, "unexpected character '%c'", *res);
|
|
return ACT_RET_PRS_ERR;
|
|
}
|
|
cur_arg++;
|
|
}
|
|
else if (strcmp(args[cur_arg], "at-least") == 0) {
|
|
if (!*args[cur_arg + 1]) {
|
|
memprintf(err, "missing argument for '%s'", args[cur_arg]);
|
|
return ACT_RET_PRS_ERR;
|
|
}
|
|
res = parse_size_err(args[cur_arg+1], &bytes);
|
|
if (res) {
|
|
memprintf(err, "unexpected character '%c'", *res);
|
|
return ACT_RET_PRS_ERR;
|
|
}
|
|
cur_arg++;
|
|
}
|
|
else
|
|
break;
|
|
cur_arg++;
|
|
}
|
|
|
|
if (time == UINT_MAX) {
|
|
memprintf(err, "expects time <time> [ at-least <bytes> ]");
|
|
return ACT_RET_PRS_ERR;
|
|
}
|
|
|
|
rule->arg.act.p[0] = (void *)(uintptr_t)time;
|
|
rule->arg.act.p[1] = (void *)(uintptr_t)bytes;
|
|
|
|
*orig_arg = cur_arg;
|
|
|
|
rule->action = ACT_CUSTOM;
|
|
rule->action_ptr = http_action_wait_for_body;
|
|
return ACT_RET_PRS_OK;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* All supported http-request action keywords must be declared here. */
|
|
/************************************************************************/
|
|
|
|
static struct action_kw_list http_req_actions = {
|
|
.kw = {
|
|
{ "add-acl", parse_http_set_map, KWF_MATCH_PREFIX },
|
|
{ "add-header", parse_http_set_header, 0 },
|
|
{ "allow", parse_http_allow, 0 },
|
|
{ "auth", parse_http_auth, 0 },
|
|
{ "capture", parse_http_req_capture, 0 },
|
|
{ "del-acl", parse_http_set_map, KWF_MATCH_PREFIX },
|
|
{ "del-header", parse_http_del_header, 0 },
|
|
{ "del-map", parse_http_set_map, KWF_MATCH_PREFIX },
|
|
{ "deny", parse_http_deny, 0 },
|
|
{ "disable-l7-retry", parse_http_req_disable_l7_retry, 0 },
|
|
{ "early-hint", parse_http_set_header, 0 },
|
|
{ "normalize-uri", parse_http_normalize_uri, KWF_EXPERIMENTAL },
|
|
{ "redirect", parse_http_redirect, 0 },
|
|
{ "reject", parse_http_action_reject, 0 },
|
|
{ "replace-header", parse_http_replace_header, 0 },
|
|
{ "replace-path", parse_replace_uri, 0 },
|
|
{ "replace-pathq", parse_replace_uri, 0 },
|
|
{ "replace-uri", parse_replace_uri, 0 },
|
|
{ "replace-value", parse_http_replace_header, 0 },
|
|
{ "return", parse_http_return, 0 },
|
|
{ "set-header", parse_http_set_header, 0 },
|
|
{ "set-map", parse_http_set_map, KWF_MATCH_PREFIX },
|
|
{ "set-method", parse_set_req_line, 0 },
|
|
{ "set-path", parse_set_req_line, 0 },
|
|
{ "set-pathq", parse_set_req_line, 0 },
|
|
{ "set-query", parse_set_req_line, 0 },
|
|
{ "set-uri", parse_set_req_line, 0 },
|
|
{ "strict-mode", parse_http_strict_mode, 0 },
|
|
{ "tarpit", parse_http_deny, 0 },
|
|
{ "track-sc", parse_http_track_sc, KWF_MATCH_PREFIX },
|
|
{ "set-timeout", parse_http_set_timeout, 0 },
|
|
{ "wait-for-body", parse_http_wait_for_body, 0 },
|
|
{ NULL, NULL }
|
|
}
|
|
};
|
|
|
|
INITCALL1(STG_REGISTER, http_req_keywords_register, &http_req_actions);
|
|
|
|
static struct action_kw_list http_res_actions = {
|
|
.kw = {
|
|
{ "add-acl", parse_http_set_map, KWF_MATCH_PREFIX },
|
|
{ "add-header", parse_http_set_header, 0 },
|
|
{ "allow", parse_http_allow, 0 },
|
|
{ "capture", parse_http_res_capture, 0 },
|
|
{ "del-acl", parse_http_set_map, KWF_MATCH_PREFIX },
|
|
{ "del-header", parse_http_del_header, 0 },
|
|
{ "del-map", parse_http_set_map, KWF_MATCH_PREFIX },
|
|
{ "deny", parse_http_deny, 0 },
|
|
{ "redirect", parse_http_redirect, 0 },
|
|
{ "replace-header", parse_http_replace_header, 0 },
|
|
{ "replace-value", parse_http_replace_header, 0 },
|
|
{ "return", parse_http_return, 0 },
|
|
{ "set-header", parse_http_set_header, 0 },
|
|
{ "set-map", parse_http_set_map, KWF_MATCH_PREFIX },
|
|
{ "set-status", parse_http_set_status, 0 },
|
|
{ "strict-mode", parse_http_strict_mode, 0 },
|
|
{ "track-sc", parse_http_track_sc, KWF_MATCH_PREFIX },
|
|
{ "set-timeout", parse_http_set_timeout, 0 },
|
|
{ "wait-for-body", parse_http_wait_for_body, 0 },
|
|
{ NULL, NULL }
|
|
}
|
|
};
|
|
|
|
INITCALL1(STG_REGISTER, http_res_keywords_register, &http_res_actions);
|
|
|
|
static struct action_kw_list http_after_res_actions = {
|
|
.kw = {
|
|
{ "add-header", parse_http_set_header, 0 },
|
|
{ "allow", parse_http_allow, 0 },
|
|
{ "capture", parse_http_res_capture, 0 },
|
|
{ "del-acl", parse_http_set_map, KWF_MATCH_PREFIX },
|
|
{ "del-header", parse_http_del_header, 0 },
|
|
{ "del-map", parse_http_set_map, KWF_MATCH_PREFIX },
|
|
{ "replace-header", parse_http_replace_header, 0 },
|
|
{ "replace-value", parse_http_replace_header, 0 },
|
|
{ "set-header", parse_http_set_header, 0 },
|
|
{ "set-map", parse_http_set_map, KWF_MATCH_PREFIX },
|
|
{ "set-status", parse_http_set_status, 0 },
|
|
{ "strict-mode", parse_http_strict_mode, 0 },
|
|
{ NULL, NULL }
|
|
}
|
|
};
|
|
|
|
INITCALL1(STG_REGISTER, http_after_res_keywords_register, &http_after_res_actions);
|
|
|
|
/*
|
|
* Local variables:
|
|
* c-indent-level: 8
|
|
* c-basic-offset: 8
|
|
* End:
|
|
*/
|