blob: 838deb52dc6ff267be44b01205b822a787ed3fc1 [file] [log] [blame]
Willy Tarreau79e57332018-10-02 16:01:16 +02001/*
2 * HTTP actions
3 *
4 * Copyright 2000-2018 Willy Tarreau <w@1wt.eu>
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version
9 * 2 of the License, or (at your option) any later version.
10 *
11 */
12
13#include <sys/types.h>
14
15#include <ctype.h>
16#include <string.h>
17#include <time.h>
18
Willy Tarreaudcc048a2020-06-04 19:11:43 +020019#include <haproxy/acl.h>
Willy Tarreau122eba92020-06-04 10:15:32 +020020#include <haproxy/action.h>
Willy Tarreau4c7e4b72020-05-27 12:58:42 +020021#include <haproxy/api.h>
Willy Tarreaub2551052020-06-09 09:07:15 +020022#include <haproxy/arg.h>
23#include <haproxy/capture-t.h>
Willy Tarreau6be78492020-06-05 00:00:29 +020024#include <haproxy/cfgparse.h>
Willy Tarreauc13ed532020-06-02 10:22:45 +020025#include <haproxy/chunk.h>
Willy Tarreauf268ee82020-06-04 17:05:57 +020026#include <haproxy/global.h>
Willy Tarreaucd72d8c2020-06-02 19:11:26 +020027#include <haproxy/http.h>
Willy Tarreauc2b1ff02020-06-04 21:21:03 +020028#include <haproxy/http_ana.h>
Willy Tarreau87735332020-06-04 09:08:41 +020029#include <haproxy/http_htx.h>
Willy Tarreauc761f842020-06-04 11:40:28 +020030#include <haproxy/http_rules.h>
Willy Tarreau36979d92020-06-05 17:27:29 +020031#include <haproxy/log.h>
Willy Tarreau225a90a2020-06-04 15:06:28 +020032#include <haproxy/pattern.h>
Willy Tarreaud0ef4392020-06-02 09:38:52 +020033#include <haproxy/pool.h>
Willy Tarreau7cd8b6e2020-06-02 17:32:26 +020034#include <haproxy/regex.h>
Willy Tarreaue6ce10b2020-06-04 15:33:47 +020035#include <haproxy/sample.h>
Willy Tarreau5e539c92020-06-04 20:45:39 +020036#include <haproxy/stream_interface.h>
Willy Tarreau48fbcae2020-06-03 18:09:46 +020037#include <haproxy/tools.h>
Willy Tarreau8c42b8a2020-06-04 19:27:34 +020038#include <haproxy/uri_auth-t.h>
Willy Tarreaud6788052020-05-27 15:59:00 +020039#include <haproxy/version.h>
Willy Tarreau79e57332018-10-02 16:01:16 +020040
Willy Tarreau79e57332018-10-02 16:01:16 +020041
Christopher Faulet2eb53962020-01-14 14:47:34 +010042/* Release memory allocated by most of HTTP actions. Concretly, it releases
43 * <arg.http>.
44 */
45static void release_http_action(struct act_rule *rule)
46{
47 struct logformat_node *lf, *lfb;
48
Tim Duesterhused526372020-03-05 17:56:33 +010049 istfree(&rule->arg.http.str);
Christopher Faulet2eb53962020-01-14 14:47:34 +010050 if (rule->arg.http.re)
51 regex_free(rule->arg.http.re);
52 list_for_each_entry_safe(lf, lfb, &rule->arg.http.fmt, list) {
53 LIST_DEL(&lf->list);
54 release_sample_expr(lf->expr);
55 free(lf->arg);
56 free(lf);
57 }
58}
59
Christopher Faulet5cb513a2020-05-13 17:56:56 +020060/* Release memory allocated by HTTP actions relying on an http reply. Concretly,
61 * it releases <.arg.http_reply>
62 */
63static void release_act_http_reply(struct act_rule *rule)
64{
65 release_http_reply(rule->arg.http_reply);
66 rule->arg.http_reply = NULL;
67}
68
69
70/* Check function for HTTP actions relying on an http reply. The function
71 * returns 1 in success case, otherwise, it returns 0 and err is filled.
72 */
73static int check_act_http_reply(struct act_rule *rule, struct proxy *px, char **err)
74{
75 struct http_reply *reply = rule->arg.http_reply;
76
77 if (!http_check_http_reply(reply, px, err)) {
78 release_act_http_reply(rule);
79 return 0;
80 }
81 return 1;
82}
83
Willy Tarreau79e57332018-10-02 16:01:16 +020084
85/* This function executes one of the set-{method,path,query,uri} actions. It
86 * builds a string in the trash from the specified format string. It finds
Christopher Faulet2c22a692019-12-18 15:39:56 +010087 * the action to be performed in <.action>, previously filled by function
Ilya Shipitsinc02a23f2020-05-06 00:53:22 +050088 * parse_set_req_line(). The replacement action is executed by the function
Christopher Faulete00d06c2019-12-16 17:18:42 +010089 * http_action_set_req_line(). On success, it returns ACT_RET_CONT. If an error
90 * occurs while soft rewrites are enabled, the action is canceled, but the rule
91 * processing continue. Otherwsize ACT_RET_ERR is returned.
Willy Tarreau79e57332018-10-02 16:01:16 +020092 */
93static enum act_return http_action_set_req_line(struct act_rule *rule, struct proxy *px,
94 struct session *sess, struct stream *s, int flags)
95{
96 struct buffer *replace;
Christopher Faulet13403762019-12-13 09:01:57 +010097 enum act_return ret = ACT_RET_CONT;
Willy Tarreau79e57332018-10-02 16:01:16 +020098
99 replace = alloc_trash_chunk();
100 if (!replace)
Christopher Faulete00d06c2019-12-16 17:18:42 +0100101 goto fail_alloc;
Willy Tarreau79e57332018-10-02 16:01:16 +0200102
103 /* If we have to create a query string, prepare a '?'. */
Christopher Faulet2c22a692019-12-18 15:39:56 +0100104 if (rule->action == 2) // set-query
Willy Tarreau79e57332018-10-02 16:01:16 +0200105 replace->area[replace->data++] = '?';
106 replace->data += build_logline(s, replace->area + replace->data,
107 replace->size - replace->data,
Christopher Faulet96bff762019-12-17 13:46:18 +0100108 &rule->arg.http.fmt);
Willy Tarreau79e57332018-10-02 16:01:16 +0200109
Christopher Faulet2c22a692019-12-18 15:39:56 +0100110 if (http_req_replace_stline(rule->action, replace->area, replace->data, px, s) == -1)
Christopher Faulete00d06c2019-12-16 17:18:42 +0100111 goto fail_rewrite;
Willy Tarreau79e57332018-10-02 16:01:16 +0200112
Christopher Faulete00d06c2019-12-16 17:18:42 +0100113 leave:
Willy Tarreau79e57332018-10-02 16:01:16 +0200114 free_trash_chunk(replace);
115 return ret;
Christopher Faulete00d06c2019-12-16 17:18:42 +0100116
117 fail_alloc:
118 if (!(s->flags & SF_ERR_MASK))
119 s->flags |= SF_ERR_RESOURCE;
120 ret = ACT_RET_ERR;
121 goto leave;
122
123 fail_rewrite:
124 _HA_ATOMIC_ADD(&sess->fe->fe_counters.failed_rewrites, 1);
125 if (s->flags & SF_BE_ASSIGNED)
126 _HA_ATOMIC_ADD(&s->be->be_counters.failed_rewrites, 1);
William Lallemand36119de2021-03-08 15:26:48 +0100127 if (sess->listener && sess->listener->counters)
Christopher Faulete00d06c2019-12-16 17:18:42 +0100128 _HA_ATOMIC_ADD(&sess->listener->counters->failed_rewrites, 1);
129 if (objt_server(s->target))
130 _HA_ATOMIC_ADD(&__objt_server(s->target)->counters.failed_rewrites, 1);
131
Christopher Faulet333bf8c2020-01-22 14:38:05 +0100132 if (!(s->txn->req.flags & HTTP_MSGF_SOFT_RW)) {
Christopher Faulete00d06c2019-12-16 17:18:42 +0100133 ret = ACT_RET_ERR;
Christopher Faulet333bf8c2020-01-22 14:38:05 +0100134 if (!(s->flags & SF_ERR_MASK))
135 s->flags |= SF_ERR_PRXCOND;
136 }
Christopher Faulete00d06c2019-12-16 17:18:42 +0100137 goto leave;
Willy Tarreau79e57332018-10-02 16:01:16 +0200138}
139
140/* parse an http-request action among :
141 * set-method
142 * set-path
Christopher Faulet312294f2020-09-02 17:17:44 +0200143 * set-pathq
Willy Tarreau79e57332018-10-02 16:01:16 +0200144 * set-query
145 * set-uri
146 *
147 * All of them accept a single argument of type string representing a log-format.
Christopher Faulet96bff762019-12-17 13:46:18 +0100148 * The resulting rule makes use of <http.fmt> to store the log-format list head,
Christopher Faulet2c22a692019-12-18 15:39:56 +0100149 * and <.action> to store the action type as an int (0=method, 1=path, 2=query,
150 * 3=uri). It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
Willy Tarreau79e57332018-10-02 16:01:16 +0200151 */
152static enum act_parse_ret parse_set_req_line(const char **args, int *orig_arg, struct proxy *px,
153 struct act_rule *rule, char **err)
154{
155 int cur_arg = *orig_arg;
156
Willy Tarreau79e57332018-10-02 16:01:16 +0200157 switch (args[0][4]) {
158 case 'm' :
Christopher Faulet2c22a692019-12-18 15:39:56 +0100159 rule->action = 0; // set-method
Willy Tarreau79e57332018-10-02 16:01:16 +0200160 break;
161 case 'p' :
Christopher Faulet312294f2020-09-02 17:17:44 +0200162 if (args[0][8] == 'q')
163 rule->action = 4; // set-pathq
164 else
165 rule->action = 1; // set-path
Willy Tarreau79e57332018-10-02 16:01:16 +0200166 break;
167 case 'q' :
Christopher Faulet2c22a692019-12-18 15:39:56 +0100168 rule->action = 2; // set-query
Willy Tarreau79e57332018-10-02 16:01:16 +0200169 break;
170 case 'u' :
Christopher Faulet2c22a692019-12-18 15:39:56 +0100171 rule->action = 3; // set-uri
Willy Tarreau79e57332018-10-02 16:01:16 +0200172 break;
173 default:
174 memprintf(err, "internal error: unhandled action '%s'", args[0]);
175 return ACT_RET_PRS_ERR;
176 }
Christopher Faulet96bff762019-12-17 13:46:18 +0100177 rule->action_ptr = http_action_set_req_line;
Christopher Faulet2eb53962020-01-14 14:47:34 +0100178 rule->release_ptr = release_http_action;
Willy Tarreau79e57332018-10-02 16:01:16 +0200179
180 if (!*args[cur_arg] ||
181 (*args[cur_arg + 1] && strcmp(args[cur_arg + 1], "if") != 0 && strcmp(args[cur_arg + 1], "unless") != 0)) {
182 memprintf(err, "expects exactly 1 argument <format>");
183 return ACT_RET_PRS_ERR;
184 }
185
Christopher Faulet96bff762019-12-17 13:46:18 +0100186 LIST_INIT(&rule->arg.http.fmt);
Willy Tarreau79e57332018-10-02 16:01:16 +0200187 px->conf.args.ctx = ARGC_HRQ;
Christopher Faulet96bff762019-12-17 13:46:18 +0100188 if (!parse_logformat_string(args[cur_arg], px, &rule->arg.http.fmt, LOG_OPT_HTTP,
Willy Tarreau79e57332018-10-02 16:01:16 +0200189 (px->cap & PR_CAP_FE) ? SMP_VAL_FE_HRQ_HDR : SMP_VAL_BE_HRQ_HDR, err)) {
190 return ACT_RET_PRS_ERR;
191 }
192
193 (*orig_arg)++;
194 return ACT_RET_PRS_OK;
195}
196
Willy Tarreau33810222019-06-12 17:44:02 +0200197/* This function executes a replace-uri action. It finds its arguments in
Christopher Faulet96bff762019-12-17 13:46:18 +0100198 * <rule>.arg.http. It builds a string in the trash from the format string
Willy Tarreau33810222019-06-12 17:44:02 +0200199 * previously filled by function parse_replace_uri() and will execute the regex
Christopher Faulet96bff762019-12-17 13:46:18 +0100200 * in <http.re> to replace the URI. It uses the format string present in
Christopher Faulet2c22a692019-12-18 15:39:56 +0100201 * <http.fmt>. The component to act on (path/uri) is taken from <.action> which
Christopher Faulet96bff762019-12-17 13:46:18 +0100202 * contains 1 for the path or 3 for the URI (values used by
203 * http_req_replace_stline()). On success, it returns ACT_RET_CONT. If an error
204 * occurs while soft rewrites are enabled, the action is canceled, but the rule
205 * processing continue. Otherwsize ACT_RET_ERR is returned.
Willy Tarreau33810222019-06-12 17:44:02 +0200206 */
207static enum act_return http_action_replace_uri(struct act_rule *rule, struct proxy *px,
208 struct session *sess, struct stream *s, int flags)
209{
Christopher Faulet13403762019-12-13 09:01:57 +0100210 enum act_return ret = ACT_RET_CONT;
Willy Tarreau33810222019-06-12 17:44:02 +0200211 struct buffer *replace, *output;
212 struct ist uri;
213 int len;
214
215 replace = alloc_trash_chunk();
216 output = alloc_trash_chunk();
217 if (!replace || !output)
Christopher Faulete00d06c2019-12-16 17:18:42 +0100218 goto fail_alloc;
Christopher Faulet12c28b62019-07-15 16:30:24 +0200219 uri = htx_sl_req_uri(http_get_stline(htxbuf(&s->req.buf)));
Willy Tarreau262c3f12019-12-17 06:52:51 +0100220
Christopher Faulet1fa0cc12020-09-02 11:10:38 +0200221 if (rule->action == 1) // replace-path
222 uri = iststop(http_get_path(uri), '?');
Christopher Faulet312294f2020-09-02 17:17:44 +0200223 else if (rule->action == 4) // replace-pathq
224 uri = http_get_path(uri);
Willy Tarreau262c3f12019-12-17 06:52:51 +0100225
Christopher Faulet96bff762019-12-17 13:46:18 +0100226 if (!regex_exec_match2(rule->arg.http.re, uri.ptr, uri.len, MAX_MATCH, pmatch, 0))
Willy Tarreau33810222019-06-12 17:44:02 +0200227 goto leave;
228
Christopher Faulet96bff762019-12-17 13:46:18 +0100229 replace->data = build_logline(s, replace->area, replace->size, &rule->arg.http.fmt);
Willy Tarreau33810222019-06-12 17:44:02 +0200230
231 /* note: uri.ptr doesn't need to be zero-terminated because it will
232 * only be used to pick pmatch references.
233 */
234 len = exp_replace(output->area, output->size, uri.ptr, replace->area, pmatch);
235 if (len == -1)
Christopher Faulete00d06c2019-12-16 17:18:42 +0100236 goto fail_rewrite;
Willy Tarreau33810222019-06-12 17:44:02 +0200237
Christopher Faulet2c22a692019-12-18 15:39:56 +0100238 if (http_req_replace_stline(rule->action, output->area, len, px, s) == -1)
Christopher Faulete00d06c2019-12-16 17:18:42 +0100239 goto fail_rewrite;
Willy Tarreau33810222019-06-12 17:44:02 +0200240
Christopher Faulete00d06c2019-12-16 17:18:42 +0100241 leave:
Willy Tarreau33810222019-06-12 17:44:02 +0200242 free_trash_chunk(output);
243 free_trash_chunk(replace);
244 return ret;
Christopher Faulete00d06c2019-12-16 17:18:42 +0100245
246 fail_alloc:
247 if (!(s->flags & SF_ERR_MASK))
248 s->flags |= SF_ERR_RESOURCE;
249 ret = ACT_RET_ERR;
250 goto leave;
251
252 fail_rewrite:
253 _HA_ATOMIC_ADD(&sess->fe->fe_counters.failed_rewrites, 1);
254 if (s->flags & SF_BE_ASSIGNED)
255 _HA_ATOMIC_ADD(&s->be->be_counters.failed_rewrites, 1);
William Lallemand36119de2021-03-08 15:26:48 +0100256 if (sess->listener && sess->listener->counters)
Christopher Faulete00d06c2019-12-16 17:18:42 +0100257 _HA_ATOMIC_ADD(&sess->listener->counters->failed_rewrites, 1);
258 if (objt_server(s->target))
259 _HA_ATOMIC_ADD(&__objt_server(s->target)->counters.failed_rewrites, 1);
260
Christopher Faulet333bf8c2020-01-22 14:38:05 +0100261 if (!(s->txn->req.flags & HTTP_MSGF_SOFT_RW)) {
Christopher Faulete00d06c2019-12-16 17:18:42 +0100262 ret = ACT_RET_ERR;
Christopher Faulet333bf8c2020-01-22 14:38:05 +0100263 if (!(s->flags & SF_ERR_MASK))
264 s->flags |= SF_ERR_PRXCOND;
265 }
Christopher Faulete00d06c2019-12-16 17:18:42 +0100266 goto leave;
Willy Tarreau33810222019-06-12 17:44:02 +0200267}
268
Christopher Faulet312294f2020-09-02 17:17:44 +0200269/* parse a "replace-uri", "replace-path" or "replace-pathq"
270 * http-request action.
Willy Tarreau33810222019-06-12 17:44:02 +0200271 * This action takes 2 arguments (a regex and a replacement format string).
Christopher Faulet2c22a692019-12-18 15:39:56 +0100272 * The resulting rule makes use of <.action> to store the action (1/3 for now),
Christopher Faulet96bff762019-12-17 13:46:18 +0100273 * <http.re> to store the compiled regex, and <http.fmt> to store the log-format
Willy Tarreau33810222019-06-12 17:44:02 +0200274 * list head. It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
275 */
276static enum act_parse_ret parse_replace_uri(const char **args, int *orig_arg, struct proxy *px,
277 struct act_rule *rule, char **err)
278{
279 int cur_arg = *orig_arg;
280 char *error = NULL;
281
Christopher Faulet312294f2020-09-02 17:17:44 +0200282 switch (args[0][8]) {
283 case 'p':
284 if (args[0][12] == 'q')
285 rule->action = 4; // replace-pathq, same as set-pathq
286 else
287 rule->action = 1; // replace-path, same as set-path
288 break;
289 case 'u':
Christopher Faulet2c22a692019-12-18 15:39:56 +0100290 rule->action = 3; // replace-uri, same as set-uri
Christopher Faulet312294f2020-09-02 17:17:44 +0200291 break;
292 default:
293 memprintf(err, "internal error: unhandled action '%s'", args[0]);
294 return ACT_RET_PRS_ERR;
295 }
Willy Tarreau262c3f12019-12-17 06:52:51 +0100296
Willy Tarreau33810222019-06-12 17:44:02 +0200297 rule->action_ptr = http_action_replace_uri;
Christopher Faulet2eb53962020-01-14 14:47:34 +0100298 rule->release_ptr = release_http_action;
Willy Tarreau33810222019-06-12 17:44:02 +0200299
300 if (!*args[cur_arg] || !*args[cur_arg+1] ||
301 (*args[cur_arg+2] && strcmp(args[cur_arg+2], "if") != 0 && strcmp(args[cur_arg+2], "unless") != 0)) {
302 memprintf(err, "expects exactly 2 arguments <match-regex> and <replace-format>");
303 return ACT_RET_PRS_ERR;
304 }
305
Christopher Faulet96bff762019-12-17 13:46:18 +0100306 if (!(rule->arg.http.re = regex_comp(args[cur_arg], 1, 1, &error))) {
Willy Tarreau33810222019-06-12 17:44:02 +0200307 memprintf(err, "failed to parse the regex : %s", error);
308 free(error);
309 return ACT_RET_PRS_ERR;
310 }
311
Christopher Faulet96bff762019-12-17 13:46:18 +0100312 LIST_INIT(&rule->arg.http.fmt);
Willy Tarreau33810222019-06-12 17:44:02 +0200313 px->conf.args.ctx = ARGC_HRQ;
Christopher Faulet96bff762019-12-17 13:46:18 +0100314 if (!parse_logformat_string(args[cur_arg + 1], px, &rule->arg.http.fmt, LOG_OPT_HTTP,
Willy Tarreau33810222019-06-12 17:44:02 +0200315 (px->cap & PR_CAP_FE) ? SMP_VAL_FE_HRQ_HDR : SMP_VAL_BE_HRQ_HDR, err)) {
Christopher Faulet1337b322020-01-14 14:50:55 +0100316 regex_free(rule->arg.http.re);
Willy Tarreau33810222019-06-12 17:44:02 +0200317 return ACT_RET_PRS_ERR;
318 }
319
320 (*orig_arg) += 2;
321 return ACT_RET_PRS_OK;
322}
323
Willy Tarreau79e57332018-10-02 16:01:16 +0200324/* This function is just a compliant action wrapper for "set-status". */
325static enum act_return action_http_set_status(struct act_rule *rule, struct proxy *px,
326 struct session *sess, struct stream *s, int flags)
327{
Christopher Faulet96bff762019-12-17 13:46:18 +0100328 if (http_res_set_status(rule->arg.http.i, rule->arg.http.str, s) == -1) {
Christopher Faulete00d06c2019-12-16 17:18:42 +0100329 _HA_ATOMIC_ADD(&sess->fe->fe_counters.failed_rewrites, 1);
330 if (s->flags & SF_BE_ASSIGNED)
331 _HA_ATOMIC_ADD(&s->be->be_counters.failed_rewrites, 1);
William Lallemand36119de2021-03-08 15:26:48 +0100332 if (sess->listener && sess->listener->counters)
Christopher Faulete00d06c2019-12-16 17:18:42 +0100333 _HA_ATOMIC_ADD(&sess->listener->counters->failed_rewrites, 1);
334 if (objt_server(s->target))
335 _HA_ATOMIC_ADD(&__objt_server(s->target)->counters.failed_rewrites, 1);
336
Christopher Faulet333bf8c2020-01-22 14:38:05 +0100337 if (!(s->txn->req.flags & HTTP_MSGF_SOFT_RW)) {
Christopher Faulet333bf8c2020-01-22 14:38:05 +0100338 if (!(s->flags & SF_ERR_MASK))
339 s->flags |= SF_ERR_PRXCOND;
Christopher Faulet692a6c22020-02-07 10:22:31 +0100340 return ACT_RET_ERR;
Christopher Faulet333bf8c2020-01-22 14:38:05 +0100341 }
Christopher Faulete00d06c2019-12-16 17:18:42 +0100342 }
343
Willy Tarreau79e57332018-10-02 16:01:16 +0200344 return ACT_RET_CONT;
345}
346
347/* parse set-status action:
348 * This action accepts a single argument of type int representing
349 * an http status code. It returns ACT_RET_PRS_OK on success,
350 * ACT_RET_PRS_ERR on error.
351 */
352static enum act_parse_ret parse_http_set_status(const char **args, int *orig_arg, struct proxy *px,
353 struct act_rule *rule, char **err)
354{
355 char *error;
356
357 rule->action = ACT_CUSTOM;
358 rule->action_ptr = action_http_set_status;
Christopher Faulet2eb53962020-01-14 14:47:34 +0100359 rule->release_ptr = release_http_action;
Willy Tarreau79e57332018-10-02 16:01:16 +0200360
361 /* Check if an argument is available */
362 if (!*args[*orig_arg]) {
363 memprintf(err, "expects 1 argument: <status>; or 3 arguments: <status> reason <fmt>");
364 return ACT_RET_PRS_ERR;
365 }
366
367 /* convert status code as integer */
Christopher Faulet96bff762019-12-17 13:46:18 +0100368 rule->arg.http.i = strtol(args[*orig_arg], &error, 10);
369 if (*error != '\0' || rule->arg.http.i < 100 || rule->arg.http.i > 999) {
Willy Tarreau79e57332018-10-02 16:01:16 +0200370 memprintf(err, "expects an integer status code between 100 and 999");
371 return ACT_RET_PRS_ERR;
372 }
373
374 (*orig_arg)++;
375
376 /* set custom reason string */
Christopher Faulet96bff762019-12-17 13:46:18 +0100377 rule->arg.http.str = ist(NULL); // If null, we use the default reason for the status code.
Willy Tarreau79e57332018-10-02 16:01:16 +0200378 if (*args[*orig_arg] && strcmp(args[*orig_arg], "reason") == 0 &&
379 (*args[*orig_arg + 1] && strcmp(args[*orig_arg + 1], "if") != 0 && strcmp(args[*orig_arg + 1], "unless") != 0)) {
380 (*orig_arg)++;
Christopher Faulet96bff762019-12-17 13:46:18 +0100381 rule->arg.http.str.ptr = strdup(args[*orig_arg]);
382 rule->arg.http.str.len = strlen(rule->arg.http.str.ptr);
Willy Tarreau79e57332018-10-02 16:01:16 +0200383 (*orig_arg)++;
384 }
385
Christopher Fauletc20b3712020-01-27 15:51:56 +0100386 LIST_INIT(&rule->arg.http.fmt);
Willy Tarreau79e57332018-10-02 16:01:16 +0200387 return ACT_RET_PRS_OK;
388}
389
390/* This function executes the "reject" HTTP action. It clears the request and
391 * response buffer without sending any response. It can be useful as an HTTP
392 * alternative to the silent-drop action to defend against DoS attacks, and may
393 * also be used with HTTP/2 to close a connection instead of just a stream.
394 * The txn status is unchanged, indicating no response was sent. The termination
Christopher Faulet90d22a82020-03-06 11:18:39 +0100395 * flags will indicate "PR". It always returns ACT_RET_ABRT.
Willy Tarreau79e57332018-10-02 16:01:16 +0200396 */
397static enum act_return http_action_reject(struct act_rule *rule, struct proxy *px,
398 struct session *sess, struct stream *s, int flags)
399{
Willy Tarreau0f9cd7b2019-01-31 19:02:43 +0100400 si_must_kill_conn(chn_prod(&s->req));
Willy Tarreau79e57332018-10-02 16:01:16 +0200401 channel_abort(&s->req);
402 channel_abort(&s->res);
Christopher Fauletd4a824e2020-03-06 15:07:09 +0100403 s->req.analysers &= AN_REQ_FLT_END;
404 s->res.analysers &= AN_RES_FLT_END;
Willy Tarreau79e57332018-10-02 16:01:16 +0200405
Olivier Houcharda798bf52019-03-08 18:52:00 +0100406 _HA_ATOMIC_ADD(&s->be->be_counters.denied_req, 1);
407 _HA_ATOMIC_ADD(&sess->fe->fe_counters.denied_req, 1);
Willy Tarreau79e57332018-10-02 16:01:16 +0200408 if (sess->listener && sess->listener->counters)
Olivier Houcharda798bf52019-03-08 18:52:00 +0100409 _HA_ATOMIC_ADD(&sess->listener->counters->denied_req, 1);
Willy Tarreau79e57332018-10-02 16:01:16 +0200410
411 if (!(s->flags & SF_ERR_MASK))
412 s->flags |= SF_ERR_PRXCOND;
413 if (!(s->flags & SF_FINST_MASK))
414 s->flags |= SF_FINST_R;
415
Christopher Faulet90d22a82020-03-06 11:18:39 +0100416 return ACT_RET_ABRT;
Willy Tarreau79e57332018-10-02 16:01:16 +0200417}
418
419/* parse the "reject" action:
420 * This action takes no argument and returns ACT_RET_PRS_OK on success,
421 * ACT_RET_PRS_ERR on error.
422 */
423static enum act_parse_ret parse_http_action_reject(const char **args, int *orig_arg, struct proxy *px,
424 struct act_rule *rule, char **err)
425{
426 rule->action = ACT_CUSTOM;
427 rule->action_ptr = http_action_reject;
428 return ACT_RET_PRS_OK;
429}
430
Olivier Houchard602bf7d2019-05-10 13:59:15 +0200431/* This function executes the "disable-l7-retry" HTTP action.
432 * It disables L7 retries (all retry except for a connection failure). This
433 * can be useful for example to avoid retrying on POST requests.
434 * It just removes the L7 retry flag on the stream_interface, and always
435 * return ACT_RET_CONT;
436 */
437static enum act_return http_req_disable_l7_retry(struct act_rule *rule, struct proxy *px,
438 struct session *sess, struct stream *s, int flags)
439{
440 struct stream_interface *si = &s->si[1];
441
442 /* In theory, the SI_FL_L7_RETRY flags isn't set at this point, but
443 * let's be future-proof and remove it anyway.
444 */
445 si->flags &= ~SI_FL_L7_RETRY;
446 si->flags |= SI_FL_D_L7_RETRY;
447 return ACT_RET_CONT;
448}
449
450/* parse the "disable-l7-retry" action:
451 * This action takes no argument and returns ACT_RET_PRS_OK on success,
452 * ACT_RET_PRS_ERR on error.
453 */
454static enum act_parse_ret parse_http_req_disable_l7_retry(const char **args,
455 int *orig_args, struct proxy *px,
456 struct act_rule *rule, char **err)
457{
458 rule->action = ACT_CUSTOM;
459 rule->action_ptr = http_req_disable_l7_retry;
460 return ACT_RET_PRS_OK;
461}
462
Willy Tarreau79e57332018-10-02 16:01:16 +0200463/* This function executes the "capture" action. It executes a fetch expression,
464 * turns the result into a string and puts it in a capture slot. It always
465 * returns 1. If an error occurs the action is cancelled, but the rule
466 * processing continues.
467 */
468static enum act_return http_action_req_capture(struct act_rule *rule, struct proxy *px,
469 struct session *sess, struct stream *s, int flags)
470{
471 struct sample *key;
472 struct cap_hdr *h = rule->arg.cap.hdr;
473 char **cap = s->req_cap;
474 int len;
475
476 key = sample_fetch_as_type(s->be, sess, s, SMP_OPT_DIR_REQ|SMP_OPT_FINAL, rule->arg.cap.expr, SMP_T_STR);
477 if (!key)
478 return ACT_RET_CONT;
479
480 if (cap[h->index] == NULL)
481 cap[h->index] = pool_alloc(h->pool);
482
483 if (cap[h->index] == NULL) /* no more capture memory */
484 return ACT_RET_CONT;
485
486 len = key->data.u.str.data;
487 if (len > h->len)
488 len = h->len;
489
490 memcpy(cap[h->index], key->data.u.str.area, len);
491 cap[h->index][len] = 0;
492 return ACT_RET_CONT;
493}
494
495/* This function executes the "capture" action and store the result in a
496 * capture slot if exists. It executes a fetch expression, turns the result
497 * into a string and puts it in a capture slot. It always returns 1. If an
498 * error occurs the action is cancelled, but the rule processing continues.
499 */
500static enum act_return http_action_req_capture_by_id(struct act_rule *rule, struct proxy *px,
501 struct session *sess, struct stream *s, int flags)
502{
503 struct sample *key;
504 struct cap_hdr *h;
505 char **cap = s->req_cap;
506 struct proxy *fe = strm_fe(s);
507 int len;
508 int i;
509
510 /* Look for the original configuration. */
511 for (h = fe->req_cap, i = fe->nb_req_cap - 1;
512 h != NULL && i != rule->arg.capid.idx ;
513 i--, h = h->next);
514 if (!h)
515 return ACT_RET_CONT;
516
517 key = sample_fetch_as_type(s->be, sess, s, SMP_OPT_DIR_REQ|SMP_OPT_FINAL, rule->arg.capid.expr, SMP_T_STR);
518 if (!key)
519 return ACT_RET_CONT;
520
521 if (cap[h->index] == NULL)
522 cap[h->index] = pool_alloc(h->pool);
523
524 if (cap[h->index] == NULL) /* no more capture memory */
525 return ACT_RET_CONT;
526
527 len = key->data.u.str.data;
528 if (len > h->len)
529 len = h->len;
530
531 memcpy(cap[h->index], key->data.u.str.area, len);
532 cap[h->index][len] = 0;
533 return ACT_RET_CONT;
534}
535
536/* Check an "http-request capture" action.
537 *
538 * The function returns 1 in success case, otherwise, it returns 0 and err is
539 * filled.
540 */
541static int check_http_req_capture(struct act_rule *rule, struct proxy *px, char **err)
542{
543 if (rule->action_ptr != http_action_req_capture_by_id)
544 return 1;
545
Baptiste Assmann19a69b32020-01-16 14:34:22 +0100546 /* capture slots can only be declared in frontends, so we can't check their
547 * existence in backends at configuration parsing step
548 */
549 if (px->cap & PR_CAP_FE && rule->arg.capid.idx >= px->nb_req_cap) {
Willy Tarreau79e57332018-10-02 16:01:16 +0200550 memprintf(err, "unable to find capture id '%d' referenced by http-request capture rule",
551 rule->arg.capid.idx);
552 return 0;
553 }
554
555 return 1;
556}
557
Christopher Faulet2eb53962020-01-14 14:47:34 +0100558/* Release memory allocate by an http capture action */
559static void release_http_capture(struct act_rule *rule)
560{
561 if (rule->action_ptr == http_action_req_capture)
562 release_sample_expr(rule->arg.cap.expr);
563 else
564 release_sample_expr(rule->arg.capid.expr);
565}
566
Willy Tarreau79e57332018-10-02 16:01:16 +0200567/* parse an "http-request capture" action. It takes a single argument which is
568 * a sample fetch expression. It stores the expression into arg->act.p[0] and
569 * the allocated hdr_cap struct or the preallocated "id" into arg->act.p[1].
570 * It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
571 */
572static enum act_parse_ret parse_http_req_capture(const char **args, int *orig_arg, struct proxy *px,
573 struct act_rule *rule, char **err)
574{
575 struct sample_expr *expr;
576 struct cap_hdr *hdr;
577 int cur_arg;
578 int len = 0;
579
580 for (cur_arg = *orig_arg; cur_arg < *orig_arg + 3 && *args[cur_arg]; cur_arg++)
581 if (strcmp(args[cur_arg], "if") == 0 ||
582 strcmp(args[cur_arg], "unless") == 0)
583 break;
584
585 if (cur_arg < *orig_arg + 3) {
586 memprintf(err, "expects <expression> [ 'len' <length> | id <idx> ]");
587 return ACT_RET_PRS_ERR;
588 }
589
590 cur_arg = *orig_arg;
Willy Tarreaue3b57bf2020-02-14 16:50:14 +0100591 expr = sample_parse_expr((char **)args, &cur_arg, px->conf.args.file, px->conf.args.line, err, &px->conf.args, NULL);
Willy Tarreau79e57332018-10-02 16:01:16 +0200592 if (!expr)
593 return ACT_RET_PRS_ERR;
594
595 if (!(expr->fetch->val & SMP_VAL_FE_HRQ_HDR)) {
596 memprintf(err,
597 "fetch method '%s' extracts information from '%s', none of which is available here",
598 args[cur_arg-1], sample_src_names(expr->fetch->use));
Christopher Faulet1337b322020-01-14 14:50:55 +0100599 release_sample_expr(expr);
Willy Tarreau79e57332018-10-02 16:01:16 +0200600 return ACT_RET_PRS_ERR;
601 }
602
603 if (!args[cur_arg] || !*args[cur_arg]) {
604 memprintf(err, "expects 'len or 'id'");
Christopher Faulet1337b322020-01-14 14:50:55 +0100605 release_sample_expr(expr);
Willy Tarreau79e57332018-10-02 16:01:16 +0200606 return ACT_RET_PRS_ERR;
607 }
608
609 if (strcmp(args[cur_arg], "len") == 0) {
610 cur_arg++;
611
612 if (!(px->cap & PR_CAP_FE)) {
613 memprintf(err, "proxy '%s' has no frontend capability", px->id);
Christopher Faulet1337b322020-01-14 14:50:55 +0100614 release_sample_expr(expr);
Willy Tarreau79e57332018-10-02 16:01:16 +0200615 return ACT_RET_PRS_ERR;
616 }
617
618 px->conf.args.ctx = ARGC_CAP;
619
620 if (!args[cur_arg]) {
621 memprintf(err, "missing length value");
Christopher Faulet1337b322020-01-14 14:50:55 +0100622 release_sample_expr(expr);
Willy Tarreau79e57332018-10-02 16:01:16 +0200623 return ACT_RET_PRS_ERR;
624 }
625 /* we copy the table name for now, it will be resolved later */
626 len = atoi(args[cur_arg]);
627 if (len <= 0) {
628 memprintf(err, "length must be > 0");
Christopher Faulet1337b322020-01-14 14:50:55 +0100629 release_sample_expr(expr);
Willy Tarreau79e57332018-10-02 16:01:16 +0200630 return ACT_RET_PRS_ERR;
631 }
632 cur_arg++;
633
Willy Tarreau79e57332018-10-02 16:01:16 +0200634 hdr = calloc(1, sizeof(*hdr));
635 hdr->next = px->req_cap;
636 hdr->name = NULL; /* not a header capture */
637 hdr->namelen = 0;
638 hdr->len = len;
639 hdr->pool = create_pool("caphdr", hdr->len + 1, MEM_F_SHARED);
640 hdr->index = px->nb_req_cap++;
641
642 px->req_cap = hdr;
643 px->to_log |= LW_REQHDR;
644
645 rule->action = ACT_CUSTOM;
646 rule->action_ptr = http_action_req_capture;
Christopher Faulet2eb53962020-01-14 14:47:34 +0100647 rule->release_ptr = release_http_capture;
Willy Tarreau79e57332018-10-02 16:01:16 +0200648 rule->arg.cap.expr = expr;
649 rule->arg.cap.hdr = hdr;
650 }
651
652 else if (strcmp(args[cur_arg], "id") == 0) {
653 int id;
654 char *error;
655
656 cur_arg++;
657
658 if (!args[cur_arg]) {
659 memprintf(err, "missing id value");
Christopher Faulet1337b322020-01-14 14:50:55 +0100660 release_sample_expr(expr);
Willy Tarreau79e57332018-10-02 16:01:16 +0200661 return ACT_RET_PRS_ERR;
662 }
663
664 id = strtol(args[cur_arg], &error, 10);
665 if (*error != '\0') {
666 memprintf(err, "cannot parse id '%s'", args[cur_arg]);
Christopher Faulet1337b322020-01-14 14:50:55 +0100667 release_sample_expr(expr);
Willy Tarreau79e57332018-10-02 16:01:16 +0200668 return ACT_RET_PRS_ERR;
669 }
670 cur_arg++;
671
672 px->conf.args.ctx = ARGC_CAP;
673
674 rule->action = ACT_CUSTOM;
675 rule->action_ptr = http_action_req_capture_by_id;
676 rule->check_ptr = check_http_req_capture;
Christopher Faulet2eb53962020-01-14 14:47:34 +0100677 rule->release_ptr = release_http_capture;
Willy Tarreau79e57332018-10-02 16:01:16 +0200678 rule->arg.capid.expr = expr;
679 rule->arg.capid.idx = id;
680 }
681
682 else {
683 memprintf(err, "expects 'len' or 'id', found '%s'", args[cur_arg]);
Christopher Faulet1337b322020-01-14 14:50:55 +0100684 release_sample_expr(expr);
Willy Tarreau79e57332018-10-02 16:01:16 +0200685 return ACT_RET_PRS_ERR;
686 }
687
688 *orig_arg = cur_arg;
689 return ACT_RET_PRS_OK;
690}
691
692/* This function executes the "capture" action and store the result in a
693 * capture slot if exists. It executes a fetch expression, turns the result
694 * into a string and puts it in a capture slot. It always returns 1. If an
695 * error occurs the action is cancelled, but the rule processing continues.
696 */
697static enum act_return http_action_res_capture_by_id(struct act_rule *rule, struct proxy *px,
698 struct session *sess, struct stream *s, int flags)
699{
700 struct sample *key;
701 struct cap_hdr *h;
702 char **cap = s->res_cap;
703 struct proxy *fe = strm_fe(s);
704 int len;
705 int i;
706
707 /* Look for the original configuration. */
708 for (h = fe->rsp_cap, i = fe->nb_rsp_cap - 1;
709 h != NULL && i != rule->arg.capid.idx ;
710 i--, h = h->next);
711 if (!h)
712 return ACT_RET_CONT;
713
714 key = sample_fetch_as_type(s->be, sess, s, SMP_OPT_DIR_RES|SMP_OPT_FINAL, rule->arg.capid.expr, SMP_T_STR);
715 if (!key)
716 return ACT_RET_CONT;
717
718 if (cap[h->index] == NULL)
719 cap[h->index] = pool_alloc(h->pool);
720
721 if (cap[h->index] == NULL) /* no more capture memory */
722 return ACT_RET_CONT;
723
724 len = key->data.u.str.data;
725 if (len > h->len)
726 len = h->len;
727
728 memcpy(cap[h->index], key->data.u.str.area, len);
729 cap[h->index][len] = 0;
730 return ACT_RET_CONT;
731}
732
733/* Check an "http-response capture" action.
734 *
735 * The function returns 1 in success case, otherwise, it returns 0 and err is
736 * filled.
737 */
738static int check_http_res_capture(struct act_rule *rule, struct proxy *px, char **err)
739{
740 if (rule->action_ptr != http_action_res_capture_by_id)
741 return 1;
742
Tim Duesterhusf3f4aa02020-07-03 13:43:42 +0200743 /* capture slots can only be declared in frontends, so we can't check their
744 * existence in backends at configuration parsing step
745 */
746 if (px->cap & PR_CAP_FE && rule->arg.capid.idx >= px->nb_rsp_cap) {
Willy Tarreau79e57332018-10-02 16:01:16 +0200747 memprintf(err, "unable to find capture id '%d' referenced by http-response capture rule",
748 rule->arg.capid.idx);
749 return 0;
750 }
751
752 return 1;
753}
754
755/* parse an "http-response capture" action. It takes a single argument which is
756 * a sample fetch expression. It stores the expression into arg->act.p[0] and
Thayne McCombs8f0cc5c2021-01-07 21:35:52 -0700757 * the allocated hdr_cap struct of the preallocated id into arg->act.p[1].
Willy Tarreau79e57332018-10-02 16:01:16 +0200758 * It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
759 */
760static enum act_parse_ret parse_http_res_capture(const char **args, int *orig_arg, struct proxy *px,
761 struct act_rule *rule, char **err)
762{
763 struct sample_expr *expr;
764 int cur_arg;
765 int id;
766 char *error;
767
768 for (cur_arg = *orig_arg; cur_arg < *orig_arg + 3 && *args[cur_arg]; cur_arg++)
769 if (strcmp(args[cur_arg], "if") == 0 ||
770 strcmp(args[cur_arg], "unless") == 0)
771 break;
772
773 if (cur_arg < *orig_arg + 3) {
774 memprintf(err, "expects <expression> id <idx>");
775 return ACT_RET_PRS_ERR;
776 }
777
778 cur_arg = *orig_arg;
Willy Tarreaue3b57bf2020-02-14 16:50:14 +0100779 expr = sample_parse_expr((char **)args, &cur_arg, px->conf.args.file, px->conf.args.line, err, &px->conf.args, NULL);
Willy Tarreau79e57332018-10-02 16:01:16 +0200780 if (!expr)
781 return ACT_RET_PRS_ERR;
782
783 if (!(expr->fetch->val & SMP_VAL_FE_HRS_HDR)) {
784 memprintf(err,
785 "fetch method '%s' extracts information from '%s', none of which is available here",
786 args[cur_arg-1], sample_src_names(expr->fetch->use));
Christopher Faulet1337b322020-01-14 14:50:55 +0100787 release_sample_expr(expr);
Willy Tarreau79e57332018-10-02 16:01:16 +0200788 return ACT_RET_PRS_ERR;
789 }
790
791 if (!args[cur_arg] || !*args[cur_arg]) {
792 memprintf(err, "expects 'id'");
Christopher Faulet1337b322020-01-14 14:50:55 +0100793 release_sample_expr(expr);
Willy Tarreau79e57332018-10-02 16:01:16 +0200794 return ACT_RET_PRS_ERR;
795 }
796
797 if (strcmp(args[cur_arg], "id") != 0) {
798 memprintf(err, "expects 'id', found '%s'", args[cur_arg]);
Christopher Faulet1337b322020-01-14 14:50:55 +0100799 release_sample_expr(expr);
Willy Tarreau79e57332018-10-02 16:01:16 +0200800 return ACT_RET_PRS_ERR;
801 }
802
803 cur_arg++;
804
805 if (!args[cur_arg]) {
806 memprintf(err, "missing id value");
Christopher Faulet1337b322020-01-14 14:50:55 +0100807 release_sample_expr(expr);
Willy Tarreau79e57332018-10-02 16:01:16 +0200808 return ACT_RET_PRS_ERR;
809 }
810
811 id = strtol(args[cur_arg], &error, 10);
812 if (*error != '\0') {
813 memprintf(err, "cannot parse id '%s'", args[cur_arg]);
Christopher Faulet1337b322020-01-14 14:50:55 +0100814 release_sample_expr(expr);
Willy Tarreau79e57332018-10-02 16:01:16 +0200815 return ACT_RET_PRS_ERR;
816 }
817 cur_arg++;
818
819 px->conf.args.ctx = ARGC_CAP;
820
821 rule->action = ACT_CUSTOM;
822 rule->action_ptr = http_action_res_capture_by_id;
823 rule->check_ptr = check_http_res_capture;
Christopher Faulet2eb53962020-01-14 14:47:34 +0100824 rule->release_ptr = release_http_capture;
Willy Tarreau79e57332018-10-02 16:01:16 +0200825 rule->arg.capid.expr = expr;
826 rule->arg.capid.idx = id;
827
828 *orig_arg = cur_arg;
829 return ACT_RET_PRS_OK;
830}
831
Christopher Faulet81e20172019-12-12 16:40:30 +0100832/* Parse a "allow" action for a request or a response rule. It takes no argument. It
833 * returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
834 */
835static enum act_parse_ret parse_http_allow(const char **args, int *orig_arg, struct proxy *px,
836 struct act_rule *rule, char **err)
837{
838 rule->action = ACT_ACTION_ALLOW;
Christopher Faulet245cf792019-12-18 14:58:12 +0100839 rule->flags |= ACT_FLAG_FINAL;
Christopher Faulet81e20172019-12-12 16:40:30 +0100840 return ACT_RET_PRS_OK;
841}
842
Christopher Faulete0fca292020-01-13 21:49:03 +0100843/* Parse "deny" or "tarpit" actions for a request rule or "deny" action for a
Christopher Faulet5cb513a2020-05-13 17:56:56 +0200844 * response rule. It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on
845 * error. It relies on http_parse_http_reply() to set
846 * <.arg.http_reply>.
Christopher Faulet81e20172019-12-12 16:40:30 +0100847 */
Christopher Faulete0fca292020-01-13 21:49:03 +0100848static enum act_parse_ret parse_http_deny(const char **args, int *orig_arg, struct proxy *px,
849 struct act_rule *rule, char **err)
Christopher Faulet81e20172019-12-12 16:40:30 +0100850{
Christopher Faulet5cb513a2020-05-13 17:56:56 +0200851 int default_status;
852 int cur_arg, arg = 0;
Christopher Faulet81e20172019-12-12 16:40:30 +0100853
854 cur_arg = *orig_arg;
Christopher Faulete0fca292020-01-13 21:49:03 +0100855 if (rule->from == ACT_F_HTTP_REQ) {
Tim Duesterhuse5ff1412021-01-02 22:31:53 +0100856 if (strcmp(args[cur_arg - 1], "tarpit") == 0) {
Christopher Faulete0fca292020-01-13 21:49:03 +0100857 rule->action = ACT_HTTP_REQ_TARPIT;
Christopher Faulet5cb513a2020-05-13 17:56:56 +0200858 default_status = 500;
Christopher Faulet81e20172019-12-12 16:40:30 +0100859 }
Christopher Faulete0fca292020-01-13 21:49:03 +0100860 else {
861 rule->action = ACT_ACTION_DENY;
Christopher Faulet5cb513a2020-05-13 17:56:56 +0200862 default_status = 403;
Christopher Faulet81e20172019-12-12 16:40:30 +0100863 }
Christopher Faulet81e20172019-12-12 16:40:30 +0100864 }
Christopher Faulete0fca292020-01-13 21:49:03 +0100865 else {
Christopher Faulet554c0eb2020-01-14 12:00:28 +0100866 rule->action = ACT_ACTION_DENY;
Christopher Faulet5cb513a2020-05-13 17:56:56 +0200867 default_status = 502;
Christopher Faulete0fca292020-01-13 21:49:03 +0100868 }
Christopher Faulet040c8cd2020-01-13 16:43:45 +0100869
Christopher Faulet5cb513a2020-05-13 17:56:56 +0200870 /* If no args or only a deny_status specified, fallback on the legacy
871 * mode and use default error files despite the fact that
872 * default-errorfiles is not used. Otherwise, parse an http reply.
873 */
Christopher Faulet040c8cd2020-01-13 16:43:45 +0100874
Christopher Faulet5cb513a2020-05-13 17:56:56 +0200875 /* Prepare parsing of log-format strings */
876 px->conf.args.ctx = ((rule->from == ACT_F_HTTP_REQ) ? ARGC_HRQ : ARGC_HRS);
Christopher Faulet554c0eb2020-01-14 12:00:28 +0100877
Christopher Faulet9467f182020-06-30 09:32:01 +0200878 if (!*(args[cur_arg]) || strcmp(args[cur_arg], "if") == 0 || strcmp(args[cur_arg], "unless") == 0) {
Christopher Faulet5cb513a2020-05-13 17:56:56 +0200879 rule->arg.http_reply = http_parse_http_reply((const char *[]){"default-errorfiles", ""}, &arg, px, default_status, err);
880 goto end;
Christopher Faulet554c0eb2020-01-14 12:00:28 +0100881 }
Christopher Faulet5cb513a2020-05-13 17:56:56 +0200882
883 if (strcmp(args[cur_arg], "deny_status") == 0) {
Christopher Faulet9467f182020-06-30 09:32:01 +0200884 if (!*(args[cur_arg+2]) || strcmp(args[cur_arg+2], "if") == 0 || strcmp(args[cur_arg+2], "unless") == 0) {
Christopher Faulet5cb513a2020-05-13 17:56:56 +0200885 rule->arg.http_reply = http_parse_http_reply((const char *[]){"status", args[cur_arg+1], "default-errorfiles", ""},
886 &arg, px, default_status, err);
887 *orig_arg += 2;
888 goto end;
Christopher Faulet554c0eb2020-01-14 12:00:28 +0100889 }
Christopher Faulet5cb513a2020-05-13 17:56:56 +0200890 args[cur_arg] += 5; /* skip "deny_" for the parsing */
Christopher Faulet554c0eb2020-01-14 12:00:28 +0100891 }
892
Christopher Faulet5cb513a2020-05-13 17:56:56 +0200893 rule->arg.http_reply = http_parse_http_reply(args, orig_arg, px, default_status, err);
Christopher Faulet554c0eb2020-01-14 12:00:28 +0100894
Christopher Faulet5cb513a2020-05-13 17:56:56 +0200895 end:
896 if (!rule->arg.http_reply)
897 return ACT_RET_PRS_ERR;
898
899 rule->flags |= ACT_FLAG_FINAL;
900 rule->check_ptr = check_act_http_reply;
901 rule->release_ptr = release_act_http_reply;
Christopher Faulet81e20172019-12-12 16:40:30 +0100902 return ACT_RET_PRS_OK;
903}
904
Christopher Fauletb3048832020-05-27 15:26:43 +0200905
906/* This function executes a auth action. It builds an 401/407 HTX message using
907 * the corresponding proxy's error message. On success, it returns
908 * ACT_RET_ABRT. If an error occurs ACT_RET_ERR is returned.
909 */
910static enum act_return http_action_auth(struct act_rule *rule, struct proxy *px,
911 struct session *sess, struct stream *s, int flags)
912{
913 struct channel *req = &s->req;
914 struct channel *res = &s->res;
915 struct htx *htx = htx_from_buf(&res->buf);
916 struct http_reply *reply;
917 const char *auth_realm;
918 struct http_hdr_ctx ctx;
919 struct ist hdr;
920
921 /* Auth might be performed on regular http-req rules as well as on stats */
922 auth_realm = rule->arg.http.str.ptr;
923 if (!auth_realm) {
924 if (px->uri_auth && s->current_rule_list == &px->uri_auth->http_req_rules)
925 auth_realm = STATS_DEFAULT_REALM;
926 else
927 auth_realm = px->id;
928 }
929
930 if (!(s->txn->flags & TX_USE_PX_CONN)) {
931 s->txn->status = 401;
932 hdr = ist("WWW-Authenticate");
933 }
934 else {
935 s->txn->status = 407;
936 hdr = ist("Proxy-Authenticate");
937 }
938 reply = http_error_message(s);
939 channel_htx_truncate(res, htx);
940
941 if (chunk_printf(&trash, "Basic realm=\"%s\"", auth_realm) == -1)
942 goto fail;
943
944 /* Write the generic 40x message */
945 if (http_reply_to_htx(s, htx, reply) == -1)
946 goto fail;
947
948 /* Remove all existing occurrences of the XXX-Authenticate header */
949 ctx.blk = NULL;
950 while (http_find_header(htx, hdr, &ctx, 1))
951 http_remove_header(htx, &ctx);
952
953 /* Now a the right XXX-Authenticate header */
954 if (!http_add_header(htx, hdr, ist2(b_orig(&trash), b_data(&trash))))
955 goto fail;
956
957 /* Finally forward the reply */
958 htx_to_buf(htx, &res->buf);
959 if (!http_forward_proxy_resp(s, 1))
960 goto fail;
961
962 /* Note: Only eval on the request */
963 s->logs.tv_request = now;
964 req->analysers &= AN_REQ_FLT_END;
965
966 if (s->sess->fe == s->be) /* report it if the request was intercepted by the frontend */
967 _HA_ATOMIC_ADD(&s->sess->fe->fe_counters.intercepted_req, 1);
968
969 if (!(s->flags & SF_ERR_MASK))
970 s->flags |= SF_ERR_LOCAL;
971 if (!(s->flags & SF_FINST_MASK))
972 s->flags |= SF_FINST_R;
973
974 stream_inc_http_err_ctr(s);
975 return ACT_RET_ABRT;
976
977 fail:
978 /* If an error occurred, remove the incomplete HTTP response from the
979 * buffer */
980 channel_htx_truncate(res, htx);
981 return ACT_RET_ERR;
982}
983
Christopher Faulet81e20172019-12-12 16:40:30 +0100984/* Parse a "auth" action. It may take 2 optional arguments to define a "realm"
985 * parameter. It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
986 */
987static enum act_parse_ret parse_http_auth(const char **args, int *orig_arg, struct proxy *px,
988 struct act_rule *rule, char **err)
989{
990 int cur_arg;
991
Christopher Fauletb3048832020-05-27 15:26:43 +0200992 rule->action = ACT_CUSTOM;
Christopher Faulet245cf792019-12-18 14:58:12 +0100993 rule->flags |= ACT_FLAG_FINAL;
Christopher Fauletb3048832020-05-27 15:26:43 +0200994 rule->action_ptr = http_action_auth;
Christopher Faulet2eb53962020-01-14 14:47:34 +0100995 rule->release_ptr = release_http_action;
Christopher Faulet81e20172019-12-12 16:40:30 +0100996
997 cur_arg = *orig_arg;
Tim Duesterhuse5ff1412021-01-02 22:31:53 +0100998 if (strcmp(args[cur_arg], "realm") == 0) {
Christopher Faulet81e20172019-12-12 16:40:30 +0100999 cur_arg++;
1000 if (!*args[cur_arg]) {
1001 memprintf(err, "missing realm value.\n");
1002 return ACT_RET_PRS_ERR;
1003 }
Christopher Faulet96bff762019-12-17 13:46:18 +01001004 rule->arg.http.str.ptr = strdup(args[cur_arg]);
1005 rule->arg.http.str.len = strlen(rule->arg.http.str.ptr);
Christopher Faulet81e20172019-12-12 16:40:30 +01001006 cur_arg++;
1007 }
1008
Christopher Fauletc20b3712020-01-27 15:51:56 +01001009 LIST_INIT(&rule->arg.http.fmt);
Christopher Faulet81e20172019-12-12 16:40:30 +01001010 *orig_arg = cur_arg;
1011 return ACT_RET_PRS_OK;
1012}
1013
1014/* Parse a "set-nice" action. It takes the nice value as argument. It returns
1015 * ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
1016 */
1017static enum act_parse_ret parse_http_set_nice(const char **args, int *orig_arg, struct proxy *px,
1018 struct act_rule *rule, char **err)
1019{
1020 int cur_arg;
1021
1022 rule->action = ACT_HTTP_SET_NICE;
1023
1024 cur_arg = *orig_arg;
1025 if (!*args[cur_arg]) {
1026 memprintf(err, "expects exactly 1 argument (integer value)");
1027 return ACT_RET_PRS_ERR;
1028 }
Christopher Faulet96bff762019-12-17 13:46:18 +01001029 rule->arg.http.i = atoi(args[cur_arg]);
1030 if (rule->arg.http.i < -1024)
1031 rule->arg.http.i = -1024;
1032 else if (rule->arg.http.i > 1024)
1033 rule->arg.http.i = 1024;
Christopher Faulet81e20172019-12-12 16:40:30 +01001034
Christopher Fauletc20b3712020-01-27 15:51:56 +01001035 LIST_INIT(&rule->arg.http.fmt);
Christopher Faulet81e20172019-12-12 16:40:30 +01001036 *orig_arg = cur_arg + 1;
1037 return ACT_RET_PRS_OK;
1038}
1039
1040/* Parse a "set-tos" action. It takes the TOS value as argument. It returns
1041 * ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
1042 */
1043static enum act_parse_ret parse_http_set_tos(const char **args, int *orig_arg, struct proxy *px,
1044 struct act_rule *rule, char **err)
1045{
1046#ifdef IP_TOS
1047 char *endp;
1048 int cur_arg;
1049
1050 rule->action = ACT_HTTP_SET_TOS;
1051
1052 cur_arg = *orig_arg;
1053 if (!*args[cur_arg]) {
1054 memprintf(err, "expects exactly 1 argument (integer/hex value)");
1055 return ACT_RET_PRS_ERR;
1056 }
Christopher Faulet96bff762019-12-17 13:46:18 +01001057 rule->arg.http.i = strtol(args[cur_arg], &endp, 0);
Christopher Faulet81e20172019-12-12 16:40:30 +01001058 if (endp && *endp != '\0') {
1059 memprintf(err, "invalid character starting at '%s' (integer/hex value expected)", endp);
1060 return ACT_RET_PRS_ERR;
1061 }
1062
Christopher Fauletc20b3712020-01-27 15:51:56 +01001063 LIST_INIT(&rule->arg.http.fmt);
Christopher Faulet81e20172019-12-12 16:40:30 +01001064 *orig_arg = cur_arg + 1;
1065 return ACT_RET_PRS_OK;
1066#else
1067 memprintf(err, "not supported on this platform (IP_TOS undefined)");
1068 return ACT_RET_PRS_ERR;
1069#endif
1070}
1071
1072/* Parse a "set-mark" action. It takes the MARK value as argument. It returns
1073 * ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
1074 */
1075static enum act_parse_ret parse_http_set_mark(const char **args, int *orig_arg, struct proxy *px,
1076 struct act_rule *rule, char **err)
1077{
1078#ifdef SO_MARK
1079 char *endp;
1080 int cur_arg;
1081
1082 rule->action = ACT_HTTP_SET_MARK;
1083
1084 cur_arg = *orig_arg;
1085 if (!*args[cur_arg]) {
1086 memprintf(err, "expects exactly 1 argument (integer/hex value)");
1087 return ACT_RET_PRS_ERR;
1088 }
Christopher Faulet96bff762019-12-17 13:46:18 +01001089 rule->arg.http.i = strtoul(args[cur_arg], &endp, 0);
Christopher Faulet81e20172019-12-12 16:40:30 +01001090 if (endp && *endp != '\0') {
1091 memprintf(err, "invalid character starting at '%s' (integer/hex value expected)", endp);
1092 return ACT_RET_PRS_ERR;
1093 }
1094
Christopher Fauletc20b3712020-01-27 15:51:56 +01001095 LIST_INIT(&rule->arg.http.fmt);
Christopher Faulet81e20172019-12-12 16:40:30 +01001096 *orig_arg = cur_arg + 1;
1097 global.last_checks |= LSTCHK_NETADM;
1098 return ACT_RET_PRS_OK;
1099#else
1100 memprintf(err, "not supported on this platform (SO_MARK undefined)");
1101 return ACT_RET_PRS_ERR;
1102#endif
1103}
1104
1105/* Parse a "set-log-level" action. It takes the level value as argument. It
1106 * returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
1107 */
1108static enum act_parse_ret parse_http_set_log_level(const char **args, int *orig_arg, struct proxy *px,
1109 struct act_rule *rule, char **err)
1110{
1111 int cur_arg;
1112
1113 rule->action = ACT_HTTP_SET_LOGL;
1114
1115 cur_arg = *orig_arg;
1116 if (!*args[cur_arg]) {
1117 bad_log_level:
1118 memprintf(err, "expects exactly 1 argument (log level name or 'silent')");
1119 return ACT_RET_PRS_ERR;
1120 }
1121 if (strcmp(args[cur_arg], "silent") == 0)
Christopher Faulet96bff762019-12-17 13:46:18 +01001122 rule->arg.http.i = -1;
1123 else if ((rule->arg.http.i = get_log_level(args[cur_arg]) + 1) == 0)
Christopher Faulet81e20172019-12-12 16:40:30 +01001124 goto bad_log_level;
1125
Christopher Fauletc20b3712020-01-27 15:51:56 +01001126 LIST_INIT(&rule->arg.http.fmt);
Christopher Faulet81e20172019-12-12 16:40:30 +01001127 *orig_arg = cur_arg + 1;
1128 return ACT_RET_PRS_OK;
1129}
1130
Christopher Faulet91b3ec12020-01-17 22:30:06 +01001131/* This function executes a early-hint action. It adds an HTTP Early Hint HTTP
1132 * 103 response header with <.arg.http.str> name and with a value built
1133 * according to <.arg.http.fmt> log line format. If it is the first early-hint
Ilya Shipitsinc02a23f2020-05-06 00:53:22 +05001134 * rule of series, the 103 response start-line is added first. At the end, if
Christopher Faulet91b3ec12020-01-17 22:30:06 +01001135 * the next rule is not an early-hint rule or if it is the last rule, the EOH
1136 * block is added to terminate the response. On success, it returns
1137 * ACT_RET_CONT. If an error occurs while soft rewrites are enabled, the action
1138 * is canceled, but the rule processing continue. Otherwsize ACT_RET_ERR is
1139 * returned.
1140 */
1141static enum act_return http_action_early_hint(struct act_rule *rule, struct proxy *px,
1142 struct session *sess, struct stream *s, int flags)
1143{
1144 struct act_rule *prev_rule, *next_rule;
1145 struct channel *res = &s->res;
1146 struct htx *htx = htx_from_buf(&res->buf);
1147 struct buffer *value = alloc_trash_chunk();
1148 enum act_return ret = ACT_RET_CONT;
1149
1150 if (!(s->txn->req.flags & HTTP_MSGF_VER_11))
1151 goto leave;
1152
1153 if (!value) {
1154 if (!(s->flags & SF_ERR_MASK))
1155 s->flags |= SF_ERR_RESOURCE;
1156 goto error;
1157 }
1158
1159 /* get previous and next rules */
1160 prev_rule = LIST_PREV(&rule->list, typeof(rule), list);
1161 next_rule = LIST_NEXT(&rule->list, typeof(rule), list);
1162
1163 /* if no previous rule or previous rule is not early-hint, start a new response. Otherwise,
1164 * continue to add link to a previously started response */
1165 if (&prev_rule->list == s->current_rule_list || prev_rule->action_ptr != http_action_early_hint) {
1166 struct htx_sl *sl;
1167 unsigned int flags = (HTX_SL_F_IS_RESP|HTX_SL_F_VER_11|
1168 HTX_SL_F_XFER_LEN|HTX_SL_F_BODYLESS);
1169
1170 sl = htx_add_stline(htx, HTX_BLK_RES_SL, flags,
1171 ist("HTTP/1.1"), ist("103"), ist("Early Hints"));
1172 if (!sl)
1173 goto error;
1174 sl->info.res.status = 103;
1175 }
1176
1177 /* Add the HTTP Early Hint HTTP 103 response heade */
1178 value->data = build_logline(s, b_tail(value), b_room(value), &rule->arg.http.fmt);
1179 if (!htx_add_header(htx, rule->arg.http.str, ist2(b_head(value), b_data(value))))
1180 goto error;
1181
1182 /* if it is the last rule or the next one is not an early-hint, terminate the current
1183 * response. */
1184 if (&next_rule->list == s->current_rule_list || next_rule->action_ptr != http_action_early_hint) {
Christopher Faulet91b3ec12020-01-17 22:30:06 +01001185 if (!htx_add_endof(htx, HTX_BLK_EOH)) {
1186 /* If an error occurred during an Early-hint rule,
1187 * remove the incomplete HTTP 103 response from the
1188 * buffer */
1189 goto error;
1190 }
1191
Christopher Fauleta72a7e42020-01-28 09:28:11 +01001192 if (!http_forward_proxy_resp(s, 0))
1193 goto error;
Christopher Faulet91b3ec12020-01-17 22:30:06 +01001194 }
1195
1196 leave:
1197 free_trash_chunk(value);
1198 return ret;
1199
1200 error:
1201 /* If an error occurred during an Early-hint rule, remove the incomplete
1202 * HTTP 103 response from the buffer */
1203 channel_htx_truncate(res, htx);
1204 ret = ACT_RET_ERR;
1205 goto leave;
1206}
1207
Christopher Fauletd1f27e32019-12-17 09:33:38 +01001208/* This function executes a set-header or add-header actions. It builds a string
1209 * in the trash from the specified format string. It finds the action to be
1210 * performed in <.action>, previously filled by function parse_set_header(). The
Ilya Shipitsinc02a23f2020-05-06 00:53:22 +05001211 * replacement action is executed by the function http_action_set_header(). On
Christopher Fauletd1f27e32019-12-17 09:33:38 +01001212 * success, it returns ACT_RET_CONT. If an error occurs while soft rewrites are
1213 * enabled, the action is canceled, but the rule processing continue. Otherwsize
1214 * ACT_RET_ERR is returned.
1215 */
1216static enum act_return http_action_set_header(struct act_rule *rule, struct proxy *px,
1217 struct session *sess, struct stream *s, int flags)
1218{
Christopher Faulet91e31d82020-01-24 15:37:13 +01001219 struct http_msg *msg = ((rule->from == ACT_F_HTTP_REQ) ? &s->txn->req : &s->txn->rsp);
1220 struct htx *htx = htxbuf(&msg->chn->buf);
Christopher Fauletd1f27e32019-12-17 09:33:38 +01001221 enum act_return ret = ACT_RET_CONT;
1222 struct buffer *replace;
1223 struct http_hdr_ctx ctx;
1224 struct ist n, v;
1225
1226 replace = alloc_trash_chunk();
1227 if (!replace)
1228 goto fail_alloc;
1229
1230 replace->data = build_logline(s, replace->area, replace->size, &rule->arg.http.fmt);
1231 n = rule->arg.http.str;
1232 v = ist2(replace->area, replace->data);
1233
1234 if (rule->action == 0) { // set-header
1235 /* remove all occurrences of the header */
1236 ctx.blk = NULL;
1237 while (http_find_header(htx, n, &ctx, 1))
1238 http_remove_header(htx, &ctx);
1239 }
1240
1241 /* Now add header */
1242 if (!http_add_header(htx, n, v))
1243 goto fail_rewrite;
1244
1245 leave:
1246 free_trash_chunk(replace);
1247 return ret;
1248
1249 fail_alloc:
1250 if (!(s->flags & SF_ERR_MASK))
1251 s->flags |= SF_ERR_RESOURCE;
1252 ret = ACT_RET_ERR;
1253 goto leave;
1254
1255 fail_rewrite:
1256 _HA_ATOMIC_ADD(&sess->fe->fe_counters.failed_rewrites, 1);
1257 if (s->flags & SF_BE_ASSIGNED)
1258 _HA_ATOMIC_ADD(&s->be->be_counters.failed_rewrites, 1);
William Lallemand36119de2021-03-08 15:26:48 +01001259 if (sess->listener && sess->listener->counters)
Christopher Fauletd1f27e32019-12-17 09:33:38 +01001260 _HA_ATOMIC_ADD(&sess->listener->counters->failed_rewrites, 1);
1261 if (objt_server(s->target))
1262 _HA_ATOMIC_ADD(&__objt_server(s->target)->counters.failed_rewrites, 1);
1263
Christopher Faulet333bf8c2020-01-22 14:38:05 +01001264 if (!(msg->flags & HTTP_MSGF_SOFT_RW)) {
Christopher Fauletd1f27e32019-12-17 09:33:38 +01001265 ret = ACT_RET_ERR;
Christopher Faulet333bf8c2020-01-22 14:38:05 +01001266 if (!(s->flags & SF_ERR_MASK))
1267 s->flags |= SF_ERR_PRXCOND;
1268 }
Christopher Fauletd1f27e32019-12-17 09:33:38 +01001269 goto leave;
1270}
1271
Christopher Faulet81e20172019-12-12 16:40:30 +01001272/* Parse a "set-header", "add-header" or "early-hint" actions. It takes an
1273 * header name and a log-format string as arguments. It returns ACT_RET_PRS_OK
1274 * on success, ACT_RET_PRS_ERR on error.
1275 *
1276 * Note: same function is used for the request and the response. However
1277 * "early-hint" rules are only supported for request rules.
1278 */
1279static enum act_parse_ret parse_http_set_header(const char **args, int *orig_arg, struct proxy *px,
1280 struct act_rule *rule, char **err)
1281{
Christopher Faulet81e20172019-12-12 16:40:30 +01001282 int cap, cur_arg;
1283
Christopher Faulet91b3ec12020-01-17 22:30:06 +01001284 if (args[*orig_arg-1][0] == 'e') {
1285 rule->action = ACT_CUSTOM;
1286 rule->action_ptr = http_action_early_hint;
1287 }
Christopher Fauletd1f27e32019-12-17 09:33:38 +01001288 else {
1289 if (args[*orig_arg-1][0] == 's')
1290 rule->action = 0; // set-header
1291 else
1292 rule->action = 1; // add-header
1293 rule->action_ptr = http_action_set_header;
1294 }
Christopher Faulet2eb53962020-01-14 14:47:34 +01001295 rule->release_ptr = release_http_action;
Christopher Faulet81e20172019-12-12 16:40:30 +01001296
1297 cur_arg = *orig_arg;
1298 if (!*args[cur_arg] || !*args[cur_arg+1]) {
1299 memprintf(err, "expects exactly 2 arguments");
1300 return ACT_RET_PRS_ERR;
1301 }
1302
Christopher Faulet81e20172019-12-12 16:40:30 +01001303
Christopher Faulet96bff762019-12-17 13:46:18 +01001304 rule->arg.http.str.ptr = strdup(args[cur_arg]);
1305 rule->arg.http.str.len = strlen(rule->arg.http.str.ptr);
1306 LIST_INIT(&rule->arg.http.fmt);
Christopher Faulet81e20172019-12-12 16:40:30 +01001307
1308 if (rule->from == ACT_F_HTTP_REQ) {
1309 px->conf.args.ctx = ARGC_HRQ;
1310 cap = (px->cap & PR_CAP_FE) ? SMP_VAL_FE_HRQ_HDR : SMP_VAL_BE_HRQ_HDR;
1311 }
1312 else{
1313 px->conf.args.ctx = ARGC_HRS;
1314 cap = (px->cap & PR_CAP_BE) ? SMP_VAL_BE_HRS_HDR : SMP_VAL_FE_HRS_HDR;
1315 }
1316
1317 cur_arg++;
Christopher Faulet1337b322020-01-14 14:50:55 +01001318 if (!parse_logformat_string(args[cur_arg], px, &rule->arg.http.fmt, LOG_OPT_HTTP, cap, err)) {
Tim Duesterhused526372020-03-05 17:56:33 +01001319 istfree(&rule->arg.http.str);
Christopher Faulet81e20172019-12-12 16:40:30 +01001320 return ACT_RET_PRS_ERR;
Christopher Faulet1337b322020-01-14 14:50:55 +01001321 }
Christopher Faulet81e20172019-12-12 16:40:30 +01001322
1323 free(px->conf.lfs_file);
1324 px->conf.lfs_file = strdup(px->conf.args.file);
1325 px->conf.lfs_line = px->conf.args.line;
1326
1327 *orig_arg = cur_arg + 1;
1328 return ACT_RET_PRS_OK;
1329}
1330
Christopher Faulet92d34fe2019-12-17 09:20:34 +01001331/* This function executes a replace-header or replace-value actions. It
1332 * builds a string in the trash from the specified format string. It finds
1333 * the action to be performed in <.action>, previously filled by function
Ilya Shipitsinc02a23f2020-05-06 00:53:22 +05001334 * parse_replace_header(). The replacement action is executed by the function
Christopher Faulet92d34fe2019-12-17 09:20:34 +01001335 * http_action_replace_header(). On success, it returns ACT_RET_CONT. If an error
1336 * occurs while soft rewrites are enabled, the action is canceled, but the rule
1337 * processing continue. Otherwsize ACT_RET_ERR is returned.
1338 */
1339static enum act_return http_action_replace_header(struct act_rule *rule, struct proxy *px,
1340 struct session *sess, struct stream *s, int flags)
1341{
Christopher Faulet91e31d82020-01-24 15:37:13 +01001342 struct http_msg *msg = ((rule->from == ACT_F_HTTP_REQ) ? &s->txn->req : &s->txn->rsp);
1343 struct htx *htx = htxbuf(&msg->chn->buf);
Christopher Faulet92d34fe2019-12-17 09:20:34 +01001344 enum act_return ret = ACT_RET_CONT;
1345 struct buffer *replace;
1346 int r;
1347
1348 replace = alloc_trash_chunk();
1349 if (!replace)
1350 goto fail_alloc;
1351
1352 replace->data = build_logline(s, replace->area, replace->size, &rule->arg.http.fmt);
1353
1354 r = http_replace_hdrs(s, htx, rule->arg.http.str, replace->area, rule->arg.http.re, (rule->action == 0));
1355 if (r == -1)
1356 goto fail_rewrite;
1357
1358 leave:
1359 free_trash_chunk(replace);
1360 return ret;
1361
1362 fail_alloc:
1363 if (!(s->flags & SF_ERR_MASK))
1364 s->flags |= SF_ERR_RESOURCE;
1365 ret = ACT_RET_ERR;
1366 goto leave;
1367
1368 fail_rewrite:
1369 _HA_ATOMIC_ADD(&sess->fe->fe_counters.failed_rewrites, 1);
1370 if (s->flags & SF_BE_ASSIGNED)
1371 _HA_ATOMIC_ADD(&s->be->be_counters.failed_rewrites, 1);
William Lallemand36119de2021-03-08 15:26:48 +01001372 if (sess->listener && sess->listener->counters)
Christopher Faulet92d34fe2019-12-17 09:20:34 +01001373 _HA_ATOMIC_ADD(&sess->listener->counters->failed_rewrites, 1);
1374 if (objt_server(s->target))
1375 _HA_ATOMIC_ADD(&__objt_server(s->target)->counters.failed_rewrites, 1);
1376
Christopher Faulet333bf8c2020-01-22 14:38:05 +01001377 if (!(msg->flags & HTTP_MSGF_SOFT_RW)) {
Christopher Faulet92d34fe2019-12-17 09:20:34 +01001378 ret = ACT_RET_ERR;
Christopher Faulet333bf8c2020-01-22 14:38:05 +01001379 if (!(s->flags & SF_ERR_MASK))
1380 s->flags |= SF_ERR_PRXCOND;
1381 }
Christopher Faulet92d34fe2019-12-17 09:20:34 +01001382 goto leave;
1383}
1384
Christopher Faulet81e20172019-12-12 16:40:30 +01001385/* Parse a "replace-header" or "replace-value" actions. It takes an header name,
1386 * a regex and replacement string as arguments. It returns ACT_RET_PRS_OK on
1387 * success, ACT_RET_PRS_ERR on error.
1388 */
1389static enum act_parse_ret parse_http_replace_header(const char **args, int *orig_arg, struct proxy *px,
1390 struct act_rule *rule, char **err)
1391{
1392 int cap, cur_arg;
1393
Christopher Faulet92d34fe2019-12-17 09:20:34 +01001394 if (args[*orig_arg-1][8] == 'h')
1395 rule->action = 0; // replace-header
1396 else
1397 rule->action = 1; // replace-value
1398 rule->action_ptr = http_action_replace_header;
Christopher Faulet2eb53962020-01-14 14:47:34 +01001399 rule->release_ptr = release_http_action;
Christopher Faulet81e20172019-12-12 16:40:30 +01001400
1401 cur_arg = *orig_arg;
1402 if (!*args[cur_arg] || !*args[cur_arg+1] || !*args[cur_arg+2]) {
1403 memprintf(err, "expects exactly 3 arguments");
1404 return ACT_RET_PRS_ERR;
1405 }
1406
Christopher Faulet96bff762019-12-17 13:46:18 +01001407 rule->arg.http.str.ptr = strdup(args[cur_arg]);
1408 rule->arg.http.str.len = strlen(rule->arg.http.str.ptr);
1409 LIST_INIT(&rule->arg.http.fmt);
Christopher Faulet81e20172019-12-12 16:40:30 +01001410
1411 cur_arg++;
Christopher Faulet1337b322020-01-14 14:50:55 +01001412 if (!(rule->arg.http.re = regex_comp(args[cur_arg], 1, 1, err))) {
Tim Duesterhused526372020-03-05 17:56:33 +01001413 istfree(&rule->arg.http.str);
Christopher Faulet81e20172019-12-12 16:40:30 +01001414 return ACT_RET_PRS_ERR;
Christopher Faulet1337b322020-01-14 14:50:55 +01001415 }
Christopher Faulet81e20172019-12-12 16:40:30 +01001416
1417 if (rule->from == ACT_F_HTTP_REQ) {
1418 px->conf.args.ctx = ARGC_HRQ;
1419 cap = (px->cap & PR_CAP_FE) ? SMP_VAL_FE_HRQ_HDR : SMP_VAL_BE_HRQ_HDR;
1420 }
1421 else{
1422 px->conf.args.ctx = ARGC_HRS;
1423 cap = (px->cap & PR_CAP_BE) ? SMP_VAL_BE_HRS_HDR : SMP_VAL_FE_HRS_HDR;
1424 }
1425
1426 cur_arg++;
Christopher Faulet1337b322020-01-14 14:50:55 +01001427 if (!parse_logformat_string(args[cur_arg], px, &rule->arg.http.fmt, LOG_OPT_HTTP, cap, err)) {
Tim Duesterhused526372020-03-05 17:56:33 +01001428 istfree(&rule->arg.http.str);
Christopher Faulet1337b322020-01-14 14:50:55 +01001429 regex_free(rule->arg.http.re);
Christopher Faulet81e20172019-12-12 16:40:30 +01001430 return ACT_RET_PRS_ERR;
Christopher Faulet1337b322020-01-14 14:50:55 +01001431 }
Christopher Faulet81e20172019-12-12 16:40:30 +01001432
1433 free(px->conf.lfs_file);
1434 px->conf.lfs_file = strdup(px->conf.args.file);
1435 px->conf.lfs_line = px->conf.args.line;
1436
1437 *orig_arg = cur_arg + 1;
1438 return ACT_RET_PRS_OK;
1439}
1440
Maciej Zdebebdd4c52020-11-20 13:58:48 +00001441/* This function executes a del-header action with selected matching mode for
1442 * header name. It finds the matching method to be performed in <.action>, previously
1443 * filled by function parse_http_del_header(). On success, it returns ACT_RET_CONT.
1444 * Otherwise ACT_RET_ERR is returned.
1445 */
1446static enum act_return http_action_del_header(struct act_rule *rule, struct proxy *px,
1447 struct session *sess, struct stream *s, int flags)
1448{
1449 struct http_hdr_ctx ctx;
1450 struct http_msg *msg = ((rule->from == ACT_F_HTTP_REQ) ? &s->txn->req : &s->txn->rsp);
1451 struct htx *htx = htxbuf(&msg->chn->buf);
1452 enum act_return ret = ACT_RET_CONT;
1453
1454 /* remove all occurrences of the header */
1455 ctx.blk = NULL;
1456 switch (rule->action) {
1457 case PAT_MATCH_STR:
1458 while (http_find_header(htx, rule->arg.http.str, &ctx, 1))
1459 http_remove_header(htx, &ctx);
1460 break;
1461 case PAT_MATCH_BEG:
1462 while (http_find_pfx_header(htx, rule->arg.http.str, &ctx, 1))
1463 http_remove_header(htx, &ctx);
1464 break;
1465 case PAT_MATCH_END:
1466 while (http_find_sfx_header(htx, rule->arg.http.str, &ctx, 1))
1467 http_remove_header(htx, &ctx);
1468 break;
1469 case PAT_MATCH_SUB:
1470 while (http_find_sub_header(htx, rule->arg.http.str, &ctx, 1))
1471 http_remove_header(htx, &ctx);
1472 break;
1473 case PAT_MATCH_REG:
1474 while (http_match_header(htx, rule->arg.http.re, &ctx, 1))
1475 http_remove_header(htx, &ctx);
1476 break;
1477 default:
1478 return ACT_RET_ERR;
1479 }
1480 return ret;
1481}
1482
1483/* Parse a "del-header" action. It takes string as a required argument,
1484 * optional flag (currently only -m) and optional matching method of input string
1485 * with header name to be deleted. Default matching method is exact match (-m str).
1486 * It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
Christopher Faulet81e20172019-12-12 16:40:30 +01001487 */
1488static enum act_parse_ret parse_http_del_header(const char **args, int *orig_arg, struct proxy *px,
1489 struct act_rule *rule, char **err)
1490{
1491 int cur_arg;
Maciej Zdebebdd4c52020-11-20 13:58:48 +00001492 int pat_idx;
Christopher Faulet81e20172019-12-12 16:40:30 +01001493
Maciej Zdebebdd4c52020-11-20 13:58:48 +00001494 /* set exact matching (-m str) as default */
1495 rule->action = PAT_MATCH_STR;
1496 rule->action_ptr = http_action_del_header;
Christopher Faulet2eb53962020-01-14 14:47:34 +01001497 rule->release_ptr = release_http_action;
Christopher Faulet81e20172019-12-12 16:40:30 +01001498
1499 cur_arg = *orig_arg;
1500 if (!*args[cur_arg]) {
Maciej Zdebebdd4c52020-11-20 13:58:48 +00001501 memprintf(err, "expects at least 1 argument");
Christopher Faulet81e20172019-12-12 16:40:30 +01001502 return ACT_RET_PRS_ERR;
1503 }
1504
Christopher Faulet96bff762019-12-17 13:46:18 +01001505 rule->arg.http.str.ptr = strdup(args[cur_arg]);
1506 rule->arg.http.str.len = strlen(rule->arg.http.str.ptr);
Christopher Faulet81e20172019-12-12 16:40:30 +01001507 px->conf.args.ctx = (rule->from == ACT_F_HTTP_REQ ? ARGC_HRQ : ARGC_HRS);
1508
Maciej Zdeb6dee9962020-11-23 16:03:09 +00001509 LIST_INIT(&rule->arg.http.fmt);
Maciej Zdebebdd4c52020-11-20 13:58:48 +00001510 if (strcmp(args[cur_arg+1], "-m") == 0) {
1511 cur_arg++;
1512 if (!*args[cur_arg+1]) {
1513 memprintf(err, "-m flag expects exactly 1 argument");
1514 return ACT_RET_PRS_ERR;
1515 }
1516
1517 cur_arg++;
1518 pat_idx = pat_find_match_name(args[cur_arg]);
1519 switch (pat_idx) {
1520 case PAT_MATCH_REG:
1521 if (!(rule->arg.http.re = regex_comp(rule->arg.http.str.ptr, 1, 1, err)))
1522 return ACT_RET_PRS_ERR;
1523 /* fall through */
1524 case PAT_MATCH_STR:
1525 case PAT_MATCH_BEG:
1526 case PAT_MATCH_END:
1527 case PAT_MATCH_SUB:
1528 rule->action = pat_idx;
1529 break;
1530 default:
1531 memprintf(err, "-m with unsupported matching method '%s'", args[cur_arg]);
1532 return ACT_RET_PRS_ERR;
1533 }
1534 }
1535
Christopher Faulet81e20172019-12-12 16:40:30 +01001536 *orig_arg = cur_arg + 1;
1537 return ACT_RET_PRS_OK;
1538}
1539
Christopher Faulet2eb53962020-01-14 14:47:34 +01001540/* Release memory allocated by an http redirect action. */
1541static void release_http_redir(struct act_rule *rule)
1542{
1543 struct logformat_node *lf, *lfb;
1544 struct redirect_rule *redir;
1545
1546 redir = rule->arg.redir;
1547 LIST_DEL(&redir->list);
1548 if (redir->cond) {
1549 prune_acl_cond(redir->cond);
1550 free(redir->cond);
1551 }
1552 free(redir->rdr_str);
1553 free(redir->cookie_str);
1554 list_for_each_entry_safe(lf, lfb, &redir->rdr_fmt, list) {
1555 LIST_DEL(&lf->list);
1556 free(lf);
1557 }
1558 free(redir);
1559}
1560
Christopher Faulet81e20172019-12-12 16:40:30 +01001561/* Parse a "redirect" action. It returns ACT_RET_PRS_OK on success,
1562 * ACT_RET_PRS_ERR on error.
1563 */
1564static enum act_parse_ret parse_http_redirect(const char **args, int *orig_arg, struct proxy *px,
1565 struct act_rule *rule, char **err)
1566{
1567 struct redirect_rule *redir;
1568 int dir, cur_arg;
1569
1570 rule->action = ACT_HTTP_REDIR;
Christopher Faulet245cf792019-12-18 14:58:12 +01001571 rule->flags |= ACT_FLAG_FINAL;
Christopher Faulet2eb53962020-01-14 14:47:34 +01001572 rule->release_ptr = release_http_redir;
Christopher Faulet81e20172019-12-12 16:40:30 +01001573
1574 cur_arg = *orig_arg;
1575
1576 dir = (rule->from == ACT_F_HTTP_REQ ? 0 : 1);
1577 if ((redir = http_parse_redirect_rule(px->conf.args.file, px->conf.args.line, px, &args[cur_arg], err, 1, dir)) == NULL)
1578 return ACT_RET_PRS_ERR;
1579
1580 rule->arg.redir = redir;
1581 rule->cond = redir->cond;
1582 redir->cond = NULL;
1583
1584 /* skip all arguments */
1585 while (*args[cur_arg])
1586 cur_arg++;
1587
1588 *orig_arg = cur_arg;
1589 return ACT_RET_PRS_OK;
1590}
1591
Christopher Faulet046cf442019-12-17 15:45:23 +01001592/* This function executes a add-acl, del-acl, set-map or del-map actions. On
1593 * success, it returns ACT_RET_CONT. Otherwsize ACT_RET_ERR is returned.
1594 */
1595static enum act_return http_action_set_map(struct act_rule *rule, struct proxy *px,
1596 struct session *sess, struct stream *s, int flags)
1597{
1598 struct pat_ref *ref;
1599 struct buffer *key = NULL, *value = NULL;
1600 enum act_return ret = ACT_RET_CONT;
1601
1602 /* collect reference */
1603 ref = pat_ref_lookup(rule->arg.map.ref);
1604 if (!ref)
1605 goto leave;
1606
1607 /* allocate key */
1608 key = alloc_trash_chunk();
1609 if (!key)
1610 goto fail_alloc;
1611
1612 /* collect key */
1613 key->data = build_logline(s, key->area, key->size, &rule->arg.map.key);
1614 key->area[key->data] = '\0';
1615
1616 switch (rule->action) {
1617 case 0: // add-acl
1618 /* add entry only if it does not already exist */
1619 HA_SPIN_LOCK(PATREF_LOCK, &ref->lock);
1620 if (pat_ref_find_elt(ref, key->area) == NULL)
1621 pat_ref_add(ref, key->area, NULL, NULL);
1622 HA_SPIN_UNLOCK(PATREF_LOCK, &ref->lock);
1623 break;
1624
1625 case 1: // set-map
1626 /* allocate value */
1627 value = alloc_trash_chunk();
1628 if (!value)
1629 goto fail_alloc;
1630
1631 /* collect value */
1632 value->data = build_logline(s, value->area, value->size, &rule->arg.map.value);
1633 value->area[value->data] = '\0';
1634
1635 HA_SPIN_LOCK(PATREF_LOCK, &ref->lock);
1636 if (pat_ref_find_elt(ref, key->area) != NULL) {
1637 /* update entry if it exists */
1638 pat_ref_set(ref, key->area, value->area, NULL);
1639 }
1640 else {
1641 /* insert a new entry */
1642 pat_ref_add(ref, key->area, value->area, NULL);
1643 }
1644 HA_SPIN_UNLOCK(PATREF_LOCK, &ref->lock);
1645 break;
1646
1647 case 2: // del-acl
1648 case 3: // del-map
1649 /* returned code: 1=ok, 0=ko */
1650 HA_SPIN_LOCK(PATREF_LOCK, &ref->lock);
1651 pat_ref_delete(ref, key->area);
1652 HA_SPIN_UNLOCK(PATREF_LOCK, &ref->lock);
1653 break;
1654
1655 default:
1656 ret = ACT_RET_ERR;
1657 }
1658
1659
1660 leave:
1661 free_trash_chunk(key);
1662 free_trash_chunk(value);
1663 return ret;
1664
1665 fail_alloc:
1666 if (!(s->flags & SF_ERR_MASK))
1667 s->flags |= SF_ERR_RESOURCE;
1668 ret = ACT_RET_ERR;
1669 goto leave;
1670}
1671
Christopher Faulet2eb53962020-01-14 14:47:34 +01001672/* Release memory allocated by an http map/acl action. */
1673static void release_http_map(struct act_rule *rule)
1674{
1675 struct logformat_node *lf, *lfb;
1676
1677 free(rule->arg.map.ref);
1678 list_for_each_entry_safe(lf, lfb, &rule->arg.map.key, list) {
1679 LIST_DEL(&lf->list);
1680 release_sample_expr(lf->expr);
1681 free(lf->arg);
1682 free(lf);
1683 }
1684 if (rule->action == 1) {
1685 list_for_each_entry_safe(lf, lfb, &rule->arg.map.value, list) {
1686 LIST_DEL(&lf->list);
1687 release_sample_expr(lf->expr);
1688 free(lf->arg);
1689 free(lf);
1690 }
1691 }
1692}
1693
Christopher Faulet81e20172019-12-12 16:40:30 +01001694/* Parse a "add-acl", "del-acl", "set-map" or "del-map" actions. It takes one or
Christopher Faulet046cf442019-12-17 15:45:23 +01001695 * two log-format string as argument depending on the action. The action is
1696 * stored in <.action> as an int (0=add-acl, 1=set-map, 2=del-acl,
1697 * 3=del-map). It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
Christopher Faulet81e20172019-12-12 16:40:30 +01001698 */
1699static enum act_parse_ret parse_http_set_map(const char **args, int *orig_arg, struct proxy *px,
1700 struct act_rule *rule, char **err)
1701{
1702 int cap, cur_arg;
1703
Christopher Faulet046cf442019-12-17 15:45:23 +01001704 if (args[*orig_arg-1][0] == 'a') // add-acl
1705 rule->action = 0;
1706 else if (args[*orig_arg-1][0] == 's') // set-map
1707 rule->action = 1;
1708 else if (args[*orig_arg-1][4] == 'a') // del-acl
1709 rule->action = 2;
1710 else if (args[*orig_arg-1][4] == 'm') // del-map
1711 rule->action = 3;
1712 else {
1713 memprintf(err, "internal error: unhandled action '%s'", args[0]);
1714 return ACT_RET_PRS_ERR;
1715 }
1716 rule->action_ptr = http_action_set_map;
Christopher Faulet2eb53962020-01-14 14:47:34 +01001717 rule->release_ptr = release_http_map;
Christopher Faulet81e20172019-12-12 16:40:30 +01001718
1719 cur_arg = *orig_arg;
Christopher Faulet046cf442019-12-17 15:45:23 +01001720 if (rule->action == 1 && (!*args[cur_arg] || !*args[cur_arg+1])) {
1721 /* 2 args for set-map */
Christopher Faulet81e20172019-12-12 16:40:30 +01001722 memprintf(err, "expects exactly 2 arguments");
1723 return ACT_RET_PRS_ERR;
1724 }
1725 else if (!*args[cur_arg]) {
Christopher Faulet046cf442019-12-17 15:45:23 +01001726 /* only one arg for other actions */
Christopher Faulet81e20172019-12-12 16:40:30 +01001727 memprintf(err, "expects exactly 1 arguments");
1728 return ACT_RET_PRS_ERR;
1729 }
1730
1731 /*
1732 * '+ 8' for 'set-map(' (same for del-map)
1733 * '- 9' for 'set-map(' + trailing ')' (same for del-map)
1734 */
1735 rule->arg.map.ref = my_strndup(args[cur_arg-1] + 8, strlen(args[cur_arg-1]) - 9);
1736
1737 if (rule->from == ACT_F_HTTP_REQ) {
1738 px->conf.args.ctx = ARGC_HRQ;
1739 cap = (px->cap & PR_CAP_FE) ? SMP_VAL_FE_HRQ_HDR : SMP_VAL_BE_HRQ_HDR;
1740 }
1741 else{
1742 px->conf.args.ctx = ARGC_HRS;
1743 cap = (px->cap & PR_CAP_BE) ? SMP_VAL_BE_HRS_HDR : SMP_VAL_FE_HRS_HDR;
1744 }
1745
1746 /* key pattern */
1747 LIST_INIT(&rule->arg.map.key);
Christopher Faulet1337b322020-01-14 14:50:55 +01001748 if (!parse_logformat_string(args[cur_arg], px, &rule->arg.map.key, LOG_OPT_HTTP, cap, err)) {
1749 free(rule->arg.map.ref);
Christopher Faulet81e20172019-12-12 16:40:30 +01001750 return ACT_RET_PRS_ERR;
Christopher Faulet1337b322020-01-14 14:50:55 +01001751 }
Christopher Faulet81e20172019-12-12 16:40:30 +01001752
Christopher Faulet046cf442019-12-17 15:45:23 +01001753 if (rule->action == 1) {
Christopher Faulet81e20172019-12-12 16:40:30 +01001754 /* value pattern for set-map only */
1755 cur_arg++;
1756 LIST_INIT(&rule->arg.map.value);
Christopher Faulet1337b322020-01-14 14:50:55 +01001757 if (!parse_logformat_string(args[cur_arg], px, &rule->arg.map.value, LOG_OPT_HTTP, cap, err)) {
1758 free(rule->arg.map.ref);
Christopher Faulet81e20172019-12-12 16:40:30 +01001759 return ACT_RET_PRS_ERR;
Christopher Faulet1337b322020-01-14 14:50:55 +01001760 }
Christopher Faulet81e20172019-12-12 16:40:30 +01001761 }
1762
1763 free(px->conf.lfs_file);
1764 px->conf.lfs_file = strdup(px->conf.args.file);
1765 px->conf.lfs_line = px->conf.args.line;
1766
1767 *orig_arg = cur_arg + 1;
1768 return ACT_RET_PRS_OK;
1769}
1770
Christopher Fauletac98d812019-12-18 09:20:16 +01001771/* This function executes a track-sc* actions. On success, it returns
1772 * ACT_RET_CONT. Otherwsize ACT_RET_ERR is returned.
1773 */
1774static enum act_return http_action_track_sc(struct act_rule *rule, struct proxy *px,
1775 struct session *sess, struct stream *s, int flags)
1776{
1777 struct stktable *t;
1778 struct stksess *ts;
1779 struct stktable_key *key;
Willy Tarreau826f3ab2021-02-10 12:07:15 +01001780 void *ptr1, *ptr2, *ptr3, *ptr4, *ptr5, *ptr6;
Christopher Fauletac98d812019-12-18 09:20:16 +01001781 int opt;
1782
Willy Tarreau826f3ab2021-02-10 12:07:15 +01001783 ptr1 = ptr2 = ptr3 = ptr4 = ptr5 = ptr6 = NULL;
Christopher Fauletac98d812019-12-18 09:20:16 +01001784 opt = ((rule->from == ACT_F_HTTP_REQ) ? SMP_OPT_DIR_REQ : SMP_OPT_DIR_RES) | SMP_OPT_FINAL;
1785
1786 t = rule->arg.trk_ctr.table.t;
Emeric Brun362d25e2021-03-10 16:58:03 +01001787
1788 if (stkctr_entry(&s->stkctr[rule->action]))
1789 goto end;
1790
Christopher Fauletac98d812019-12-18 09:20:16 +01001791 key = stktable_fetch_key(t, s->be, sess, s, opt, rule->arg.trk_ctr.expr, NULL);
1792
1793 if (!key)
1794 goto end;
1795 ts = stktable_get_entry(t, key);
1796 if (!ts)
1797 goto end;
1798
1799 stream_track_stkctr(&s->stkctr[rule->action], t, ts);
1800
1801 /* let's count a new HTTP request as it's the first time we do it */
1802 ptr1 = stktable_data_ptr(t, ts, STKTABLE_DT_HTTP_REQ_CNT);
1803 ptr2 = stktable_data_ptr(t, ts, STKTABLE_DT_HTTP_REQ_RATE);
1804
1805 /* When the client triggers a 4xx from the server, it's most often due
1806 * to a missing object or permission. These events should be tracked
1807 * because if they happen often, it may indicate a brute force or a
1808 * vulnerability scan. Normally this is done when receiving the response
1809 * but here we're tracking after this ought to have been done so we have
1810 * to do it on purpose.
1811 */
1812 if (rule->from == ACT_F_HTTP_RES && (unsigned)(s->txn->status - 400) < 100) {
1813 ptr3 = stktable_data_ptr(t, ts, STKTABLE_DT_HTTP_ERR_CNT);
1814 ptr4 = stktable_data_ptr(t, ts, STKTABLE_DT_HTTP_ERR_RATE);
1815 }
1816
Willy Tarreau826f3ab2021-02-10 12:07:15 +01001817 if (rule->from == ACT_F_HTTP_RES && (unsigned)(s->txn->status - 500) < 100 &&
1818 s->txn->status != 501 && s->txn->status != 505) {
1819 ptr5 = stktable_data_ptr(t, ts, STKTABLE_DT_HTTP_FAIL_CNT);
1820 ptr6 = stktable_data_ptr(t, ts, STKTABLE_DT_HTTP_FAIL_RATE);
1821 }
1822
1823 if (ptr1 || ptr2 || ptr3 || ptr4 || ptr5 || ptr6) {
Christopher Fauletac98d812019-12-18 09:20:16 +01001824 HA_RWLOCK_WRLOCK(STK_SESS_LOCK, &ts->lock);
1825
1826 if (ptr1)
1827 stktable_data_cast(ptr1, http_req_cnt)++;
1828 if (ptr2)
1829 update_freq_ctr_period(&stktable_data_cast(ptr2, http_req_rate),
1830 t->data_arg[STKTABLE_DT_HTTP_REQ_RATE].u, 1);
1831 if (ptr3)
1832 stktable_data_cast(ptr3, http_err_cnt)++;
1833 if (ptr4)
1834 update_freq_ctr_period(&stktable_data_cast(ptr4, http_err_rate),
1835 t->data_arg[STKTABLE_DT_HTTP_ERR_RATE].u, 1);
Willy Tarreau826f3ab2021-02-10 12:07:15 +01001836 if (ptr5)
1837 stktable_data_cast(ptr5, http_fail_cnt)++;
1838 if (ptr6)
1839 update_freq_ctr_period(&stktable_data_cast(ptr6, http_fail_rate),
1840 t->data_arg[STKTABLE_DT_HTTP_FAIL_RATE].u, 1);
Christopher Fauletac98d812019-12-18 09:20:16 +01001841
1842 HA_RWLOCK_WRUNLOCK(STK_SESS_LOCK, &ts->lock);
1843
1844 /* If data was modified, we need to touch to re-schedule sync */
1845 stktable_touch_local(t, ts, 0);
1846 }
1847
1848 stkctr_set_flags(&s->stkctr[rule->action], STKCTR_TRACK_CONTENT);
1849 if (sess->fe != s->be)
1850 stkctr_set_flags(&s->stkctr[rule->action], STKCTR_TRACK_BACKEND);
1851
1852 end:
1853 return ACT_RET_CONT;
1854}
Christopher Faulet81e20172019-12-12 16:40:30 +01001855
Christopher Faulet2eb53962020-01-14 14:47:34 +01001856static void release_http_track_sc(struct act_rule *rule)
1857{
1858 release_sample_expr(rule->arg.trk_ctr.expr);
1859}
1860
Christopher Faulet81e20172019-12-12 16:40:30 +01001861/* Parse a "track-sc*" actions. It returns ACT_RET_PRS_OK on success,
1862 * ACT_RET_PRS_ERR on error.
1863 */
1864static enum act_parse_ret parse_http_track_sc(const char **args, int *orig_arg, struct proxy *px,
1865 struct act_rule *rule, char **err)
1866{
1867 struct sample_expr *expr;
1868 unsigned int where;
1869 unsigned int tsc_num;
1870 const char *tsc_num_str;
1871 int cur_arg;
1872
1873 tsc_num_str = &args[*orig_arg-1][8];
1874 if (cfg_parse_track_sc_num(&tsc_num, tsc_num_str, tsc_num_str + strlen(tsc_num_str), err) == -1)
1875 return ACT_RET_PRS_ERR;
1876
1877 cur_arg = *orig_arg;
1878 expr = sample_parse_expr((char **)args, &cur_arg, px->conf.args.file, px->conf.args.line,
Willy Tarreaue3b57bf2020-02-14 16:50:14 +01001879 err, &px->conf.args, NULL);
Christopher Faulet81e20172019-12-12 16:40:30 +01001880 if (!expr)
1881 return ACT_RET_PRS_ERR;
1882
1883 where = 0;
1884 if (px->cap & PR_CAP_FE)
1885 where |= (rule->from == ACT_F_HTTP_REQ ? SMP_VAL_FE_HRQ_HDR : SMP_VAL_FE_HRS_HDR);
1886 if (px->cap & PR_CAP_BE)
1887 where |= (rule->from == ACT_F_HTTP_REQ ? SMP_VAL_BE_HRQ_HDR : SMP_VAL_BE_HRS_HDR);
1888
1889 if (!(expr->fetch->val & where)) {
1890 memprintf(err, "fetch method '%s' extracts information from '%s', none of which is available here",
1891 args[cur_arg-1], sample_src_names(expr->fetch->use));
Christopher Faulet1337b322020-01-14 14:50:55 +01001892 release_sample_expr(expr);
Christopher Faulet81e20172019-12-12 16:40:30 +01001893 return ACT_RET_PRS_ERR;
1894 }
1895
1896 if (strcmp(args[cur_arg], "table") == 0) {
1897 cur_arg++;
1898 if (!*args[cur_arg]) {
1899 memprintf(err, "missing table name");
Christopher Faulet1337b322020-01-14 14:50:55 +01001900 release_sample_expr(expr);
Christopher Faulet81e20172019-12-12 16:40:30 +01001901 return ACT_RET_PRS_ERR;
1902 }
1903
1904 /* we copy the table name for now, it will be resolved later */
1905 rule->arg.trk_ctr.table.n = strdup(args[cur_arg]);
1906 cur_arg++;
1907 }
1908
Christopher Fauletac98d812019-12-18 09:20:16 +01001909 rule->action = tsc_num;
Christopher Faulet81e20172019-12-12 16:40:30 +01001910 rule->arg.trk_ctr.expr = expr;
Christopher Fauletac98d812019-12-18 09:20:16 +01001911 rule->action_ptr = http_action_track_sc;
Christopher Faulet2eb53962020-01-14 14:47:34 +01001912 rule->release_ptr = release_http_track_sc;
Christopher Faulet81e20172019-12-12 16:40:30 +01001913 rule->check_ptr = check_trk_action;
1914
1915 *orig_arg = cur_arg;
1916 return ACT_RET_PRS_OK;
1917}
1918
Amaury Denoyelle8d228232020-12-10 13:43:54 +01001919static enum act_return action_timeout_set_stream_timeout(struct act_rule *rule,
1920 struct proxy *px,
1921 struct session *sess,
1922 struct stream *s,
1923 int flags)
1924{
1925 struct sample *key;
1926
1927 if (rule->arg.timeout.expr) {
1928 key = sample_fetch_as_type(px, sess, s, SMP_OPT_FINAL, rule->arg.timeout.expr, SMP_T_SINT);
1929 if (!key)
1930 return ACT_RET_CONT;
1931
1932 stream_set_timeout(s, rule->arg.timeout.type, MS_TO_TICKS(key->data.u.sint));
1933 }
1934 else {
1935 stream_set_timeout(s, rule->arg.timeout.type, MS_TO_TICKS(rule->arg.timeout.value));
1936 }
1937
1938 return ACT_RET_CONT;
1939}
1940
1941/* Parse a "set-timeout" action. Returns ACT_RET_PRS_ERR if parsing error.
1942 */
1943static enum act_parse_ret parse_http_set_timeout(const char **args,
1944 int *orig_arg,
1945 struct proxy *px,
1946 struct act_rule *rule, char **err)
1947{
1948 int cur_arg;
1949
1950 rule->action = ACT_CUSTOM;
1951 rule->action_ptr = action_timeout_set_stream_timeout;
1952 rule->release_ptr = release_timeout_action;
1953
1954 cur_arg = *orig_arg;
1955 if (!*args[cur_arg] || !*args[cur_arg + 1]) {
1956 memprintf(err, "expects exactly 2 arguments");
1957 return ACT_RET_PRS_ERR;
1958 }
1959
1960 if (!(px->cap & PR_CAP_BE)) {
1961 memprintf(err, "proxy '%s' has no backend capability", px->id);
1962 return ACT_RET_PRS_ERR;
1963 }
1964
1965 if (cfg_parse_rule_set_timeout(args, cur_arg,
1966 &rule->arg.timeout.value,
1967 &rule->arg.timeout.type,
1968 &rule->arg.timeout.expr,
1969 err,
1970 px->conf.args.file,
1971 px->conf.args.line, &px->conf.args) == -1) {
1972 return ACT_RET_PRS_ERR;
1973 }
1974
1975 *orig_arg = cur_arg + 2;
1976
1977 return ACT_RET_PRS_OK;
1978}
1979
Christopher Faulet46f95542019-12-20 10:07:22 +01001980/* This function executes a strict-mode actions. On success, it always returns
1981 * ACT_RET_CONT
1982 */
1983static enum act_return http_action_strict_mode(struct act_rule *rule, struct proxy *px,
1984 struct session *sess, struct stream *s, int flags)
1985{
1986 struct http_msg *msg = ((rule->from == ACT_F_HTTP_REQ) ? &s->txn->req : &s->txn->rsp);
1987
1988 if (rule->action == 0) // strict-mode on
1989 msg->flags &= ~HTTP_MSGF_SOFT_RW;
1990 else // strict-mode off
1991 msg->flags |= HTTP_MSGF_SOFT_RW;
1992 return ACT_RET_CONT;
1993}
1994
1995/* Parse a "strict-mode" action. It returns ACT_RET_PRS_OK on success,
1996 * ACT_RET_PRS_ERR on error.
1997 */
1998static enum act_parse_ret parse_http_strict_mode(const char **args, int *orig_arg, struct proxy *px,
1999 struct act_rule *rule, char **err)
2000{
2001 int cur_arg;
2002
Christopher Faulet46f95542019-12-20 10:07:22 +01002003 cur_arg = *orig_arg;
2004 if (!*args[cur_arg]) {
2005 memprintf(err, "expects exactly 1 arguments");
2006 return ACT_RET_PRS_ERR;
2007 }
2008
2009 if (strcasecmp(args[cur_arg], "on") == 0)
2010 rule->action = 0; // strict-mode on
2011 else if (strcasecmp(args[cur_arg], "off") == 0)
2012 rule->action = 1; // strict-mode off
2013 else {
2014 memprintf(err, "Unexpected value '%s'. Only 'on' and 'off' are supported", args[cur_arg]);
2015 return ACT_RET_PRS_ERR;
2016 }
2017 rule->action_ptr = http_action_strict_mode;
2018
2019 *orig_arg = cur_arg + 1;
2020 return ACT_RET_PRS_OK;
2021}
2022
Christopher Faulet24231ab2020-01-24 17:44:23 +01002023/* This function executes a return action. It builds an HTX message from an
2024 * errorfile, an raw file or a log-format string, depending on <.action>
2025 * value. On success, it returns ACT_RET_ABRT. If an error occurs ACT_RET_ERR is
2026 * returned.
2027 */
2028static enum act_return http_action_return(struct act_rule *rule, struct proxy *px,
2029 struct session *sess, struct stream *s, int flags)
2030{
2031 struct channel *req = &s->req;
Christopher Faulet24231ab2020-01-24 17:44:23 +01002032
Christopher Faulet2d36df22021-02-19 11:41:01 +01002033 s->txn->status = rule->arg.http_reply->status;
Christopher Faulet0e2ad612020-05-13 16:38:37 +02002034 if (http_reply_message(s, rule->arg.http_reply) == -1)
2035 return ACT_RET_ERR;
Christopher Faulet24231ab2020-01-24 17:44:23 +01002036
Christopher Faulet24231ab2020-01-24 17:44:23 +01002037 if (rule->from == ACT_F_HTTP_REQ) {
2038 /* let's log the request time */
2039 s->logs.tv_request = now;
2040 req->analysers &= AN_REQ_FLT_END;
2041
2042 if (s->sess->fe == s->be) /* report it if the request was intercepted by the frontend */
2043 _HA_ATOMIC_ADD(&s->sess->fe->fe_counters.intercepted_req, 1);
2044 }
2045
2046 if (!(s->flags & SF_ERR_MASK))
2047 s->flags |= SF_ERR_LOCAL;
2048 if (!(s->flags & SF_FINST_MASK))
2049 s->flags |= ((rule->from == ACT_F_HTTP_REQ) ? SF_FINST_R : SF_FINST_H);
2050
Christopher Faulet0e2ad612020-05-13 16:38:37 +02002051 return ACT_RET_ABRT;
Christopher Faulet24231ab2020-01-24 17:44:23 +01002052}
2053
Christopher Faulet24231ab2020-01-24 17:44:23 +01002054/* Parse a "return" action. It returns ACT_RET_PRS_OK on success,
Christopher Faulet47e791e2020-05-13 14:36:55 +02002055 * ACT_RET_PRS_ERR on error. It relies on http_parse_http_reply() to set
2056 * <.arg.http_reply>.
Christopher Faulet24231ab2020-01-24 17:44:23 +01002057 */
2058static enum act_parse_ret parse_http_return(const char **args, int *orig_arg, struct proxy *px,
2059 struct act_rule *rule, char **err)
2060{
Christopher Faulet47e791e2020-05-13 14:36:55 +02002061 /* Prepare parsing of log-format strings */
2062 px->conf.args.ctx = ((rule->from == ACT_F_HTTP_REQ) ? ARGC_HRQ : ARGC_HRS);
2063 rule->arg.http_reply = http_parse_http_reply(args, orig_arg, px, 200, err);
2064 if (!rule->arg.http_reply)
2065 return ACT_RET_PRS_ERR;
Christopher Faulet24231ab2020-01-24 17:44:23 +01002066
Christopher Fauletba946bf2020-05-13 08:50:07 +02002067 rule->flags |= ACT_FLAG_FINAL;
Christopher Faulet5ff0c642020-05-12 18:33:37 +02002068 rule->action = ACT_CUSTOM;
Christopher Faulet5cb513a2020-05-13 17:56:56 +02002069 rule->check_ptr = check_act_http_reply;
Christopher Faulet24231ab2020-01-24 17:44:23 +01002070 rule->action_ptr = http_action_return;
Christopher Faulet5cb513a2020-05-13 17:56:56 +02002071 rule->release_ptr = release_act_http_reply;
Christopher Faulet24231ab2020-01-24 17:44:23 +01002072 return ACT_RET_PRS_OK;
Christopher Faulet24231ab2020-01-24 17:44:23 +01002073}
2074
Willy Tarreau79e57332018-10-02 16:01:16 +02002075/************************************************************************/
2076/* All supported http-request action keywords must be declared here. */
2077/************************************************************************/
2078
2079static struct action_kw_list http_req_actions = {
2080 .kw = {
Christopher Faulet81e20172019-12-12 16:40:30 +01002081 { "add-acl", parse_http_set_map, 1 },
2082 { "add-header", parse_http_set_header, 0 },
2083 { "allow", parse_http_allow, 0 },
2084 { "auth", parse_http_auth, 0 },
2085 { "capture", parse_http_req_capture, 0 },
2086 { "del-acl", parse_http_set_map, 1 },
2087 { "del-header", parse_http_del_header, 0 },
2088 { "del-map", parse_http_set_map, 1 },
Christopher Faulete0fca292020-01-13 21:49:03 +01002089 { "deny", parse_http_deny, 0 },
Christopher Faulet81e20172019-12-12 16:40:30 +01002090 { "disable-l7-retry", parse_http_req_disable_l7_retry, 0 },
2091 { "early-hint", parse_http_set_header, 0 },
2092 { "redirect", parse_http_redirect, 0 },
2093 { "reject", parse_http_action_reject, 0 },
2094 { "replace-header", parse_http_replace_header, 0 },
2095 { "replace-path", parse_replace_uri, 0 },
Christopher Faulet312294f2020-09-02 17:17:44 +02002096 { "replace-pathq", parse_replace_uri, 0 },
Christopher Faulet81e20172019-12-12 16:40:30 +01002097 { "replace-uri", parse_replace_uri, 0 },
2098 { "replace-value", parse_http_replace_header, 0 },
Christopher Faulet24231ab2020-01-24 17:44:23 +01002099 { "return", parse_http_return, 0 },
Christopher Faulet81e20172019-12-12 16:40:30 +01002100 { "set-header", parse_http_set_header, 0 },
2101 { "set-log-level", parse_http_set_log_level, 0 },
2102 { "set-map", parse_http_set_map, 1 },
2103 { "set-method", parse_set_req_line, 0 },
2104 { "set-mark", parse_http_set_mark, 0 },
2105 { "set-nice", parse_http_set_nice, 0 },
2106 { "set-path", parse_set_req_line, 0 },
Christopher Faulet312294f2020-09-02 17:17:44 +02002107 { "set-pathq", parse_set_req_line, 0 },
Christopher Faulet81e20172019-12-12 16:40:30 +01002108 { "set-query", parse_set_req_line, 0 },
2109 { "set-tos", parse_http_set_tos, 0 },
2110 { "set-uri", parse_set_req_line, 0 },
Christopher Faulet46f95542019-12-20 10:07:22 +01002111 { "strict-mode", parse_http_strict_mode, 0 },
Christopher Faulete0fca292020-01-13 21:49:03 +01002112 { "tarpit", parse_http_deny, 0 },
Christopher Faulet81e20172019-12-12 16:40:30 +01002113 { "track-sc", parse_http_track_sc, 1 },
Amaury Denoyelle8d228232020-12-10 13:43:54 +01002114 { "set-timeout", parse_http_set_timeout, 0 },
Willy Tarreau79e57332018-10-02 16:01:16 +02002115 { NULL, NULL }
2116 }
2117};
2118
Willy Tarreau0108d902018-11-25 19:14:37 +01002119INITCALL1(STG_REGISTER, http_req_keywords_register, &http_req_actions);
2120
Willy Tarreau79e57332018-10-02 16:01:16 +02002121static struct action_kw_list http_res_actions = {
2122 .kw = {
Christopher Faulet81e20172019-12-12 16:40:30 +01002123 { "add-acl", parse_http_set_map, 1 },
2124 { "add-header", parse_http_set_header, 0 },
2125 { "allow", parse_http_allow, 0 },
2126 { "capture", parse_http_res_capture, 0 },
2127 { "del-acl", parse_http_set_map, 1 },
2128 { "del-header", parse_http_del_header, 0 },
2129 { "del-map", parse_http_set_map, 1 },
Christopher Faulete0fca292020-01-13 21:49:03 +01002130 { "deny", parse_http_deny, 0 },
Christopher Faulet81e20172019-12-12 16:40:30 +01002131 { "redirect", parse_http_redirect, 0 },
2132 { "replace-header", parse_http_replace_header, 0 },
2133 { "replace-value", parse_http_replace_header, 0 },
Christopher Faulet24231ab2020-01-24 17:44:23 +01002134 { "return", parse_http_return, 0 },
Christopher Faulet81e20172019-12-12 16:40:30 +01002135 { "set-header", parse_http_set_header, 0 },
2136 { "set-log-level", parse_http_set_log_level, 0 },
2137 { "set-map", parse_http_set_map, 1 },
2138 { "set-mark", parse_http_set_mark, 0 },
2139 { "set-nice", parse_http_set_nice, 0 },
2140 { "set-status", parse_http_set_status, 0 },
2141 { "set-tos", parse_http_set_tos, 0 },
Christopher Faulet46f95542019-12-20 10:07:22 +01002142 { "strict-mode", parse_http_strict_mode, 0 },
Christopher Faulet81e20172019-12-12 16:40:30 +01002143 { "track-sc", parse_http_track_sc, 1 },
Willy Tarreau79e57332018-10-02 16:01:16 +02002144 { NULL, NULL }
2145 }
2146};
2147
Willy Tarreau0108d902018-11-25 19:14:37 +01002148INITCALL1(STG_REGISTER, http_res_keywords_register, &http_res_actions);
Willy Tarreau79e57332018-10-02 16:01:16 +02002149
Christopher Faulet6d0c3df2020-01-22 09:26:35 +01002150static struct action_kw_list http_after_res_actions = {
2151 .kw = {
2152 { "add-header", parse_http_set_header, 0 },
2153 { "allow", parse_http_allow, 0 },
2154 { "del-header", parse_http_del_header, 0 },
2155 { "replace-header", parse_http_replace_header, 0 },
2156 { "replace-value", parse_http_replace_header, 0 },
2157 { "set-header", parse_http_set_header, 0 },
2158 { "set-status", parse_http_set_status, 0 },
2159 { "strict-mode", parse_http_strict_mode, 0 },
2160 { NULL, NULL }
2161 }
2162};
2163
2164INITCALL1(STG_REGISTER, http_after_res_keywords_register, &http_after_res_actions);
2165
Willy Tarreau79e57332018-10-02 16:01:16 +02002166/*
2167 * Local variables:
2168 * c-indent-level: 8
2169 * c-basic-offset: 8
2170 * End:
2171 */