/* * HTTP actions * * Copyright 2000-2018 Willy Tarreau * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* Release memory allocated by most of HTTP actions. Concretly, it releases * . */ 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 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 "); 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 * 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 "); 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 * .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 to replace the URI. It uses the format string present in * . 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), * to store the compiled regex, and 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 and "); 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: ; or 3 arguments: reason "); 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 [ 'len' | id ]"); 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 id "); 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 * 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