diff options
Diffstat (limited to 'src/http_act.c')
-rw-r--r-- | src/http_act.c | 2501 |
1 files changed, 2501 insertions, 0 deletions
diff --git a/src/http_act.c b/src/http_act.c new file mode 100644 index 0000000..7d45780 --- /dev/null +++ b/src/http_act.c @@ -0,0 +1,2501 @@ +/* + * 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) +{ + struct logformat_node *lf, *lfb; + + istfree(&rule->arg.http.str); + if (rule->arg.http.re) + regex_free(rule->arg.http.re); + list_for_each_entry_safe(lf, lfb, &rule->arg.http.fmt, list) { + LIST_DELETE(&lf->list); + release_sample_expr(lf->expr); + free(lf->arg); + free(lf); + } +} + +/* 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; + LIST_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; + LIST_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; + LIST_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; + LIST_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; + LIST_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; + LIST_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; + LIST_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) +{ + struct logformat_node *lf, *lfb; + + free(rule->arg.map.ref); + list_for_each_entry_safe(lf, lfb, &rule->arg.map.key, list) { + LIST_DELETE(&lf->list); + release_sample_expr(lf->expr); + free(lf->arg); + free(lf); + } + if (rule->action == 1) { + list_for_each_entry_safe(lf, lfb, &rule->arg.map.value, list) { + LIST_DELETE(&lf->list); + release_sample_expr(lf->expr); + free(lf->arg); + free(lf); + } + } +} + +/* 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 */ + LIST_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++; + LIST_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 && (unsigned)(s->txn->status - 400) < 100) { + 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 && (unsigned)(s->txn->status - 500) < 100 && + s->txn->status != 501 && s->txn->status != 505) { + 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: + */ |