blob: 5da2d7d1ab974b87a27d701b3b3ebb58c1749ef5 [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>
Tim Duesterhusd2bedcc2021-04-15 21:45:57 +020039#include <haproxy/uri_normalizer.h>
Willy Tarreaud6788052020-05-27 15:59:00 +020040#include <haproxy/version.h>
Willy Tarreau79e57332018-10-02 16:01:16 +020041
Willy Tarreau79e57332018-10-02 16:01:16 +020042
Christopher Faulet2eb53962020-01-14 14:47:34 +010043/* Release memory allocated by most of HTTP actions. Concretly, it releases
44 * <arg.http>.
45 */
46static void release_http_action(struct act_rule *rule)
47{
48 struct logformat_node *lf, *lfb;
49
Tim Duesterhused526372020-03-05 17:56:33 +010050 istfree(&rule->arg.http.str);
Christopher Faulet2eb53962020-01-14 14:47:34 +010051 if (rule->arg.http.re)
52 regex_free(rule->arg.http.re);
53 list_for_each_entry_safe(lf, lfb, &rule->arg.http.fmt, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +020054 LIST_DELETE(&lf->list);
Christopher Faulet2eb53962020-01-14 14:47:34 +010055 release_sample_expr(lf->expr);
56 free(lf->arg);
57 free(lf);
58 }
59}
60
Christopher Faulet5cb513a2020-05-13 17:56:56 +020061/* Release memory allocated by HTTP actions relying on an http reply. Concretly,
62 * it releases <.arg.http_reply>
63 */
64static void release_act_http_reply(struct act_rule *rule)
65{
66 release_http_reply(rule->arg.http_reply);
67 rule->arg.http_reply = NULL;
68}
69
70
71/* Check function for HTTP actions relying on an http reply. The function
72 * returns 1 in success case, otherwise, it returns 0 and err is filled.
73 */
74static int check_act_http_reply(struct act_rule *rule, struct proxy *px, char **err)
75{
76 struct http_reply *reply = rule->arg.http_reply;
77
78 if (!http_check_http_reply(reply, px, err)) {
79 release_act_http_reply(rule);
80 return 0;
81 }
82 return 1;
83}
84
Willy Tarreau79e57332018-10-02 16:01:16 +020085
86/* This function executes one of the set-{method,path,query,uri} actions. It
87 * builds a string in the trash from the specified format string. It finds
Christopher Faulet2c22a692019-12-18 15:39:56 +010088 * the action to be performed in <.action>, previously filled by function
Ilya Shipitsinc02a23f2020-05-06 00:53:22 +050089 * parse_set_req_line(). The replacement action is executed by the function
Christopher Faulete00d06c2019-12-16 17:18:42 +010090 * http_action_set_req_line(). On success, it returns ACT_RET_CONT. If an error
91 * occurs while soft rewrites are enabled, the action is canceled, but the rule
92 * processing continue. Otherwsize ACT_RET_ERR is returned.
Willy Tarreau79e57332018-10-02 16:01:16 +020093 */
94static enum act_return http_action_set_req_line(struct act_rule *rule, struct proxy *px,
95 struct session *sess, struct stream *s, int flags)
96{
97 struct buffer *replace;
Christopher Faulet13403762019-12-13 09:01:57 +010098 enum act_return ret = ACT_RET_CONT;
Willy Tarreau79e57332018-10-02 16:01:16 +020099
100 replace = alloc_trash_chunk();
101 if (!replace)
Christopher Faulete00d06c2019-12-16 17:18:42 +0100102 goto fail_alloc;
Willy Tarreau79e57332018-10-02 16:01:16 +0200103
104 /* If we have to create a query string, prepare a '?'. */
Christopher Faulet2c22a692019-12-18 15:39:56 +0100105 if (rule->action == 2) // set-query
Willy Tarreau79e57332018-10-02 16:01:16 +0200106 replace->area[replace->data++] = '?';
107 replace->data += build_logline(s, replace->area + replace->data,
108 replace->size - replace->data,
Christopher Faulet96bff762019-12-17 13:46:18 +0100109 &rule->arg.http.fmt);
Willy Tarreau79e57332018-10-02 16:01:16 +0200110
Christopher Faulet2c22a692019-12-18 15:39:56 +0100111 if (http_req_replace_stline(rule->action, replace->area, replace->data, px, s) == -1)
Christopher Faulete00d06c2019-12-16 17:18:42 +0100112 goto fail_rewrite;
Willy Tarreau79e57332018-10-02 16:01:16 +0200113
Christopher Faulete00d06c2019-12-16 17:18:42 +0100114 leave:
Willy Tarreau79e57332018-10-02 16:01:16 +0200115 free_trash_chunk(replace);
116 return ret;
Christopher Faulete00d06c2019-12-16 17:18:42 +0100117
118 fail_alloc:
119 if (!(s->flags & SF_ERR_MASK))
120 s->flags |= SF_ERR_RESOURCE;
121 ret = ACT_RET_ERR;
122 goto leave;
123
124 fail_rewrite:
Willy Tarreau4781b152021-04-06 13:53:36 +0200125 _HA_ATOMIC_INC(&sess->fe->fe_counters.failed_rewrites);
Christopher Faulete00d06c2019-12-16 17:18:42 +0100126 if (s->flags & SF_BE_ASSIGNED)
Willy Tarreau4781b152021-04-06 13:53:36 +0200127 _HA_ATOMIC_INC(&s->be->be_counters.failed_rewrites);
William Lallemand36119de2021-03-08 15:26:48 +0100128 if (sess->listener && sess->listener->counters)
Willy Tarreau4781b152021-04-06 13:53:36 +0200129 _HA_ATOMIC_INC(&sess->listener->counters->failed_rewrites);
Christopher Faulete00d06c2019-12-16 17:18:42 +0100130 if (objt_server(s->target))
Willy Tarreau4781b152021-04-06 13:53:36 +0200131 _HA_ATOMIC_INC(&__objt_server(s->target)->counters.failed_rewrites);
Christopher Faulete00d06c2019-12-16 17:18:42 +0100132
Christopher Faulet333bf8c2020-01-22 14:38:05 +0100133 if (!(s->txn->req.flags & HTTP_MSGF_SOFT_RW)) {
Christopher Faulete00d06c2019-12-16 17:18:42 +0100134 ret = ACT_RET_ERR;
Christopher Faulet333bf8c2020-01-22 14:38:05 +0100135 if (!(s->flags & SF_ERR_MASK))
136 s->flags |= SF_ERR_PRXCOND;
137 }
Christopher Faulete00d06c2019-12-16 17:18:42 +0100138 goto leave;
Willy Tarreau79e57332018-10-02 16:01:16 +0200139}
140
141/* parse an http-request action among :
142 * set-method
143 * set-path
Christopher Faulet312294f2020-09-02 17:17:44 +0200144 * set-pathq
Willy Tarreau79e57332018-10-02 16:01:16 +0200145 * set-query
146 * set-uri
147 *
148 * All of them accept a single argument of type string representing a log-format.
Christopher Faulet96bff762019-12-17 13:46:18 +0100149 * The resulting rule makes use of <http.fmt> to store the log-format list head,
Christopher Faulet2c22a692019-12-18 15:39:56 +0100150 * and <.action> to store the action type as an int (0=method, 1=path, 2=query,
151 * 3=uri). It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
Willy Tarreau79e57332018-10-02 16:01:16 +0200152 */
153static enum act_parse_ret parse_set_req_line(const char **args, int *orig_arg, struct proxy *px,
154 struct act_rule *rule, char **err)
155{
156 int cur_arg = *orig_arg;
157
Willy Tarreau79e57332018-10-02 16:01:16 +0200158 switch (args[0][4]) {
159 case 'm' :
Christopher Faulet2c22a692019-12-18 15:39:56 +0100160 rule->action = 0; // set-method
Willy Tarreau79e57332018-10-02 16:01:16 +0200161 break;
162 case 'p' :
Christopher Faulet312294f2020-09-02 17:17:44 +0200163 if (args[0][8] == 'q')
164 rule->action = 4; // set-pathq
165 else
166 rule->action = 1; // set-path
Willy Tarreau79e57332018-10-02 16:01:16 +0200167 break;
168 case 'q' :
Christopher Faulet2c22a692019-12-18 15:39:56 +0100169 rule->action = 2; // set-query
Willy Tarreau79e57332018-10-02 16:01:16 +0200170 break;
171 case 'u' :
Christopher Faulet2c22a692019-12-18 15:39:56 +0100172 rule->action = 3; // set-uri
Willy Tarreau79e57332018-10-02 16:01:16 +0200173 break;
174 default:
175 memprintf(err, "internal error: unhandled action '%s'", args[0]);
176 return ACT_RET_PRS_ERR;
177 }
Christopher Faulet96bff762019-12-17 13:46:18 +0100178 rule->action_ptr = http_action_set_req_line;
Christopher Faulet2eb53962020-01-14 14:47:34 +0100179 rule->release_ptr = release_http_action;
Willy Tarreau79e57332018-10-02 16:01:16 +0200180
181 if (!*args[cur_arg] ||
182 (*args[cur_arg + 1] && strcmp(args[cur_arg + 1], "if") != 0 && strcmp(args[cur_arg + 1], "unless") != 0)) {
183 memprintf(err, "expects exactly 1 argument <format>");
184 return ACT_RET_PRS_ERR;
185 }
186
Christopher Faulet96bff762019-12-17 13:46:18 +0100187 LIST_INIT(&rule->arg.http.fmt);
Willy Tarreau79e57332018-10-02 16:01:16 +0200188 px->conf.args.ctx = ARGC_HRQ;
Christopher Faulet96bff762019-12-17 13:46:18 +0100189 if (!parse_logformat_string(args[cur_arg], px, &rule->arg.http.fmt, LOG_OPT_HTTP,
Willy Tarreau79e57332018-10-02 16:01:16 +0200190 (px->cap & PR_CAP_FE) ? SMP_VAL_FE_HRQ_HDR : SMP_VAL_BE_HRQ_HDR, err)) {
191 return ACT_RET_PRS_ERR;
192 }
193
194 (*orig_arg)++;
195 return ACT_RET_PRS_OK;
196}
197
Tim Duesterhusd2bedcc2021-04-15 21:45:57 +0200198/* This function executes the http-request normalize-uri action.
199 * `rule->action` is expected to be a value from `enum act_normalize_uri`.
200 *
201 * On success, it returns ACT_RET_CONT. If an error
202 * occurs while soft rewrites are enabled, the action is canceled, but the rule
203 * processing continue. Otherwsize ACT_RET_ERR is returned.
204 */
205static enum act_return http_action_normalize_uri(struct act_rule *rule, struct proxy *px,
206 struct session *sess, struct stream *s, int flags)
207{
208 enum act_return ret = ACT_RET_CONT;
209 struct htx *htx = htxbuf(&s->req.buf);
210 const struct ist uri = htx_sl_req_uri(http_get_stline(htx));
211 struct buffer *replace = alloc_trash_chunk();
212 enum uri_normalizer_err err = URI_NORMALIZER_ERR_INTERNAL_ERROR;
213
214 if (!replace)
215 goto fail_alloc;
216
217 switch ((enum act_normalize_uri) rule->action) {
Tim Duesterhus5be6ab22021-04-17 11:21:10 +0200218 case ACT_NORMALIZE_URI_PATH_MERGE_SLASHES: {
Tim Duesterhusd371e992021-04-15 21:45:58 +0200219 const struct ist path = http_get_path(uri);
220 struct ist newpath = ist2(replace->area, replace->size);
221
222 if (!isttest(path))
223 goto leave;
224
225 err = uri_normalizer_path_merge_slashes(iststop(path, '?'), &newpath);
226
227 if (err != URI_NORMALIZER_ERR_NONE)
228 break;
229
230 if (!http_replace_req_path(htx, newpath, 0))
231 goto fail_rewrite;
232
233 break;
234 }
Tim Duesterhus5be6ab22021-04-17 11:21:10 +0200235 case ACT_NORMALIZE_URI_PATH_STRIP_DOTDOT:
236 case ACT_NORMALIZE_URI_PATH_STRIP_DOTDOT_FULL: {
Tim Duesterhus9982fc22021-04-15 21:45:59 +0200237 const struct ist path = http_get_path(uri);
238 struct ist newpath = ist2(replace->area, replace->size);
239
240 if (!isttest(path))
241 goto leave;
242
Tim Duesterhus5be6ab22021-04-17 11:21:10 +0200243 err = uri_normalizer_path_dotdot(iststop(path, '?'), rule->action == ACT_NORMALIZE_URI_PATH_STRIP_DOTDOT_FULL, &newpath);
Tim Duesterhus9982fc22021-04-15 21:45:59 +0200244
245 if (err != URI_NORMALIZER_ERR_NONE)
246 break;
247
248 if (!http_replace_req_path(htx, newpath, 0))
249 goto fail_rewrite;
250
251 break;
252 }
Tim Duesterhus5be6ab22021-04-17 11:21:10 +0200253 case ACT_NORMALIZE_URI_QUERY_SORT_BY_NAME: {
Tim Duesterhusd7b89be2021-04-15 21:46:01 +0200254 const struct ist path = http_get_path(uri);
255 struct ist newquery = ist2(replace->area, replace->size);
256
257 if (!isttest(path))
258 goto leave;
259
260 err = uri_normalizer_query_sort(istfind(path, '?'), '&', &newquery);
261
262 if (err != URI_NORMALIZER_ERR_NONE)
263 break;
264
265 if (!http_replace_req_query(htx, newquery))
266 goto fail_rewrite;
267
268 break;
269 }
Tim Duesterhus5be6ab22021-04-17 11:21:10 +0200270 case ACT_NORMALIZE_URI_PERCENT_TO_UPPERCASE:
271 case ACT_NORMALIZE_URI_PERCENT_TO_UPPERCASE_STRICT: {
Tim Duesterhusa4071932021-04-15 21:46:02 +0200272 const struct ist path = http_get_path(uri);
273 struct ist newpath = ist2(replace->area, replace->size);
274
275 if (!isttest(path))
276 goto leave;
277
Tim Duesterhus5be6ab22021-04-17 11:21:10 +0200278 err = uri_normalizer_percent_upper(path, rule->action == ACT_NORMALIZE_URI_PERCENT_TO_UPPERCASE_STRICT, &newpath);
Tim Duesterhusa4071932021-04-15 21:46:02 +0200279
280 if (err != URI_NORMALIZER_ERR_NONE)
281 break;
282
283 if (!http_replace_req_path(htx, newpath, 1))
284 goto fail_rewrite;
285
286 break;
287 }
Tim Duesterhusd2bedcc2021-04-15 21:45:57 +0200288 }
289
290 switch (err) {
291 case URI_NORMALIZER_ERR_NONE:
292 break;
293 case URI_NORMALIZER_ERR_INTERNAL_ERROR:
294 ret = ACT_RET_ERR;
295 break;
296 case URI_NORMALIZER_ERR_INVALID_INPUT:
297 ret = ACT_RET_INV;
298 break;
299 case URI_NORMALIZER_ERR_ALLOC:
300 goto fail_alloc;
301 }
302
303 leave:
304 free_trash_chunk(replace);
305 return ret;
306
307 fail_alloc:
308 if (!(s->flags & SF_ERR_MASK))
309 s->flags |= SF_ERR_RESOURCE;
310 ret = ACT_RET_ERR;
311 goto leave;
312
313 fail_rewrite:
314 _HA_ATOMIC_ADD(&sess->fe->fe_counters.failed_rewrites, 1);
315 if (s->flags & SF_BE_ASSIGNED)
316 _HA_ATOMIC_ADD(&s->be->be_counters.failed_rewrites, 1);
317 if (sess->listener && sess->listener->counters)
318 _HA_ATOMIC_ADD(&sess->listener->counters->failed_rewrites, 1);
319 if (objt_server(s->target))
320 _HA_ATOMIC_ADD(&__objt_server(s->target)->counters.failed_rewrites, 1);
321
322 if (!(s->txn->req.flags & HTTP_MSGF_SOFT_RW)) {
323 ret = ACT_RET_ERR;
324 if (!(s->flags & SF_ERR_MASK))
325 s->flags |= SF_ERR_PRXCOND;
326 }
327 goto leave;
328}
329
330/* Parses the http-request normalize-uri action. It expects a single <normalizer>
331 * argument, corresponding too a value in `enum act_normalize_uri`.
332 *
333 * It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
334 */
335static enum act_parse_ret parse_http_normalize_uri(const char **args, int *orig_arg, struct proxy *px,
336 struct act_rule *rule, char **err)
337{
338 int cur_arg = *orig_arg;
339
340 rule->action_ptr = http_action_normalize_uri;
341 rule->release_ptr = NULL;
342
343 if (!*args[cur_arg]) {
344 memprintf(err, "missing argument <normalizer>");
345 return ACT_RET_PRS_ERR;
346 }
347
Tim Duesterhus5be6ab22021-04-17 11:21:10 +0200348 if (strcmp(args[cur_arg], "path-merge-slashes") == 0) {
Tim Duesterhusd371e992021-04-15 21:45:58 +0200349 cur_arg++;
Tim Duesterhusd2bedcc2021-04-15 21:45:57 +0200350
Tim Duesterhus5be6ab22021-04-17 11:21:10 +0200351 rule->action = ACT_NORMALIZE_URI_PATH_MERGE_SLASHES;
Tim Duesterhusd2bedcc2021-04-15 21:45:57 +0200352 }
Tim Duesterhus5be6ab22021-04-17 11:21:10 +0200353 else if (strcmp(args[cur_arg], "path-strip-dotdot") == 0) {
Tim Duesterhus9982fc22021-04-15 21:45:59 +0200354 cur_arg++;
355
Tim Duesterhus560e1a62021-04-15 21:46:00 +0200356 if (strcmp(args[cur_arg], "full") == 0) {
357 cur_arg++;
Tim Duesterhus5be6ab22021-04-17 11:21:10 +0200358 rule->action = ACT_NORMALIZE_URI_PATH_STRIP_DOTDOT_FULL;
Tim Duesterhus560e1a62021-04-15 21:46:00 +0200359 }
360 else if (!*args[cur_arg]) {
Tim Duesterhus5be6ab22021-04-17 11:21:10 +0200361 rule->action = ACT_NORMALIZE_URI_PATH_STRIP_DOTDOT;
Tim Duesterhus560e1a62021-04-15 21:46:00 +0200362 }
363 else if (strcmp(args[cur_arg], "if") != 0 && strcmp(args[cur_arg], "unless") != 0) {
364 memprintf(err, "unknown argument '%s' for 'dotdot' normalizer", args[cur_arg]);
365 return ACT_RET_PRS_ERR;
366 }
Tim Duesterhus9982fc22021-04-15 21:45:59 +0200367 }
Tim Duesterhus5be6ab22021-04-17 11:21:10 +0200368 else if (strcmp(args[cur_arg], "query-sort-by-name") == 0) {
Tim Duesterhusd7b89be2021-04-15 21:46:01 +0200369 cur_arg++;
370
Tim Duesterhus5be6ab22021-04-17 11:21:10 +0200371 rule->action = ACT_NORMALIZE_URI_QUERY_SORT_BY_NAME;
Tim Duesterhusd7b89be2021-04-15 21:46:01 +0200372 }
Tim Duesterhus5be6ab22021-04-17 11:21:10 +0200373 else if (strcmp(args[cur_arg], "percent-to-uppercase") == 0) {
Tim Duesterhusa4071932021-04-15 21:46:02 +0200374 cur_arg++;
375
376 if (strcmp(args[cur_arg], "strict") == 0) {
377 cur_arg++;
Tim Duesterhus5be6ab22021-04-17 11:21:10 +0200378 rule->action = ACT_NORMALIZE_URI_PERCENT_TO_UPPERCASE_STRICT;
Tim Duesterhusa4071932021-04-15 21:46:02 +0200379 }
380 else if (!*args[cur_arg]) {
Tim Duesterhus5be6ab22021-04-17 11:21:10 +0200381 rule->action = ACT_NORMALIZE_URI_PERCENT_TO_UPPERCASE;
Tim Duesterhusa4071932021-04-15 21:46:02 +0200382 }
383 else if (strcmp(args[cur_arg], "if") != 0 && strcmp(args[cur_arg], "unless") != 0) {
384 memprintf(err, "unknown argument '%s' for 'percent-upper' normalizer", args[cur_arg]);
385 return ACT_RET_PRS_ERR;
386 }
387 }
Tim Duesterhusd2bedcc2021-04-15 21:45:57 +0200388 else {
389 memprintf(err, "unknown normalizer '%s'", args[cur_arg]);
390 return ACT_RET_PRS_ERR;
391 }
392
393 *orig_arg = cur_arg;
394 return ACT_RET_PRS_OK;
395}
396
Willy Tarreau33810222019-06-12 17:44:02 +0200397/* This function executes a replace-uri action. It finds its arguments in
Christopher Faulet96bff762019-12-17 13:46:18 +0100398 * <rule>.arg.http. It builds a string in the trash from the format string
Willy Tarreau33810222019-06-12 17:44:02 +0200399 * previously filled by function parse_replace_uri() and will execute the regex
Christopher Faulet96bff762019-12-17 13:46:18 +0100400 * in <http.re> to replace the URI. It uses the format string present in
Christopher Faulet2c22a692019-12-18 15:39:56 +0100401 * <http.fmt>. The component to act on (path/uri) is taken from <.action> which
Christopher Faulet96bff762019-12-17 13:46:18 +0100402 * contains 1 for the path or 3 for the URI (values used by
403 * http_req_replace_stline()). On success, it returns ACT_RET_CONT. If an error
404 * occurs while soft rewrites are enabled, the action is canceled, but the rule
405 * processing continue. Otherwsize ACT_RET_ERR is returned.
Willy Tarreau33810222019-06-12 17:44:02 +0200406 */
407static enum act_return http_action_replace_uri(struct act_rule *rule, struct proxy *px,
408 struct session *sess, struct stream *s, int flags)
409{
Christopher Faulet13403762019-12-13 09:01:57 +0100410 enum act_return ret = ACT_RET_CONT;
Willy Tarreau33810222019-06-12 17:44:02 +0200411 struct buffer *replace, *output;
412 struct ist uri;
413 int len;
414
415 replace = alloc_trash_chunk();
416 output = alloc_trash_chunk();
417 if (!replace || !output)
Christopher Faulete00d06c2019-12-16 17:18:42 +0100418 goto fail_alloc;
Christopher Faulet12c28b62019-07-15 16:30:24 +0200419 uri = htx_sl_req_uri(http_get_stline(htxbuf(&s->req.buf)));
Willy Tarreau262c3f12019-12-17 06:52:51 +0100420
Christopher Faulet1fa0cc12020-09-02 11:10:38 +0200421 if (rule->action == 1) // replace-path
422 uri = iststop(http_get_path(uri), '?');
Christopher Faulet312294f2020-09-02 17:17:44 +0200423 else if (rule->action == 4) // replace-pathq
424 uri = http_get_path(uri);
Willy Tarreau262c3f12019-12-17 06:52:51 +0100425
Christopher Faulet96bff762019-12-17 13:46:18 +0100426 if (!regex_exec_match2(rule->arg.http.re, uri.ptr, uri.len, MAX_MATCH, pmatch, 0))
Willy Tarreau33810222019-06-12 17:44:02 +0200427 goto leave;
428
Christopher Faulet96bff762019-12-17 13:46:18 +0100429 replace->data = build_logline(s, replace->area, replace->size, &rule->arg.http.fmt);
Willy Tarreau33810222019-06-12 17:44:02 +0200430
431 /* note: uri.ptr doesn't need to be zero-terminated because it will
432 * only be used to pick pmatch references.
433 */
434 len = exp_replace(output->area, output->size, uri.ptr, replace->area, pmatch);
435 if (len == -1)
Christopher Faulete00d06c2019-12-16 17:18:42 +0100436 goto fail_rewrite;
Willy Tarreau33810222019-06-12 17:44:02 +0200437
Christopher Faulet2c22a692019-12-18 15:39:56 +0100438 if (http_req_replace_stline(rule->action, output->area, len, px, s) == -1)
Christopher Faulete00d06c2019-12-16 17:18:42 +0100439 goto fail_rewrite;
Willy Tarreau33810222019-06-12 17:44:02 +0200440
Christopher Faulete00d06c2019-12-16 17:18:42 +0100441 leave:
Willy Tarreau33810222019-06-12 17:44:02 +0200442 free_trash_chunk(output);
443 free_trash_chunk(replace);
444 return ret;
Christopher Faulete00d06c2019-12-16 17:18:42 +0100445
446 fail_alloc:
447 if (!(s->flags & SF_ERR_MASK))
448 s->flags |= SF_ERR_RESOURCE;
449 ret = ACT_RET_ERR;
450 goto leave;
451
452 fail_rewrite:
Willy Tarreau4781b152021-04-06 13:53:36 +0200453 _HA_ATOMIC_INC(&sess->fe->fe_counters.failed_rewrites);
Christopher Faulete00d06c2019-12-16 17:18:42 +0100454 if (s->flags & SF_BE_ASSIGNED)
Willy Tarreau4781b152021-04-06 13:53:36 +0200455 _HA_ATOMIC_INC(&s->be->be_counters.failed_rewrites);
William Lallemand36119de2021-03-08 15:26:48 +0100456 if (sess->listener && sess->listener->counters)
Willy Tarreau4781b152021-04-06 13:53:36 +0200457 _HA_ATOMIC_INC(&sess->listener->counters->failed_rewrites);
Christopher Faulete00d06c2019-12-16 17:18:42 +0100458 if (objt_server(s->target))
Willy Tarreau4781b152021-04-06 13:53:36 +0200459 _HA_ATOMIC_INC(&__objt_server(s->target)->counters.failed_rewrites);
Christopher Faulete00d06c2019-12-16 17:18:42 +0100460
Christopher Faulet333bf8c2020-01-22 14:38:05 +0100461 if (!(s->txn->req.flags & HTTP_MSGF_SOFT_RW)) {
Christopher Faulete00d06c2019-12-16 17:18:42 +0100462 ret = ACT_RET_ERR;
Christopher Faulet333bf8c2020-01-22 14:38:05 +0100463 if (!(s->flags & SF_ERR_MASK))
464 s->flags |= SF_ERR_PRXCOND;
465 }
Christopher Faulete00d06c2019-12-16 17:18:42 +0100466 goto leave;
Willy Tarreau33810222019-06-12 17:44:02 +0200467}
468
Christopher Faulet312294f2020-09-02 17:17:44 +0200469/* parse a "replace-uri", "replace-path" or "replace-pathq"
470 * http-request action.
Willy Tarreau33810222019-06-12 17:44:02 +0200471 * This action takes 2 arguments (a regex and a replacement format string).
Christopher Faulet2c22a692019-12-18 15:39:56 +0100472 * The resulting rule makes use of <.action> to store the action (1/3 for now),
Christopher Faulet96bff762019-12-17 13:46:18 +0100473 * <http.re> to store the compiled regex, and <http.fmt> to store the log-format
Willy Tarreau33810222019-06-12 17:44:02 +0200474 * list head. It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
475 */
476static enum act_parse_ret parse_replace_uri(const char **args, int *orig_arg, struct proxy *px,
477 struct act_rule *rule, char **err)
478{
479 int cur_arg = *orig_arg;
480 char *error = NULL;
481
Christopher Faulet312294f2020-09-02 17:17:44 +0200482 switch (args[0][8]) {
483 case 'p':
484 if (args[0][12] == 'q')
485 rule->action = 4; // replace-pathq, same as set-pathq
486 else
487 rule->action = 1; // replace-path, same as set-path
488 break;
489 case 'u':
Christopher Faulet2c22a692019-12-18 15:39:56 +0100490 rule->action = 3; // replace-uri, same as set-uri
Christopher Faulet312294f2020-09-02 17:17:44 +0200491 break;
492 default:
493 memprintf(err, "internal error: unhandled action '%s'", args[0]);
494 return ACT_RET_PRS_ERR;
495 }
Willy Tarreau262c3f12019-12-17 06:52:51 +0100496
Willy Tarreau33810222019-06-12 17:44:02 +0200497 rule->action_ptr = http_action_replace_uri;
Christopher Faulet2eb53962020-01-14 14:47:34 +0100498 rule->release_ptr = release_http_action;
Willy Tarreau33810222019-06-12 17:44:02 +0200499
500 if (!*args[cur_arg] || !*args[cur_arg+1] ||
501 (*args[cur_arg+2] && strcmp(args[cur_arg+2], "if") != 0 && strcmp(args[cur_arg+2], "unless") != 0)) {
502 memprintf(err, "expects exactly 2 arguments <match-regex> and <replace-format>");
503 return ACT_RET_PRS_ERR;
504 }
505
Christopher Faulet96bff762019-12-17 13:46:18 +0100506 if (!(rule->arg.http.re = regex_comp(args[cur_arg], 1, 1, &error))) {
Willy Tarreau33810222019-06-12 17:44:02 +0200507 memprintf(err, "failed to parse the regex : %s", error);
508 free(error);
509 return ACT_RET_PRS_ERR;
510 }
511
Christopher Faulet96bff762019-12-17 13:46:18 +0100512 LIST_INIT(&rule->arg.http.fmt);
Willy Tarreau33810222019-06-12 17:44:02 +0200513 px->conf.args.ctx = ARGC_HRQ;
Christopher Faulet96bff762019-12-17 13:46:18 +0100514 if (!parse_logformat_string(args[cur_arg + 1], px, &rule->arg.http.fmt, LOG_OPT_HTTP,
Willy Tarreau33810222019-06-12 17:44:02 +0200515 (px->cap & PR_CAP_FE) ? SMP_VAL_FE_HRQ_HDR : SMP_VAL_BE_HRQ_HDR, err)) {
Christopher Faulet1337b322020-01-14 14:50:55 +0100516 regex_free(rule->arg.http.re);
Willy Tarreau33810222019-06-12 17:44:02 +0200517 return ACT_RET_PRS_ERR;
518 }
519
520 (*orig_arg) += 2;
521 return ACT_RET_PRS_OK;
522}
523
Willy Tarreau79e57332018-10-02 16:01:16 +0200524/* This function is just a compliant action wrapper for "set-status". */
525static enum act_return action_http_set_status(struct act_rule *rule, struct proxy *px,
526 struct session *sess, struct stream *s, int flags)
527{
Christopher Faulet96bff762019-12-17 13:46:18 +0100528 if (http_res_set_status(rule->arg.http.i, rule->arg.http.str, s) == -1) {
Willy Tarreau4781b152021-04-06 13:53:36 +0200529 _HA_ATOMIC_INC(&sess->fe->fe_counters.failed_rewrites);
Christopher Faulete00d06c2019-12-16 17:18:42 +0100530 if (s->flags & SF_BE_ASSIGNED)
Willy Tarreau4781b152021-04-06 13:53:36 +0200531 _HA_ATOMIC_INC(&s->be->be_counters.failed_rewrites);
William Lallemand36119de2021-03-08 15:26:48 +0100532 if (sess->listener && sess->listener->counters)
Willy Tarreau4781b152021-04-06 13:53:36 +0200533 _HA_ATOMIC_INC(&sess->listener->counters->failed_rewrites);
Christopher Faulete00d06c2019-12-16 17:18:42 +0100534 if (objt_server(s->target))
Willy Tarreau4781b152021-04-06 13:53:36 +0200535 _HA_ATOMIC_INC(&__objt_server(s->target)->counters.failed_rewrites);
Christopher Faulete00d06c2019-12-16 17:18:42 +0100536
Christopher Faulet333bf8c2020-01-22 14:38:05 +0100537 if (!(s->txn->req.flags & HTTP_MSGF_SOFT_RW)) {
Christopher Faulet333bf8c2020-01-22 14:38:05 +0100538 if (!(s->flags & SF_ERR_MASK))
539 s->flags |= SF_ERR_PRXCOND;
Christopher Faulet692a6c22020-02-07 10:22:31 +0100540 return ACT_RET_ERR;
Christopher Faulet333bf8c2020-01-22 14:38:05 +0100541 }
Christopher Faulete00d06c2019-12-16 17:18:42 +0100542 }
543
Willy Tarreau79e57332018-10-02 16:01:16 +0200544 return ACT_RET_CONT;
545}
546
547/* parse set-status action:
548 * This action accepts a single argument of type int representing
549 * an http status code. It returns ACT_RET_PRS_OK on success,
550 * ACT_RET_PRS_ERR on error.
551 */
552static enum act_parse_ret parse_http_set_status(const char **args, int *orig_arg, struct proxy *px,
553 struct act_rule *rule, char **err)
554{
555 char *error;
556
557 rule->action = ACT_CUSTOM;
558 rule->action_ptr = action_http_set_status;
Christopher Faulet2eb53962020-01-14 14:47:34 +0100559 rule->release_ptr = release_http_action;
Willy Tarreau79e57332018-10-02 16:01:16 +0200560
561 /* Check if an argument is available */
562 if (!*args[*orig_arg]) {
563 memprintf(err, "expects 1 argument: <status>; or 3 arguments: <status> reason <fmt>");
564 return ACT_RET_PRS_ERR;
565 }
566
567 /* convert status code as integer */
Christopher Faulet96bff762019-12-17 13:46:18 +0100568 rule->arg.http.i = strtol(args[*orig_arg], &error, 10);
569 if (*error != '\0' || rule->arg.http.i < 100 || rule->arg.http.i > 999) {
Willy Tarreau79e57332018-10-02 16:01:16 +0200570 memprintf(err, "expects an integer status code between 100 and 999");
571 return ACT_RET_PRS_ERR;
572 }
573
574 (*orig_arg)++;
575
576 /* set custom reason string */
Christopher Faulet96bff762019-12-17 13:46:18 +0100577 rule->arg.http.str = ist(NULL); // If null, we use the default reason for the status code.
Willy Tarreau79e57332018-10-02 16:01:16 +0200578 if (*args[*orig_arg] && strcmp(args[*orig_arg], "reason") == 0 &&
579 (*args[*orig_arg + 1] && strcmp(args[*orig_arg + 1], "if") != 0 && strcmp(args[*orig_arg + 1], "unless") != 0)) {
580 (*orig_arg)++;
Christopher Faulet96bff762019-12-17 13:46:18 +0100581 rule->arg.http.str.ptr = strdup(args[*orig_arg]);
582 rule->arg.http.str.len = strlen(rule->arg.http.str.ptr);
Willy Tarreau79e57332018-10-02 16:01:16 +0200583 (*orig_arg)++;
584 }
585
Christopher Fauletc20b3712020-01-27 15:51:56 +0100586 LIST_INIT(&rule->arg.http.fmt);
Willy Tarreau79e57332018-10-02 16:01:16 +0200587 return ACT_RET_PRS_OK;
588}
589
590/* This function executes the "reject" HTTP action. It clears the request and
591 * response buffer without sending any response. It can be useful as an HTTP
592 * alternative to the silent-drop action to defend against DoS attacks, and may
593 * also be used with HTTP/2 to close a connection instead of just a stream.
594 * The txn status is unchanged, indicating no response was sent. The termination
Christopher Faulet90d22a82020-03-06 11:18:39 +0100595 * flags will indicate "PR". It always returns ACT_RET_ABRT.
Willy Tarreau79e57332018-10-02 16:01:16 +0200596 */
597static enum act_return http_action_reject(struct act_rule *rule, struct proxy *px,
598 struct session *sess, struct stream *s, int flags)
599{
Willy Tarreau0f9cd7b2019-01-31 19:02:43 +0100600 si_must_kill_conn(chn_prod(&s->req));
Willy Tarreau79e57332018-10-02 16:01:16 +0200601 channel_abort(&s->req);
602 channel_abort(&s->res);
Christopher Fauletd4a824e2020-03-06 15:07:09 +0100603 s->req.analysers &= AN_REQ_FLT_END;
604 s->res.analysers &= AN_RES_FLT_END;
Willy Tarreau79e57332018-10-02 16:01:16 +0200605
Willy Tarreau4781b152021-04-06 13:53:36 +0200606 _HA_ATOMIC_INC(&s->be->be_counters.denied_req);
607 _HA_ATOMIC_INC(&sess->fe->fe_counters.denied_req);
Willy Tarreau79e57332018-10-02 16:01:16 +0200608 if (sess->listener && sess->listener->counters)
Willy Tarreau4781b152021-04-06 13:53:36 +0200609 _HA_ATOMIC_INC(&sess->listener->counters->denied_req);
Willy Tarreau79e57332018-10-02 16:01:16 +0200610
611 if (!(s->flags & SF_ERR_MASK))
612 s->flags |= SF_ERR_PRXCOND;
613 if (!(s->flags & SF_FINST_MASK))
614 s->flags |= SF_FINST_R;
615
Christopher Faulet90d22a82020-03-06 11:18:39 +0100616 return ACT_RET_ABRT;
Willy Tarreau79e57332018-10-02 16:01:16 +0200617}
618
619/* parse the "reject" action:
620 * This action takes no argument and returns ACT_RET_PRS_OK on success,
621 * ACT_RET_PRS_ERR on error.
622 */
623static enum act_parse_ret parse_http_action_reject(const char **args, int *orig_arg, struct proxy *px,
624 struct act_rule *rule, char **err)
625{
626 rule->action = ACT_CUSTOM;
627 rule->action_ptr = http_action_reject;
628 return ACT_RET_PRS_OK;
629}
630
Olivier Houchard602bf7d2019-05-10 13:59:15 +0200631/* This function executes the "disable-l7-retry" HTTP action.
632 * It disables L7 retries (all retry except for a connection failure). This
633 * can be useful for example to avoid retrying on POST requests.
634 * It just removes the L7 retry flag on the stream_interface, and always
635 * return ACT_RET_CONT;
636 */
637static enum act_return http_req_disable_l7_retry(struct act_rule *rule, struct proxy *px,
638 struct session *sess, struct stream *s, int flags)
639{
640 struct stream_interface *si = &s->si[1];
641
642 /* In theory, the SI_FL_L7_RETRY flags isn't set at this point, but
643 * let's be future-proof and remove it anyway.
644 */
645 si->flags &= ~SI_FL_L7_RETRY;
646 si->flags |= SI_FL_D_L7_RETRY;
647 return ACT_RET_CONT;
648}
649
650/* parse the "disable-l7-retry" action:
651 * This action takes no argument and returns ACT_RET_PRS_OK on success,
652 * ACT_RET_PRS_ERR on error.
653 */
654static enum act_parse_ret parse_http_req_disable_l7_retry(const char **args,
655 int *orig_args, struct proxy *px,
656 struct act_rule *rule, char **err)
657{
658 rule->action = ACT_CUSTOM;
659 rule->action_ptr = http_req_disable_l7_retry;
660 return ACT_RET_PRS_OK;
661}
662
Willy Tarreau79e57332018-10-02 16:01:16 +0200663/* This function executes the "capture" action. It executes a fetch expression,
664 * turns the result into a string and puts it in a capture slot. It always
665 * returns 1. If an error occurs the action is cancelled, but the rule
666 * processing continues.
667 */
668static enum act_return http_action_req_capture(struct act_rule *rule, struct proxy *px,
669 struct session *sess, struct stream *s, int flags)
670{
671 struct sample *key;
672 struct cap_hdr *h = rule->arg.cap.hdr;
673 char **cap = s->req_cap;
674 int len;
675
676 key = sample_fetch_as_type(s->be, sess, s, SMP_OPT_DIR_REQ|SMP_OPT_FINAL, rule->arg.cap.expr, SMP_T_STR);
677 if (!key)
678 return ACT_RET_CONT;
679
680 if (cap[h->index] == NULL)
681 cap[h->index] = pool_alloc(h->pool);
682
683 if (cap[h->index] == NULL) /* no more capture memory */
684 return ACT_RET_CONT;
685
686 len = key->data.u.str.data;
687 if (len > h->len)
688 len = h->len;
689
690 memcpy(cap[h->index], key->data.u.str.area, len);
691 cap[h->index][len] = 0;
692 return ACT_RET_CONT;
693}
694
695/* This function executes the "capture" action and store the result in a
696 * capture slot if exists. It executes a fetch expression, turns the result
697 * into a string and puts it in a capture slot. It always returns 1. If an
698 * error occurs the action is cancelled, but the rule processing continues.
699 */
700static enum act_return http_action_req_capture_by_id(struct act_rule *rule, struct proxy *px,
701 struct session *sess, struct stream *s, int flags)
702{
703 struct sample *key;
704 struct cap_hdr *h;
705 char **cap = s->req_cap;
706 struct proxy *fe = strm_fe(s);
707 int len;
708 int i;
709
710 /* Look for the original configuration. */
711 for (h = fe->req_cap, i = fe->nb_req_cap - 1;
712 h != NULL && i != rule->arg.capid.idx ;
713 i--, h = h->next);
714 if (!h)
715 return ACT_RET_CONT;
716
717 key = sample_fetch_as_type(s->be, sess, s, SMP_OPT_DIR_REQ|SMP_OPT_FINAL, rule->arg.capid.expr, SMP_T_STR);
718 if (!key)
719 return ACT_RET_CONT;
720
721 if (cap[h->index] == NULL)
722 cap[h->index] = pool_alloc(h->pool);
723
724 if (cap[h->index] == NULL) /* no more capture memory */
725 return ACT_RET_CONT;
726
727 len = key->data.u.str.data;
728 if (len > h->len)
729 len = h->len;
730
731 memcpy(cap[h->index], key->data.u.str.area, len);
732 cap[h->index][len] = 0;
733 return ACT_RET_CONT;
734}
735
736/* Check an "http-request capture" action.
737 *
738 * The function returns 1 in success case, otherwise, it returns 0 and err is
739 * filled.
740 */
741static int check_http_req_capture(struct act_rule *rule, struct proxy *px, char **err)
742{
743 if (rule->action_ptr != http_action_req_capture_by_id)
744 return 1;
745
Baptiste Assmann19a69b32020-01-16 14:34:22 +0100746 /* capture slots can only be declared in frontends, so we can't check their
747 * existence in backends at configuration parsing step
748 */
749 if (px->cap & PR_CAP_FE && rule->arg.capid.idx >= px->nb_req_cap) {
Willy Tarreau79e57332018-10-02 16:01:16 +0200750 memprintf(err, "unable to find capture id '%d' referenced by http-request capture rule",
751 rule->arg.capid.idx);
752 return 0;
753 }
754
755 return 1;
756}
757
Christopher Faulet2eb53962020-01-14 14:47:34 +0100758/* Release memory allocate by an http capture action */
759static void release_http_capture(struct act_rule *rule)
760{
761 if (rule->action_ptr == http_action_req_capture)
762 release_sample_expr(rule->arg.cap.expr);
763 else
764 release_sample_expr(rule->arg.capid.expr);
765}
766
Willy Tarreau79e57332018-10-02 16:01:16 +0200767/* parse an "http-request capture" action. It takes a single argument which is
768 * a sample fetch expression. It stores the expression into arg->act.p[0] and
769 * the allocated hdr_cap struct or the preallocated "id" into arg->act.p[1].
770 * It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
771 */
772static enum act_parse_ret parse_http_req_capture(const char **args, int *orig_arg, struct proxy *px,
773 struct act_rule *rule, char **err)
774{
775 struct sample_expr *expr;
776 struct cap_hdr *hdr;
777 int cur_arg;
778 int len = 0;
779
780 for (cur_arg = *orig_arg; cur_arg < *orig_arg + 3 && *args[cur_arg]; cur_arg++)
781 if (strcmp(args[cur_arg], "if") == 0 ||
782 strcmp(args[cur_arg], "unless") == 0)
783 break;
784
785 if (cur_arg < *orig_arg + 3) {
786 memprintf(err, "expects <expression> [ 'len' <length> | id <idx> ]");
787 return ACT_RET_PRS_ERR;
788 }
789
790 cur_arg = *orig_arg;
Willy Tarreaue3b57bf2020-02-14 16:50:14 +0100791 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 +0200792 if (!expr)
793 return ACT_RET_PRS_ERR;
794
795 if (!(expr->fetch->val & SMP_VAL_FE_HRQ_HDR)) {
796 memprintf(err,
797 "fetch method '%s' extracts information from '%s', none of which is available here",
798 args[cur_arg-1], sample_src_names(expr->fetch->use));
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 if (!args[cur_arg] || !*args[cur_arg]) {
804 memprintf(err, "expects 'len or 'id'");
Christopher Faulet1337b322020-01-14 14:50:55 +0100805 release_sample_expr(expr);
Willy Tarreau79e57332018-10-02 16:01:16 +0200806 return ACT_RET_PRS_ERR;
807 }
808
809 if (strcmp(args[cur_arg], "len") == 0) {
810 cur_arg++;
811
812 if (!(px->cap & PR_CAP_FE)) {
813 memprintf(err, "proxy '%s' has no frontend capability", px->id);
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
818 px->conf.args.ctx = ARGC_CAP;
819
820 if (!args[cur_arg]) {
821 memprintf(err, "missing length value");
Christopher Faulet1337b322020-01-14 14:50:55 +0100822 release_sample_expr(expr);
Willy Tarreau79e57332018-10-02 16:01:16 +0200823 return ACT_RET_PRS_ERR;
824 }
825 /* we copy the table name for now, it will be resolved later */
826 len = atoi(args[cur_arg]);
827 if (len <= 0) {
828 memprintf(err, "length must be > 0");
Christopher Faulet1337b322020-01-14 14:50:55 +0100829 release_sample_expr(expr);
Willy Tarreau79e57332018-10-02 16:01:16 +0200830 return ACT_RET_PRS_ERR;
831 }
832 cur_arg++;
833
Willy Tarreau79e57332018-10-02 16:01:16 +0200834 hdr = calloc(1, sizeof(*hdr));
835 hdr->next = px->req_cap;
836 hdr->name = NULL; /* not a header capture */
837 hdr->namelen = 0;
838 hdr->len = len;
839 hdr->pool = create_pool("caphdr", hdr->len + 1, MEM_F_SHARED);
840 hdr->index = px->nb_req_cap++;
841
842 px->req_cap = hdr;
843 px->to_log |= LW_REQHDR;
844
845 rule->action = ACT_CUSTOM;
846 rule->action_ptr = http_action_req_capture;
Christopher Faulet2eb53962020-01-14 14:47:34 +0100847 rule->release_ptr = release_http_capture;
Willy Tarreau79e57332018-10-02 16:01:16 +0200848 rule->arg.cap.expr = expr;
849 rule->arg.cap.hdr = hdr;
850 }
851
852 else if (strcmp(args[cur_arg], "id") == 0) {
853 int id;
854 char *error;
855
856 cur_arg++;
857
858 if (!args[cur_arg]) {
859 memprintf(err, "missing id value");
Christopher Faulet1337b322020-01-14 14:50:55 +0100860 release_sample_expr(expr);
Willy Tarreau79e57332018-10-02 16:01:16 +0200861 return ACT_RET_PRS_ERR;
862 }
863
864 id = strtol(args[cur_arg], &error, 10);
865 if (*error != '\0') {
866 memprintf(err, "cannot parse id '%s'", args[cur_arg]);
Christopher Faulet1337b322020-01-14 14:50:55 +0100867 release_sample_expr(expr);
Willy Tarreau79e57332018-10-02 16:01:16 +0200868 return ACT_RET_PRS_ERR;
869 }
870 cur_arg++;
871
872 px->conf.args.ctx = ARGC_CAP;
873
874 rule->action = ACT_CUSTOM;
875 rule->action_ptr = http_action_req_capture_by_id;
876 rule->check_ptr = check_http_req_capture;
Christopher Faulet2eb53962020-01-14 14:47:34 +0100877 rule->release_ptr = release_http_capture;
Willy Tarreau79e57332018-10-02 16:01:16 +0200878 rule->arg.capid.expr = expr;
879 rule->arg.capid.idx = id;
880 }
881
882 else {
883 memprintf(err, "expects 'len' or 'id', found '%s'", args[cur_arg]);
Christopher Faulet1337b322020-01-14 14:50:55 +0100884 release_sample_expr(expr);
Willy Tarreau79e57332018-10-02 16:01:16 +0200885 return ACT_RET_PRS_ERR;
886 }
887
888 *orig_arg = cur_arg;
889 return ACT_RET_PRS_OK;
890}
891
892/* This function executes the "capture" action and store the result in a
893 * capture slot if exists. It executes a fetch expression, turns the result
894 * into a string and puts it in a capture slot. It always returns 1. If an
895 * error occurs the action is cancelled, but the rule processing continues.
896 */
897static enum act_return http_action_res_capture_by_id(struct act_rule *rule, struct proxy *px,
898 struct session *sess, struct stream *s, int flags)
899{
900 struct sample *key;
901 struct cap_hdr *h;
902 char **cap = s->res_cap;
903 struct proxy *fe = strm_fe(s);
904 int len;
905 int i;
906
907 /* Look for the original configuration. */
908 for (h = fe->rsp_cap, i = fe->nb_rsp_cap - 1;
909 h != NULL && i != rule->arg.capid.idx ;
910 i--, h = h->next);
911 if (!h)
912 return ACT_RET_CONT;
913
914 key = sample_fetch_as_type(s->be, sess, s, SMP_OPT_DIR_RES|SMP_OPT_FINAL, rule->arg.capid.expr, SMP_T_STR);
915 if (!key)
916 return ACT_RET_CONT;
917
918 if (cap[h->index] == NULL)
919 cap[h->index] = pool_alloc(h->pool);
920
921 if (cap[h->index] == NULL) /* no more capture memory */
922 return ACT_RET_CONT;
923
924 len = key->data.u.str.data;
925 if (len > h->len)
926 len = h->len;
927
928 memcpy(cap[h->index], key->data.u.str.area, len);
929 cap[h->index][len] = 0;
930 return ACT_RET_CONT;
931}
932
933/* Check an "http-response capture" action.
934 *
935 * The function returns 1 in success case, otherwise, it returns 0 and err is
936 * filled.
937 */
938static int check_http_res_capture(struct act_rule *rule, struct proxy *px, char **err)
939{
940 if (rule->action_ptr != http_action_res_capture_by_id)
941 return 1;
942
Tim Duesterhusf3f4aa02020-07-03 13:43:42 +0200943 /* capture slots can only be declared in frontends, so we can't check their
944 * existence in backends at configuration parsing step
945 */
946 if (px->cap & PR_CAP_FE && rule->arg.capid.idx >= px->nb_rsp_cap) {
Willy Tarreau79e57332018-10-02 16:01:16 +0200947 memprintf(err, "unable to find capture id '%d' referenced by http-response capture rule",
948 rule->arg.capid.idx);
949 return 0;
950 }
951
952 return 1;
953}
954
955/* parse an "http-response capture" action. It takes a single argument which is
956 * a sample fetch expression. It stores the expression into arg->act.p[0] and
Thayne McCombs8f0cc5c2021-01-07 21:35:52 -0700957 * the allocated hdr_cap struct of the preallocated id into arg->act.p[1].
Willy Tarreau79e57332018-10-02 16:01:16 +0200958 * It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
959 */
960static enum act_parse_ret parse_http_res_capture(const char **args, int *orig_arg, struct proxy *px,
961 struct act_rule *rule, char **err)
962{
963 struct sample_expr *expr;
964 int cur_arg;
965 int id;
966 char *error;
967
968 for (cur_arg = *orig_arg; cur_arg < *orig_arg + 3 && *args[cur_arg]; cur_arg++)
969 if (strcmp(args[cur_arg], "if") == 0 ||
970 strcmp(args[cur_arg], "unless") == 0)
971 break;
972
973 if (cur_arg < *orig_arg + 3) {
974 memprintf(err, "expects <expression> id <idx>");
975 return ACT_RET_PRS_ERR;
976 }
977
978 cur_arg = *orig_arg;
Willy Tarreaue3b57bf2020-02-14 16:50:14 +0100979 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 +0200980 if (!expr)
981 return ACT_RET_PRS_ERR;
982
983 if (!(expr->fetch->val & SMP_VAL_FE_HRS_HDR)) {
984 memprintf(err,
985 "fetch method '%s' extracts information from '%s', none of which is available here",
986 args[cur_arg-1], sample_src_names(expr->fetch->use));
Christopher Faulet1337b322020-01-14 14:50:55 +0100987 release_sample_expr(expr);
Willy Tarreau79e57332018-10-02 16:01:16 +0200988 return ACT_RET_PRS_ERR;
989 }
990
991 if (!args[cur_arg] || !*args[cur_arg]) {
992 memprintf(err, "expects 'id'");
Christopher Faulet1337b322020-01-14 14:50:55 +0100993 release_sample_expr(expr);
Willy Tarreau79e57332018-10-02 16:01:16 +0200994 return ACT_RET_PRS_ERR;
995 }
996
997 if (strcmp(args[cur_arg], "id") != 0) {
998 memprintf(err, "expects 'id', found '%s'", args[cur_arg]);
Christopher Faulet1337b322020-01-14 14:50:55 +0100999 release_sample_expr(expr);
Willy Tarreau79e57332018-10-02 16:01:16 +02001000 return ACT_RET_PRS_ERR;
1001 }
1002
1003 cur_arg++;
1004
1005 if (!args[cur_arg]) {
1006 memprintf(err, "missing id value");
Christopher Faulet1337b322020-01-14 14:50:55 +01001007 release_sample_expr(expr);
Willy Tarreau79e57332018-10-02 16:01:16 +02001008 return ACT_RET_PRS_ERR;
1009 }
1010
1011 id = strtol(args[cur_arg], &error, 10);
1012 if (*error != '\0') {
1013 memprintf(err, "cannot parse id '%s'", args[cur_arg]);
Christopher Faulet1337b322020-01-14 14:50:55 +01001014 release_sample_expr(expr);
Willy Tarreau79e57332018-10-02 16:01:16 +02001015 return ACT_RET_PRS_ERR;
1016 }
1017 cur_arg++;
1018
1019 px->conf.args.ctx = ARGC_CAP;
1020
1021 rule->action = ACT_CUSTOM;
1022 rule->action_ptr = http_action_res_capture_by_id;
1023 rule->check_ptr = check_http_res_capture;
Christopher Faulet2eb53962020-01-14 14:47:34 +01001024 rule->release_ptr = release_http_capture;
Willy Tarreau79e57332018-10-02 16:01:16 +02001025 rule->arg.capid.expr = expr;
1026 rule->arg.capid.idx = id;
1027
1028 *orig_arg = cur_arg;
1029 return ACT_RET_PRS_OK;
1030}
1031
Christopher Faulet81e20172019-12-12 16:40:30 +01001032/* Parse a "allow" action for a request or a response rule. It takes no argument. It
1033 * returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
1034 */
1035static enum act_parse_ret parse_http_allow(const char **args, int *orig_arg, struct proxy *px,
1036 struct act_rule *rule, char **err)
1037{
1038 rule->action = ACT_ACTION_ALLOW;
Christopher Faulet245cf792019-12-18 14:58:12 +01001039 rule->flags |= ACT_FLAG_FINAL;
Christopher Faulet81e20172019-12-12 16:40:30 +01001040 return ACT_RET_PRS_OK;
1041}
1042
Christopher Faulete0fca292020-01-13 21:49:03 +01001043/* Parse "deny" or "tarpit" actions for a request rule or "deny" action for a
Christopher Faulet5cb513a2020-05-13 17:56:56 +02001044 * response rule. It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on
1045 * error. It relies on http_parse_http_reply() to set
1046 * <.arg.http_reply>.
Christopher Faulet81e20172019-12-12 16:40:30 +01001047 */
Christopher Faulete0fca292020-01-13 21:49:03 +01001048static enum act_parse_ret parse_http_deny(const char **args, int *orig_arg, struct proxy *px,
1049 struct act_rule *rule, char **err)
Christopher Faulet81e20172019-12-12 16:40:30 +01001050{
Christopher Faulet5cb513a2020-05-13 17:56:56 +02001051 int default_status;
1052 int cur_arg, arg = 0;
Christopher Faulet81e20172019-12-12 16:40:30 +01001053
1054 cur_arg = *orig_arg;
Christopher Faulete0fca292020-01-13 21:49:03 +01001055 if (rule->from == ACT_F_HTTP_REQ) {
Tim Duesterhuse5ff1412021-01-02 22:31:53 +01001056 if (strcmp(args[cur_arg - 1], "tarpit") == 0) {
Christopher Faulete0fca292020-01-13 21:49:03 +01001057 rule->action = ACT_HTTP_REQ_TARPIT;
Christopher Faulet5cb513a2020-05-13 17:56:56 +02001058 default_status = 500;
Christopher Faulet81e20172019-12-12 16:40:30 +01001059 }
Christopher Faulete0fca292020-01-13 21:49:03 +01001060 else {
1061 rule->action = ACT_ACTION_DENY;
Christopher Faulet5cb513a2020-05-13 17:56:56 +02001062 default_status = 403;
Christopher Faulet81e20172019-12-12 16:40:30 +01001063 }
Christopher Faulet81e20172019-12-12 16:40:30 +01001064 }
Christopher Faulete0fca292020-01-13 21:49:03 +01001065 else {
Christopher Faulet554c0eb2020-01-14 12:00:28 +01001066 rule->action = ACT_ACTION_DENY;
Christopher Faulet5cb513a2020-05-13 17:56:56 +02001067 default_status = 502;
Christopher Faulete0fca292020-01-13 21:49:03 +01001068 }
Christopher Faulet040c8cd2020-01-13 16:43:45 +01001069
Christopher Faulet5cb513a2020-05-13 17:56:56 +02001070 /* If no args or only a deny_status specified, fallback on the legacy
1071 * mode and use default error files despite the fact that
1072 * default-errorfiles is not used. Otherwise, parse an http reply.
1073 */
Christopher Faulet040c8cd2020-01-13 16:43:45 +01001074
Christopher Faulet5cb513a2020-05-13 17:56:56 +02001075 /* Prepare parsing of log-format strings */
1076 px->conf.args.ctx = ((rule->from == ACT_F_HTTP_REQ) ? ARGC_HRQ : ARGC_HRS);
Christopher Faulet554c0eb2020-01-14 12:00:28 +01001077
Christopher Faulet9467f182020-06-30 09:32:01 +02001078 if (!*(args[cur_arg]) || strcmp(args[cur_arg], "if") == 0 || strcmp(args[cur_arg], "unless") == 0) {
Christopher Faulet5cb513a2020-05-13 17:56:56 +02001079 rule->arg.http_reply = http_parse_http_reply((const char *[]){"default-errorfiles", ""}, &arg, px, default_status, err);
1080 goto end;
Christopher Faulet554c0eb2020-01-14 12:00:28 +01001081 }
Christopher Faulet5cb513a2020-05-13 17:56:56 +02001082
1083 if (strcmp(args[cur_arg], "deny_status") == 0) {
Christopher Faulet9467f182020-06-30 09:32:01 +02001084 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 +02001085 rule->arg.http_reply = http_parse_http_reply((const char *[]){"status", args[cur_arg+1], "default-errorfiles", ""},
1086 &arg, px, default_status, err);
1087 *orig_arg += 2;
1088 goto end;
Christopher Faulet554c0eb2020-01-14 12:00:28 +01001089 }
Christopher Faulet5cb513a2020-05-13 17:56:56 +02001090 args[cur_arg] += 5; /* skip "deny_" for the parsing */
Christopher Faulet554c0eb2020-01-14 12:00:28 +01001091 }
1092
Christopher Faulet5cb513a2020-05-13 17:56:56 +02001093 rule->arg.http_reply = http_parse_http_reply(args, orig_arg, px, default_status, err);
Christopher Faulet554c0eb2020-01-14 12:00:28 +01001094
Christopher Faulet5cb513a2020-05-13 17:56:56 +02001095 end:
1096 if (!rule->arg.http_reply)
1097 return ACT_RET_PRS_ERR;
1098
1099 rule->flags |= ACT_FLAG_FINAL;
1100 rule->check_ptr = check_act_http_reply;
1101 rule->release_ptr = release_act_http_reply;
Christopher Faulet81e20172019-12-12 16:40:30 +01001102 return ACT_RET_PRS_OK;
1103}
1104
Christopher Fauletb3048832020-05-27 15:26:43 +02001105
1106/* This function executes a auth action. It builds an 401/407 HTX message using
1107 * the corresponding proxy's error message. On success, it returns
1108 * ACT_RET_ABRT. If an error occurs ACT_RET_ERR is returned.
1109 */
1110static enum act_return http_action_auth(struct act_rule *rule, struct proxy *px,
1111 struct session *sess, struct stream *s, int flags)
1112{
1113 struct channel *req = &s->req;
1114 struct channel *res = &s->res;
1115 struct htx *htx = htx_from_buf(&res->buf);
1116 struct http_reply *reply;
1117 const char *auth_realm;
1118 struct http_hdr_ctx ctx;
1119 struct ist hdr;
1120
1121 /* Auth might be performed on regular http-req rules as well as on stats */
1122 auth_realm = rule->arg.http.str.ptr;
1123 if (!auth_realm) {
1124 if (px->uri_auth && s->current_rule_list == &px->uri_auth->http_req_rules)
1125 auth_realm = STATS_DEFAULT_REALM;
1126 else
1127 auth_realm = px->id;
1128 }
1129
1130 if (!(s->txn->flags & TX_USE_PX_CONN)) {
1131 s->txn->status = 401;
1132 hdr = ist("WWW-Authenticate");
1133 }
1134 else {
1135 s->txn->status = 407;
1136 hdr = ist("Proxy-Authenticate");
1137 }
1138 reply = http_error_message(s);
1139 channel_htx_truncate(res, htx);
1140
1141 if (chunk_printf(&trash, "Basic realm=\"%s\"", auth_realm) == -1)
1142 goto fail;
1143
1144 /* Write the generic 40x message */
1145 if (http_reply_to_htx(s, htx, reply) == -1)
1146 goto fail;
1147
1148 /* Remove all existing occurrences of the XXX-Authenticate header */
1149 ctx.blk = NULL;
1150 while (http_find_header(htx, hdr, &ctx, 1))
1151 http_remove_header(htx, &ctx);
1152
1153 /* Now a the right XXX-Authenticate header */
1154 if (!http_add_header(htx, hdr, ist2(b_orig(&trash), b_data(&trash))))
1155 goto fail;
1156
1157 /* Finally forward the reply */
1158 htx_to_buf(htx, &res->buf);
1159 if (!http_forward_proxy_resp(s, 1))
1160 goto fail;
1161
1162 /* Note: Only eval on the request */
1163 s->logs.tv_request = now;
1164 req->analysers &= AN_REQ_FLT_END;
1165
1166 if (s->sess->fe == s->be) /* report it if the request was intercepted by the frontend */
Willy Tarreau4781b152021-04-06 13:53:36 +02001167 _HA_ATOMIC_INC(&s->sess->fe->fe_counters.intercepted_req);
Christopher Fauletb3048832020-05-27 15:26:43 +02001168
1169 if (!(s->flags & SF_ERR_MASK))
1170 s->flags |= SF_ERR_LOCAL;
1171 if (!(s->flags & SF_FINST_MASK))
1172 s->flags |= SF_FINST_R;
1173
1174 stream_inc_http_err_ctr(s);
1175 return ACT_RET_ABRT;
1176
1177 fail:
1178 /* If an error occurred, remove the incomplete HTTP response from the
1179 * buffer */
1180 channel_htx_truncate(res, htx);
1181 return ACT_RET_ERR;
1182}
1183
Christopher Faulet81e20172019-12-12 16:40:30 +01001184/* Parse a "auth" action. It may take 2 optional arguments to define a "realm"
1185 * parameter. It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
1186 */
1187static enum act_parse_ret parse_http_auth(const char **args, int *orig_arg, struct proxy *px,
1188 struct act_rule *rule, char **err)
1189{
1190 int cur_arg;
1191
Christopher Fauletb3048832020-05-27 15:26:43 +02001192 rule->action = ACT_CUSTOM;
Christopher Faulet245cf792019-12-18 14:58:12 +01001193 rule->flags |= ACT_FLAG_FINAL;
Christopher Fauletb3048832020-05-27 15:26:43 +02001194 rule->action_ptr = http_action_auth;
Christopher Faulet2eb53962020-01-14 14:47:34 +01001195 rule->release_ptr = release_http_action;
Christopher Faulet81e20172019-12-12 16:40:30 +01001196
1197 cur_arg = *orig_arg;
Tim Duesterhuse5ff1412021-01-02 22:31:53 +01001198 if (strcmp(args[cur_arg], "realm") == 0) {
Christopher Faulet81e20172019-12-12 16:40:30 +01001199 cur_arg++;
1200 if (!*args[cur_arg]) {
1201 memprintf(err, "missing realm value.\n");
1202 return ACT_RET_PRS_ERR;
1203 }
Christopher Faulet96bff762019-12-17 13:46:18 +01001204 rule->arg.http.str.ptr = strdup(args[cur_arg]);
1205 rule->arg.http.str.len = strlen(rule->arg.http.str.ptr);
Christopher Faulet81e20172019-12-12 16:40:30 +01001206 cur_arg++;
1207 }
1208
Christopher Fauletc20b3712020-01-27 15:51:56 +01001209 LIST_INIT(&rule->arg.http.fmt);
Christopher Faulet81e20172019-12-12 16:40:30 +01001210 *orig_arg = cur_arg;
1211 return ACT_RET_PRS_OK;
1212}
1213
1214/* Parse a "set-nice" action. It takes the nice value as argument. It returns
1215 * ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
1216 */
1217static enum act_parse_ret parse_http_set_nice(const char **args, int *orig_arg, struct proxy *px,
1218 struct act_rule *rule, char **err)
1219{
1220 int cur_arg;
1221
1222 rule->action = ACT_HTTP_SET_NICE;
1223
1224 cur_arg = *orig_arg;
1225 if (!*args[cur_arg]) {
1226 memprintf(err, "expects exactly 1 argument (integer value)");
1227 return ACT_RET_PRS_ERR;
1228 }
Christopher Faulet96bff762019-12-17 13:46:18 +01001229 rule->arg.http.i = atoi(args[cur_arg]);
1230 if (rule->arg.http.i < -1024)
1231 rule->arg.http.i = -1024;
1232 else if (rule->arg.http.i > 1024)
1233 rule->arg.http.i = 1024;
Christopher Faulet81e20172019-12-12 16:40:30 +01001234
Christopher Fauletc20b3712020-01-27 15:51:56 +01001235 LIST_INIT(&rule->arg.http.fmt);
Christopher Faulet81e20172019-12-12 16:40:30 +01001236 *orig_arg = cur_arg + 1;
1237 return ACT_RET_PRS_OK;
1238}
1239
1240/* Parse a "set-tos" action. It takes the TOS value as argument. It returns
1241 * ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
1242 */
1243static enum act_parse_ret parse_http_set_tos(const char **args, int *orig_arg, struct proxy *px,
1244 struct act_rule *rule, char **err)
1245{
1246#ifdef IP_TOS
1247 char *endp;
1248 int cur_arg;
1249
1250 rule->action = ACT_HTTP_SET_TOS;
1251
1252 cur_arg = *orig_arg;
1253 if (!*args[cur_arg]) {
1254 memprintf(err, "expects exactly 1 argument (integer/hex value)");
1255 return ACT_RET_PRS_ERR;
1256 }
Christopher Faulet96bff762019-12-17 13:46:18 +01001257 rule->arg.http.i = strtol(args[cur_arg], &endp, 0);
Christopher Faulet81e20172019-12-12 16:40:30 +01001258 if (endp && *endp != '\0') {
1259 memprintf(err, "invalid character starting at '%s' (integer/hex value expected)", endp);
1260 return ACT_RET_PRS_ERR;
1261 }
1262
Christopher Fauletc20b3712020-01-27 15:51:56 +01001263 LIST_INIT(&rule->arg.http.fmt);
Christopher Faulet81e20172019-12-12 16:40:30 +01001264 *orig_arg = cur_arg + 1;
1265 return ACT_RET_PRS_OK;
1266#else
1267 memprintf(err, "not supported on this platform (IP_TOS undefined)");
1268 return ACT_RET_PRS_ERR;
1269#endif
1270}
1271
1272/* Parse a "set-mark" action. It takes the MARK value as argument. It returns
1273 * ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
1274 */
1275static enum act_parse_ret parse_http_set_mark(const char **args, int *orig_arg, struct proxy *px,
1276 struct act_rule *rule, char **err)
1277{
1278#ifdef SO_MARK
1279 char *endp;
1280 int cur_arg;
1281
1282 rule->action = ACT_HTTP_SET_MARK;
1283
1284 cur_arg = *orig_arg;
1285 if (!*args[cur_arg]) {
1286 memprintf(err, "expects exactly 1 argument (integer/hex value)");
1287 return ACT_RET_PRS_ERR;
1288 }
Christopher Faulet96bff762019-12-17 13:46:18 +01001289 rule->arg.http.i = strtoul(args[cur_arg], &endp, 0);
Christopher Faulet81e20172019-12-12 16:40:30 +01001290 if (endp && *endp != '\0') {
1291 memprintf(err, "invalid character starting at '%s' (integer/hex value expected)", endp);
1292 return ACT_RET_PRS_ERR;
1293 }
1294
Christopher Fauletc20b3712020-01-27 15:51:56 +01001295 LIST_INIT(&rule->arg.http.fmt);
Christopher Faulet81e20172019-12-12 16:40:30 +01001296 *orig_arg = cur_arg + 1;
1297 global.last_checks |= LSTCHK_NETADM;
1298 return ACT_RET_PRS_OK;
1299#else
1300 memprintf(err, "not supported on this platform (SO_MARK undefined)");
1301 return ACT_RET_PRS_ERR;
1302#endif
1303}
1304
1305/* Parse a "set-log-level" action. It takes the level value as argument. It
1306 * returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
1307 */
1308static enum act_parse_ret parse_http_set_log_level(const char **args, int *orig_arg, struct proxy *px,
1309 struct act_rule *rule, char **err)
1310{
1311 int cur_arg;
1312
1313 rule->action = ACT_HTTP_SET_LOGL;
1314
1315 cur_arg = *orig_arg;
1316 if (!*args[cur_arg]) {
1317 bad_log_level:
1318 memprintf(err, "expects exactly 1 argument (log level name or 'silent')");
1319 return ACT_RET_PRS_ERR;
1320 }
1321 if (strcmp(args[cur_arg], "silent") == 0)
Christopher Faulet96bff762019-12-17 13:46:18 +01001322 rule->arg.http.i = -1;
1323 else if ((rule->arg.http.i = get_log_level(args[cur_arg]) + 1) == 0)
Christopher Faulet81e20172019-12-12 16:40:30 +01001324 goto bad_log_level;
1325
Christopher Fauletc20b3712020-01-27 15:51:56 +01001326 LIST_INIT(&rule->arg.http.fmt);
Christopher Faulet81e20172019-12-12 16:40:30 +01001327 *orig_arg = cur_arg + 1;
1328 return ACT_RET_PRS_OK;
1329}
1330
Christopher Faulet91b3ec12020-01-17 22:30:06 +01001331/* This function executes a early-hint action. It adds an HTTP Early Hint HTTP
1332 * 103 response header with <.arg.http.str> name and with a value built
1333 * according to <.arg.http.fmt> log line format. If it is the first early-hint
Ilya Shipitsinc02a23f2020-05-06 00:53:22 +05001334 * rule of series, the 103 response start-line is added first. At the end, if
Christopher Faulet91b3ec12020-01-17 22:30:06 +01001335 * the next rule is not an early-hint rule or if it is the last rule, the EOH
1336 * block is added to terminate the response. On success, it returns
1337 * ACT_RET_CONT. If an error occurs while soft rewrites are enabled, the action
1338 * is canceled, but the rule processing continue. Otherwsize ACT_RET_ERR is
1339 * returned.
1340 */
1341static enum act_return http_action_early_hint(struct act_rule *rule, struct proxy *px,
1342 struct session *sess, struct stream *s, int flags)
1343{
1344 struct act_rule *prev_rule, *next_rule;
1345 struct channel *res = &s->res;
1346 struct htx *htx = htx_from_buf(&res->buf);
1347 struct buffer *value = alloc_trash_chunk();
1348 enum act_return ret = ACT_RET_CONT;
1349
1350 if (!(s->txn->req.flags & HTTP_MSGF_VER_11))
1351 goto leave;
1352
1353 if (!value) {
1354 if (!(s->flags & SF_ERR_MASK))
1355 s->flags |= SF_ERR_RESOURCE;
1356 goto error;
1357 }
1358
1359 /* get previous and next rules */
1360 prev_rule = LIST_PREV(&rule->list, typeof(rule), list);
1361 next_rule = LIST_NEXT(&rule->list, typeof(rule), list);
1362
1363 /* if no previous rule or previous rule is not early-hint, start a new response. Otherwise,
1364 * continue to add link to a previously started response */
1365 if (&prev_rule->list == s->current_rule_list || prev_rule->action_ptr != http_action_early_hint) {
1366 struct htx_sl *sl;
1367 unsigned int flags = (HTX_SL_F_IS_RESP|HTX_SL_F_VER_11|
1368 HTX_SL_F_XFER_LEN|HTX_SL_F_BODYLESS);
1369
1370 sl = htx_add_stline(htx, HTX_BLK_RES_SL, flags,
1371 ist("HTTP/1.1"), ist("103"), ist("Early Hints"));
1372 if (!sl)
1373 goto error;
1374 sl->info.res.status = 103;
1375 }
1376
1377 /* Add the HTTP Early Hint HTTP 103 response heade */
1378 value->data = build_logline(s, b_tail(value), b_room(value), &rule->arg.http.fmt);
1379 if (!htx_add_header(htx, rule->arg.http.str, ist2(b_head(value), b_data(value))))
1380 goto error;
1381
1382 /* if it is the last rule or the next one is not an early-hint, terminate the current
1383 * response. */
1384 if (&next_rule->list == s->current_rule_list || next_rule->action_ptr != http_action_early_hint) {
Christopher Faulet91b3ec12020-01-17 22:30:06 +01001385 if (!htx_add_endof(htx, HTX_BLK_EOH)) {
1386 /* If an error occurred during an Early-hint rule,
1387 * remove the incomplete HTTP 103 response from the
1388 * buffer */
1389 goto error;
1390 }
1391
Christopher Fauleta72a7e42020-01-28 09:28:11 +01001392 if (!http_forward_proxy_resp(s, 0))
1393 goto error;
Christopher Faulet91b3ec12020-01-17 22:30:06 +01001394 }
1395
1396 leave:
1397 free_trash_chunk(value);
1398 return ret;
1399
1400 error:
1401 /* If an error occurred during an Early-hint rule, remove the incomplete
1402 * HTTP 103 response from the buffer */
1403 channel_htx_truncate(res, htx);
1404 ret = ACT_RET_ERR;
1405 goto leave;
1406}
1407
Christopher Fauletd1f27e32019-12-17 09:33:38 +01001408/* This function executes a set-header or add-header actions. It builds a string
1409 * in the trash from the specified format string. It finds the action to be
1410 * performed in <.action>, previously filled by function parse_set_header(). The
Ilya Shipitsinc02a23f2020-05-06 00:53:22 +05001411 * replacement action is executed by the function http_action_set_header(). On
Christopher Fauletd1f27e32019-12-17 09:33:38 +01001412 * success, it returns ACT_RET_CONT. If an error occurs while soft rewrites are
1413 * enabled, the action is canceled, but the rule processing continue. Otherwsize
1414 * ACT_RET_ERR is returned.
1415 */
1416static enum act_return http_action_set_header(struct act_rule *rule, struct proxy *px,
1417 struct session *sess, struct stream *s, int flags)
1418{
Christopher Faulet91e31d82020-01-24 15:37:13 +01001419 struct http_msg *msg = ((rule->from == ACT_F_HTTP_REQ) ? &s->txn->req : &s->txn->rsp);
1420 struct htx *htx = htxbuf(&msg->chn->buf);
Christopher Fauletd1f27e32019-12-17 09:33:38 +01001421 enum act_return ret = ACT_RET_CONT;
1422 struct buffer *replace;
1423 struct http_hdr_ctx ctx;
1424 struct ist n, v;
1425
1426 replace = alloc_trash_chunk();
1427 if (!replace)
1428 goto fail_alloc;
1429
1430 replace->data = build_logline(s, replace->area, replace->size, &rule->arg.http.fmt);
1431 n = rule->arg.http.str;
1432 v = ist2(replace->area, replace->data);
1433
1434 if (rule->action == 0) { // set-header
1435 /* remove all occurrences of the header */
1436 ctx.blk = NULL;
1437 while (http_find_header(htx, n, &ctx, 1))
1438 http_remove_header(htx, &ctx);
1439 }
1440
1441 /* Now add header */
1442 if (!http_add_header(htx, n, v))
1443 goto fail_rewrite;
1444
1445 leave:
1446 free_trash_chunk(replace);
1447 return ret;
1448
1449 fail_alloc:
1450 if (!(s->flags & SF_ERR_MASK))
1451 s->flags |= SF_ERR_RESOURCE;
1452 ret = ACT_RET_ERR;
1453 goto leave;
1454
1455 fail_rewrite:
Willy Tarreau4781b152021-04-06 13:53:36 +02001456 _HA_ATOMIC_INC(&sess->fe->fe_counters.failed_rewrites);
Christopher Fauletd1f27e32019-12-17 09:33:38 +01001457 if (s->flags & SF_BE_ASSIGNED)
Willy Tarreau4781b152021-04-06 13:53:36 +02001458 _HA_ATOMIC_INC(&s->be->be_counters.failed_rewrites);
William Lallemand36119de2021-03-08 15:26:48 +01001459 if (sess->listener && sess->listener->counters)
Willy Tarreau4781b152021-04-06 13:53:36 +02001460 _HA_ATOMIC_INC(&sess->listener->counters->failed_rewrites);
Christopher Fauletd1f27e32019-12-17 09:33:38 +01001461 if (objt_server(s->target))
Willy Tarreau4781b152021-04-06 13:53:36 +02001462 _HA_ATOMIC_INC(&__objt_server(s->target)->counters.failed_rewrites);
Christopher Fauletd1f27e32019-12-17 09:33:38 +01001463
Christopher Faulet333bf8c2020-01-22 14:38:05 +01001464 if (!(msg->flags & HTTP_MSGF_SOFT_RW)) {
Christopher Fauletd1f27e32019-12-17 09:33:38 +01001465 ret = ACT_RET_ERR;
Christopher Faulet333bf8c2020-01-22 14:38:05 +01001466 if (!(s->flags & SF_ERR_MASK))
1467 s->flags |= SF_ERR_PRXCOND;
1468 }
Christopher Fauletd1f27e32019-12-17 09:33:38 +01001469 goto leave;
1470}
1471
Christopher Faulet81e20172019-12-12 16:40:30 +01001472/* Parse a "set-header", "add-header" or "early-hint" actions. It takes an
1473 * header name and a log-format string as arguments. It returns ACT_RET_PRS_OK
1474 * on success, ACT_RET_PRS_ERR on error.
1475 *
1476 * Note: same function is used for the request and the response. However
1477 * "early-hint" rules are only supported for request rules.
1478 */
1479static enum act_parse_ret parse_http_set_header(const char **args, int *orig_arg, struct proxy *px,
1480 struct act_rule *rule, char **err)
1481{
Christopher Faulet81e20172019-12-12 16:40:30 +01001482 int cap, cur_arg;
1483
Christopher Faulet91b3ec12020-01-17 22:30:06 +01001484 if (args[*orig_arg-1][0] == 'e') {
1485 rule->action = ACT_CUSTOM;
1486 rule->action_ptr = http_action_early_hint;
1487 }
Christopher Fauletd1f27e32019-12-17 09:33:38 +01001488 else {
1489 if (args[*orig_arg-1][0] == 's')
1490 rule->action = 0; // set-header
1491 else
1492 rule->action = 1; // add-header
1493 rule->action_ptr = http_action_set_header;
1494 }
Christopher Faulet2eb53962020-01-14 14:47:34 +01001495 rule->release_ptr = release_http_action;
Christopher Faulet81e20172019-12-12 16:40:30 +01001496
1497 cur_arg = *orig_arg;
1498 if (!*args[cur_arg] || !*args[cur_arg+1]) {
1499 memprintf(err, "expects exactly 2 arguments");
1500 return ACT_RET_PRS_ERR;
1501 }
1502
Christopher Faulet81e20172019-12-12 16:40:30 +01001503
Christopher Faulet96bff762019-12-17 13:46:18 +01001504 rule->arg.http.str.ptr = strdup(args[cur_arg]);
1505 rule->arg.http.str.len = strlen(rule->arg.http.str.ptr);
1506 LIST_INIT(&rule->arg.http.fmt);
Christopher Faulet81e20172019-12-12 16:40:30 +01001507
1508 if (rule->from == ACT_F_HTTP_REQ) {
1509 px->conf.args.ctx = ARGC_HRQ;
1510 cap = (px->cap & PR_CAP_FE) ? SMP_VAL_FE_HRQ_HDR : SMP_VAL_BE_HRQ_HDR;
1511 }
1512 else{
1513 px->conf.args.ctx = ARGC_HRS;
1514 cap = (px->cap & PR_CAP_BE) ? SMP_VAL_BE_HRS_HDR : SMP_VAL_FE_HRS_HDR;
1515 }
1516
1517 cur_arg++;
Christopher Faulet1337b322020-01-14 14:50:55 +01001518 if (!parse_logformat_string(args[cur_arg], px, &rule->arg.http.fmt, LOG_OPT_HTTP, cap, err)) {
Tim Duesterhused526372020-03-05 17:56:33 +01001519 istfree(&rule->arg.http.str);
Christopher Faulet81e20172019-12-12 16:40:30 +01001520 return ACT_RET_PRS_ERR;
Christopher Faulet1337b322020-01-14 14:50:55 +01001521 }
Christopher Faulet81e20172019-12-12 16:40:30 +01001522
1523 free(px->conf.lfs_file);
1524 px->conf.lfs_file = strdup(px->conf.args.file);
1525 px->conf.lfs_line = px->conf.args.line;
1526
1527 *orig_arg = cur_arg + 1;
1528 return ACT_RET_PRS_OK;
1529}
1530
Christopher Faulet92d34fe2019-12-17 09:20:34 +01001531/* This function executes a replace-header or replace-value actions. It
1532 * builds a string in the trash from the specified format string. It finds
1533 * the action to be performed in <.action>, previously filled by function
Ilya Shipitsinc02a23f2020-05-06 00:53:22 +05001534 * parse_replace_header(). The replacement action is executed by the function
Christopher Faulet92d34fe2019-12-17 09:20:34 +01001535 * http_action_replace_header(). On success, it returns ACT_RET_CONT. If an error
1536 * occurs while soft rewrites are enabled, the action is canceled, but the rule
1537 * processing continue. Otherwsize ACT_RET_ERR is returned.
1538 */
1539static enum act_return http_action_replace_header(struct act_rule *rule, struct proxy *px,
1540 struct session *sess, struct stream *s, int flags)
1541{
Christopher Faulet91e31d82020-01-24 15:37:13 +01001542 struct http_msg *msg = ((rule->from == ACT_F_HTTP_REQ) ? &s->txn->req : &s->txn->rsp);
1543 struct htx *htx = htxbuf(&msg->chn->buf);
Christopher Faulet92d34fe2019-12-17 09:20:34 +01001544 enum act_return ret = ACT_RET_CONT;
1545 struct buffer *replace;
1546 int r;
1547
1548 replace = alloc_trash_chunk();
1549 if (!replace)
1550 goto fail_alloc;
1551
1552 replace->data = build_logline(s, replace->area, replace->size, &rule->arg.http.fmt);
1553
1554 r = http_replace_hdrs(s, htx, rule->arg.http.str, replace->area, rule->arg.http.re, (rule->action == 0));
1555 if (r == -1)
1556 goto fail_rewrite;
1557
1558 leave:
1559 free_trash_chunk(replace);
1560 return ret;
1561
1562 fail_alloc:
1563 if (!(s->flags & SF_ERR_MASK))
1564 s->flags |= SF_ERR_RESOURCE;
1565 ret = ACT_RET_ERR;
1566 goto leave;
1567
1568 fail_rewrite:
Willy Tarreau4781b152021-04-06 13:53:36 +02001569 _HA_ATOMIC_INC(&sess->fe->fe_counters.failed_rewrites);
Christopher Faulet92d34fe2019-12-17 09:20:34 +01001570 if (s->flags & SF_BE_ASSIGNED)
Willy Tarreau4781b152021-04-06 13:53:36 +02001571 _HA_ATOMIC_INC(&s->be->be_counters.failed_rewrites);
William Lallemand36119de2021-03-08 15:26:48 +01001572 if (sess->listener && sess->listener->counters)
Willy Tarreau4781b152021-04-06 13:53:36 +02001573 _HA_ATOMIC_INC(&sess->listener->counters->failed_rewrites);
Christopher Faulet92d34fe2019-12-17 09:20:34 +01001574 if (objt_server(s->target))
Willy Tarreau4781b152021-04-06 13:53:36 +02001575 _HA_ATOMIC_INC(&__objt_server(s->target)->counters.failed_rewrites);
Christopher Faulet92d34fe2019-12-17 09:20:34 +01001576
Christopher Faulet333bf8c2020-01-22 14:38:05 +01001577 if (!(msg->flags & HTTP_MSGF_SOFT_RW)) {
Christopher Faulet92d34fe2019-12-17 09:20:34 +01001578 ret = ACT_RET_ERR;
Christopher Faulet333bf8c2020-01-22 14:38:05 +01001579 if (!(s->flags & SF_ERR_MASK))
1580 s->flags |= SF_ERR_PRXCOND;
1581 }
Christopher Faulet92d34fe2019-12-17 09:20:34 +01001582 goto leave;
1583}
1584
Christopher Faulet81e20172019-12-12 16:40:30 +01001585/* Parse a "replace-header" or "replace-value" actions. It takes an header name,
1586 * a regex and replacement string as arguments. It returns ACT_RET_PRS_OK on
1587 * success, ACT_RET_PRS_ERR on error.
1588 */
1589static enum act_parse_ret parse_http_replace_header(const char **args, int *orig_arg, struct proxy *px,
1590 struct act_rule *rule, char **err)
1591{
1592 int cap, cur_arg;
1593
Christopher Faulet92d34fe2019-12-17 09:20:34 +01001594 if (args[*orig_arg-1][8] == 'h')
1595 rule->action = 0; // replace-header
1596 else
1597 rule->action = 1; // replace-value
1598 rule->action_ptr = http_action_replace_header;
Christopher Faulet2eb53962020-01-14 14:47:34 +01001599 rule->release_ptr = release_http_action;
Christopher Faulet81e20172019-12-12 16:40:30 +01001600
1601 cur_arg = *orig_arg;
1602 if (!*args[cur_arg] || !*args[cur_arg+1] || !*args[cur_arg+2]) {
1603 memprintf(err, "expects exactly 3 arguments");
1604 return ACT_RET_PRS_ERR;
1605 }
1606
Christopher Faulet96bff762019-12-17 13:46:18 +01001607 rule->arg.http.str.ptr = strdup(args[cur_arg]);
1608 rule->arg.http.str.len = strlen(rule->arg.http.str.ptr);
1609 LIST_INIT(&rule->arg.http.fmt);
Christopher Faulet81e20172019-12-12 16:40:30 +01001610
1611 cur_arg++;
Christopher Faulet1337b322020-01-14 14:50:55 +01001612 if (!(rule->arg.http.re = regex_comp(args[cur_arg], 1, 1, err))) {
Tim Duesterhused526372020-03-05 17:56:33 +01001613 istfree(&rule->arg.http.str);
Christopher Faulet81e20172019-12-12 16:40:30 +01001614 return ACT_RET_PRS_ERR;
Christopher Faulet1337b322020-01-14 14:50:55 +01001615 }
Christopher Faulet81e20172019-12-12 16:40:30 +01001616
1617 if (rule->from == ACT_F_HTTP_REQ) {
1618 px->conf.args.ctx = ARGC_HRQ;
1619 cap = (px->cap & PR_CAP_FE) ? SMP_VAL_FE_HRQ_HDR : SMP_VAL_BE_HRQ_HDR;
1620 }
1621 else{
1622 px->conf.args.ctx = ARGC_HRS;
1623 cap = (px->cap & PR_CAP_BE) ? SMP_VAL_BE_HRS_HDR : SMP_VAL_FE_HRS_HDR;
1624 }
1625
1626 cur_arg++;
Christopher Faulet1337b322020-01-14 14:50:55 +01001627 if (!parse_logformat_string(args[cur_arg], px, &rule->arg.http.fmt, LOG_OPT_HTTP, cap, err)) {
Tim Duesterhused526372020-03-05 17:56:33 +01001628 istfree(&rule->arg.http.str);
Christopher Faulet1337b322020-01-14 14:50:55 +01001629 regex_free(rule->arg.http.re);
Christopher Faulet81e20172019-12-12 16:40:30 +01001630 return ACT_RET_PRS_ERR;
Christopher Faulet1337b322020-01-14 14:50:55 +01001631 }
Christopher Faulet81e20172019-12-12 16:40:30 +01001632
1633 free(px->conf.lfs_file);
1634 px->conf.lfs_file = strdup(px->conf.args.file);
1635 px->conf.lfs_line = px->conf.args.line;
1636
1637 *orig_arg = cur_arg + 1;
1638 return ACT_RET_PRS_OK;
1639}
1640
Maciej Zdebebdd4c52020-11-20 13:58:48 +00001641/* This function executes a del-header action with selected matching mode for
1642 * header name. It finds the matching method to be performed in <.action>, previously
1643 * filled by function parse_http_del_header(). On success, it returns ACT_RET_CONT.
1644 * Otherwise ACT_RET_ERR is returned.
1645 */
1646static enum act_return http_action_del_header(struct act_rule *rule, struct proxy *px,
1647 struct session *sess, struct stream *s, int flags)
1648{
1649 struct http_hdr_ctx ctx;
1650 struct http_msg *msg = ((rule->from == ACT_F_HTTP_REQ) ? &s->txn->req : &s->txn->rsp);
1651 struct htx *htx = htxbuf(&msg->chn->buf);
1652 enum act_return ret = ACT_RET_CONT;
1653
1654 /* remove all occurrences of the header */
1655 ctx.blk = NULL;
1656 switch (rule->action) {
1657 case PAT_MATCH_STR:
1658 while (http_find_header(htx, rule->arg.http.str, &ctx, 1))
1659 http_remove_header(htx, &ctx);
1660 break;
1661 case PAT_MATCH_BEG:
1662 while (http_find_pfx_header(htx, rule->arg.http.str, &ctx, 1))
1663 http_remove_header(htx, &ctx);
1664 break;
1665 case PAT_MATCH_END:
1666 while (http_find_sfx_header(htx, rule->arg.http.str, &ctx, 1))
1667 http_remove_header(htx, &ctx);
1668 break;
1669 case PAT_MATCH_SUB:
1670 while (http_find_sub_header(htx, rule->arg.http.str, &ctx, 1))
1671 http_remove_header(htx, &ctx);
1672 break;
1673 case PAT_MATCH_REG:
1674 while (http_match_header(htx, rule->arg.http.re, &ctx, 1))
1675 http_remove_header(htx, &ctx);
1676 break;
1677 default:
1678 return ACT_RET_ERR;
1679 }
1680 return ret;
1681}
1682
1683/* Parse a "del-header" action. It takes string as a required argument,
1684 * optional flag (currently only -m) and optional matching method of input string
1685 * with header name to be deleted. Default matching method is exact match (-m str).
1686 * It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
Christopher Faulet81e20172019-12-12 16:40:30 +01001687 */
1688static enum act_parse_ret parse_http_del_header(const char **args, int *orig_arg, struct proxy *px,
1689 struct act_rule *rule, char **err)
1690{
1691 int cur_arg;
Maciej Zdebebdd4c52020-11-20 13:58:48 +00001692 int pat_idx;
Christopher Faulet81e20172019-12-12 16:40:30 +01001693
Maciej Zdebebdd4c52020-11-20 13:58:48 +00001694 /* set exact matching (-m str) as default */
1695 rule->action = PAT_MATCH_STR;
1696 rule->action_ptr = http_action_del_header;
Christopher Faulet2eb53962020-01-14 14:47:34 +01001697 rule->release_ptr = release_http_action;
Christopher Faulet81e20172019-12-12 16:40:30 +01001698
1699 cur_arg = *orig_arg;
1700 if (!*args[cur_arg]) {
Maciej Zdebebdd4c52020-11-20 13:58:48 +00001701 memprintf(err, "expects at least 1 argument");
Christopher Faulet81e20172019-12-12 16:40:30 +01001702 return ACT_RET_PRS_ERR;
1703 }
1704
Christopher Faulet96bff762019-12-17 13:46:18 +01001705 rule->arg.http.str.ptr = strdup(args[cur_arg]);
1706 rule->arg.http.str.len = strlen(rule->arg.http.str.ptr);
Christopher Faulet81e20172019-12-12 16:40:30 +01001707 px->conf.args.ctx = (rule->from == ACT_F_HTTP_REQ ? ARGC_HRQ : ARGC_HRS);
1708
Maciej Zdeb6dee9962020-11-23 16:03:09 +00001709 LIST_INIT(&rule->arg.http.fmt);
Maciej Zdebebdd4c52020-11-20 13:58:48 +00001710 if (strcmp(args[cur_arg+1], "-m") == 0) {
1711 cur_arg++;
1712 if (!*args[cur_arg+1]) {
1713 memprintf(err, "-m flag expects exactly 1 argument");
1714 return ACT_RET_PRS_ERR;
1715 }
1716
1717 cur_arg++;
1718 pat_idx = pat_find_match_name(args[cur_arg]);
1719 switch (pat_idx) {
1720 case PAT_MATCH_REG:
1721 if (!(rule->arg.http.re = regex_comp(rule->arg.http.str.ptr, 1, 1, err)))
1722 return ACT_RET_PRS_ERR;
1723 /* fall through */
1724 case PAT_MATCH_STR:
1725 case PAT_MATCH_BEG:
1726 case PAT_MATCH_END:
1727 case PAT_MATCH_SUB:
1728 rule->action = pat_idx;
1729 break;
1730 default:
1731 memprintf(err, "-m with unsupported matching method '%s'", args[cur_arg]);
1732 return ACT_RET_PRS_ERR;
1733 }
1734 }
1735
Christopher Faulet81e20172019-12-12 16:40:30 +01001736 *orig_arg = cur_arg + 1;
1737 return ACT_RET_PRS_OK;
1738}
1739
Christopher Faulet2eb53962020-01-14 14:47:34 +01001740/* Release memory allocated by an http redirect action. */
1741static void release_http_redir(struct act_rule *rule)
1742{
1743 struct logformat_node *lf, *lfb;
1744 struct redirect_rule *redir;
1745
1746 redir = rule->arg.redir;
Willy Tarreau2b718102021-04-21 07:32:39 +02001747 LIST_DELETE(&redir->list);
Christopher Faulet2eb53962020-01-14 14:47:34 +01001748 if (redir->cond) {
1749 prune_acl_cond(redir->cond);
1750 free(redir->cond);
1751 }
1752 free(redir->rdr_str);
1753 free(redir->cookie_str);
1754 list_for_each_entry_safe(lf, lfb, &redir->rdr_fmt, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02001755 LIST_DELETE(&lf->list);
Christopher Faulet2eb53962020-01-14 14:47:34 +01001756 free(lf);
1757 }
1758 free(redir);
1759}
1760
Christopher Faulet81e20172019-12-12 16:40:30 +01001761/* Parse a "redirect" action. It returns ACT_RET_PRS_OK on success,
1762 * ACT_RET_PRS_ERR on error.
1763 */
1764static enum act_parse_ret parse_http_redirect(const char **args, int *orig_arg, struct proxy *px,
1765 struct act_rule *rule, char **err)
1766{
1767 struct redirect_rule *redir;
1768 int dir, cur_arg;
1769
1770 rule->action = ACT_HTTP_REDIR;
Christopher Faulet245cf792019-12-18 14:58:12 +01001771 rule->flags |= ACT_FLAG_FINAL;
Christopher Faulet2eb53962020-01-14 14:47:34 +01001772 rule->release_ptr = release_http_redir;
Christopher Faulet81e20172019-12-12 16:40:30 +01001773
1774 cur_arg = *orig_arg;
1775
1776 dir = (rule->from == ACT_F_HTTP_REQ ? 0 : 1);
1777 if ((redir = http_parse_redirect_rule(px->conf.args.file, px->conf.args.line, px, &args[cur_arg], err, 1, dir)) == NULL)
1778 return ACT_RET_PRS_ERR;
1779
1780 rule->arg.redir = redir;
1781 rule->cond = redir->cond;
1782 redir->cond = NULL;
1783
1784 /* skip all arguments */
1785 while (*args[cur_arg])
1786 cur_arg++;
1787
1788 *orig_arg = cur_arg;
1789 return ACT_RET_PRS_OK;
1790}
1791
Christopher Faulet046cf442019-12-17 15:45:23 +01001792/* This function executes a add-acl, del-acl, set-map or del-map actions. On
1793 * success, it returns ACT_RET_CONT. Otherwsize ACT_RET_ERR is returned.
1794 */
1795static enum act_return http_action_set_map(struct act_rule *rule, struct proxy *px,
1796 struct session *sess, struct stream *s, int flags)
1797{
1798 struct pat_ref *ref;
1799 struct buffer *key = NULL, *value = NULL;
1800 enum act_return ret = ACT_RET_CONT;
1801
1802 /* collect reference */
1803 ref = pat_ref_lookup(rule->arg.map.ref);
1804 if (!ref)
1805 goto leave;
1806
1807 /* allocate key */
1808 key = alloc_trash_chunk();
1809 if (!key)
1810 goto fail_alloc;
1811
1812 /* collect key */
1813 key->data = build_logline(s, key->area, key->size, &rule->arg.map.key);
1814 key->area[key->data] = '\0';
1815
1816 switch (rule->action) {
1817 case 0: // add-acl
1818 /* add entry only if it does not already exist */
1819 HA_SPIN_LOCK(PATREF_LOCK, &ref->lock);
1820 if (pat_ref_find_elt(ref, key->area) == NULL)
1821 pat_ref_add(ref, key->area, NULL, NULL);
1822 HA_SPIN_UNLOCK(PATREF_LOCK, &ref->lock);
1823 break;
1824
1825 case 1: // set-map
1826 /* allocate value */
1827 value = alloc_trash_chunk();
1828 if (!value)
1829 goto fail_alloc;
1830
1831 /* collect value */
1832 value->data = build_logline(s, value->area, value->size, &rule->arg.map.value);
1833 value->area[value->data] = '\0';
1834
1835 HA_SPIN_LOCK(PATREF_LOCK, &ref->lock);
1836 if (pat_ref_find_elt(ref, key->area) != NULL) {
1837 /* update entry if it exists */
1838 pat_ref_set(ref, key->area, value->area, NULL);
1839 }
1840 else {
1841 /* insert a new entry */
1842 pat_ref_add(ref, key->area, value->area, NULL);
1843 }
1844 HA_SPIN_UNLOCK(PATREF_LOCK, &ref->lock);
1845 break;
1846
1847 case 2: // del-acl
1848 case 3: // del-map
1849 /* returned code: 1=ok, 0=ko */
1850 HA_SPIN_LOCK(PATREF_LOCK, &ref->lock);
1851 pat_ref_delete(ref, key->area);
1852 HA_SPIN_UNLOCK(PATREF_LOCK, &ref->lock);
1853 break;
1854
1855 default:
1856 ret = ACT_RET_ERR;
1857 }
1858
1859
1860 leave:
1861 free_trash_chunk(key);
1862 free_trash_chunk(value);
1863 return ret;
1864
1865 fail_alloc:
1866 if (!(s->flags & SF_ERR_MASK))
1867 s->flags |= SF_ERR_RESOURCE;
1868 ret = ACT_RET_ERR;
1869 goto leave;
1870}
1871
Christopher Faulet2eb53962020-01-14 14:47:34 +01001872/* Release memory allocated by an http map/acl action. */
1873static void release_http_map(struct act_rule *rule)
1874{
1875 struct logformat_node *lf, *lfb;
1876
1877 free(rule->arg.map.ref);
1878 list_for_each_entry_safe(lf, lfb, &rule->arg.map.key, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02001879 LIST_DELETE(&lf->list);
Christopher Faulet2eb53962020-01-14 14:47:34 +01001880 release_sample_expr(lf->expr);
1881 free(lf->arg);
1882 free(lf);
1883 }
1884 if (rule->action == 1) {
1885 list_for_each_entry_safe(lf, lfb, &rule->arg.map.value, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02001886 LIST_DELETE(&lf->list);
Christopher Faulet2eb53962020-01-14 14:47:34 +01001887 release_sample_expr(lf->expr);
1888 free(lf->arg);
1889 free(lf);
1890 }
1891 }
1892}
1893
Christopher Faulet81e20172019-12-12 16:40:30 +01001894/* Parse a "add-acl", "del-acl", "set-map" or "del-map" actions. It takes one or
Christopher Faulet046cf442019-12-17 15:45:23 +01001895 * two log-format string as argument depending on the action. The action is
1896 * stored in <.action> as an int (0=add-acl, 1=set-map, 2=del-acl,
1897 * 3=del-map). It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
Christopher Faulet81e20172019-12-12 16:40:30 +01001898 */
1899static enum act_parse_ret parse_http_set_map(const char **args, int *orig_arg, struct proxy *px,
1900 struct act_rule *rule, char **err)
1901{
1902 int cap, cur_arg;
1903
Christopher Faulet046cf442019-12-17 15:45:23 +01001904 if (args[*orig_arg-1][0] == 'a') // add-acl
1905 rule->action = 0;
1906 else if (args[*orig_arg-1][0] == 's') // set-map
1907 rule->action = 1;
1908 else if (args[*orig_arg-1][4] == 'a') // del-acl
1909 rule->action = 2;
1910 else if (args[*orig_arg-1][4] == 'm') // del-map
1911 rule->action = 3;
1912 else {
1913 memprintf(err, "internal error: unhandled action '%s'", args[0]);
1914 return ACT_RET_PRS_ERR;
1915 }
1916 rule->action_ptr = http_action_set_map;
Christopher Faulet2eb53962020-01-14 14:47:34 +01001917 rule->release_ptr = release_http_map;
Christopher Faulet81e20172019-12-12 16:40:30 +01001918
1919 cur_arg = *orig_arg;
Christopher Faulet046cf442019-12-17 15:45:23 +01001920 if (rule->action == 1 && (!*args[cur_arg] || !*args[cur_arg+1])) {
1921 /* 2 args for set-map */
Christopher Faulet81e20172019-12-12 16:40:30 +01001922 memprintf(err, "expects exactly 2 arguments");
1923 return ACT_RET_PRS_ERR;
1924 }
1925 else if (!*args[cur_arg]) {
Christopher Faulet046cf442019-12-17 15:45:23 +01001926 /* only one arg for other actions */
Christopher Faulet81e20172019-12-12 16:40:30 +01001927 memprintf(err, "expects exactly 1 arguments");
1928 return ACT_RET_PRS_ERR;
1929 }
1930
1931 /*
1932 * '+ 8' for 'set-map(' (same for del-map)
1933 * '- 9' for 'set-map(' + trailing ')' (same for del-map)
1934 */
1935 rule->arg.map.ref = my_strndup(args[cur_arg-1] + 8, strlen(args[cur_arg-1]) - 9);
1936
1937 if (rule->from == ACT_F_HTTP_REQ) {
1938 px->conf.args.ctx = ARGC_HRQ;
1939 cap = (px->cap & PR_CAP_FE) ? SMP_VAL_FE_HRQ_HDR : SMP_VAL_BE_HRQ_HDR;
1940 }
1941 else{
1942 px->conf.args.ctx = ARGC_HRS;
1943 cap = (px->cap & PR_CAP_BE) ? SMP_VAL_BE_HRS_HDR : SMP_VAL_FE_HRS_HDR;
1944 }
1945
1946 /* key pattern */
1947 LIST_INIT(&rule->arg.map.key);
Christopher Faulet1337b322020-01-14 14:50:55 +01001948 if (!parse_logformat_string(args[cur_arg], px, &rule->arg.map.key, LOG_OPT_HTTP, cap, err)) {
1949 free(rule->arg.map.ref);
Christopher Faulet81e20172019-12-12 16:40:30 +01001950 return ACT_RET_PRS_ERR;
Christopher Faulet1337b322020-01-14 14:50:55 +01001951 }
Christopher Faulet81e20172019-12-12 16:40:30 +01001952
Christopher Faulet046cf442019-12-17 15:45:23 +01001953 if (rule->action == 1) {
Christopher Faulet81e20172019-12-12 16:40:30 +01001954 /* value pattern for set-map only */
1955 cur_arg++;
1956 LIST_INIT(&rule->arg.map.value);
Christopher Faulet1337b322020-01-14 14:50:55 +01001957 if (!parse_logformat_string(args[cur_arg], px, &rule->arg.map.value, LOG_OPT_HTTP, cap, err)) {
1958 free(rule->arg.map.ref);
Christopher Faulet81e20172019-12-12 16:40:30 +01001959 return ACT_RET_PRS_ERR;
Christopher Faulet1337b322020-01-14 14:50:55 +01001960 }
Christopher Faulet81e20172019-12-12 16:40:30 +01001961 }
1962
1963 free(px->conf.lfs_file);
1964 px->conf.lfs_file = strdup(px->conf.args.file);
1965 px->conf.lfs_line = px->conf.args.line;
1966
1967 *orig_arg = cur_arg + 1;
1968 return ACT_RET_PRS_OK;
1969}
1970
Christopher Fauletac98d812019-12-18 09:20:16 +01001971/* This function executes a track-sc* actions. On success, it returns
1972 * ACT_RET_CONT. Otherwsize ACT_RET_ERR is returned.
1973 */
1974static enum act_return http_action_track_sc(struct act_rule *rule, struct proxy *px,
1975 struct session *sess, struct stream *s, int flags)
1976{
1977 struct stktable *t;
1978 struct stksess *ts;
1979 struct stktable_key *key;
Willy Tarreau826f3ab2021-02-10 12:07:15 +01001980 void *ptr1, *ptr2, *ptr3, *ptr4, *ptr5, *ptr6;
Christopher Fauletac98d812019-12-18 09:20:16 +01001981 int opt;
1982
Willy Tarreau826f3ab2021-02-10 12:07:15 +01001983 ptr1 = ptr2 = ptr3 = ptr4 = ptr5 = ptr6 = NULL;
Christopher Fauletac98d812019-12-18 09:20:16 +01001984 opt = ((rule->from == ACT_F_HTTP_REQ) ? SMP_OPT_DIR_REQ : SMP_OPT_DIR_RES) | SMP_OPT_FINAL;
1985
1986 t = rule->arg.trk_ctr.table.t;
Emeric Brun362d25e2021-03-10 16:58:03 +01001987
1988 if (stkctr_entry(&s->stkctr[rule->action]))
1989 goto end;
1990
Christopher Fauletac98d812019-12-18 09:20:16 +01001991 key = stktable_fetch_key(t, s->be, sess, s, opt, rule->arg.trk_ctr.expr, NULL);
1992
1993 if (!key)
1994 goto end;
1995 ts = stktable_get_entry(t, key);
1996 if (!ts)
1997 goto end;
1998
1999 stream_track_stkctr(&s->stkctr[rule->action], t, ts);
2000
2001 /* let's count a new HTTP request as it's the first time we do it */
2002 ptr1 = stktable_data_ptr(t, ts, STKTABLE_DT_HTTP_REQ_CNT);
2003 ptr2 = stktable_data_ptr(t, ts, STKTABLE_DT_HTTP_REQ_RATE);
2004
2005 /* When the client triggers a 4xx from the server, it's most often due
2006 * to a missing object or permission. These events should be tracked
2007 * because if they happen often, it may indicate a brute force or a
2008 * vulnerability scan. Normally this is done when receiving the response
2009 * but here we're tracking after this ought to have been done so we have
2010 * to do it on purpose.
2011 */
2012 if (rule->from == ACT_F_HTTP_RES && (unsigned)(s->txn->status - 400) < 100) {
2013 ptr3 = stktable_data_ptr(t, ts, STKTABLE_DT_HTTP_ERR_CNT);
2014 ptr4 = stktable_data_ptr(t, ts, STKTABLE_DT_HTTP_ERR_RATE);
2015 }
2016
Willy Tarreau826f3ab2021-02-10 12:07:15 +01002017 if (rule->from == ACT_F_HTTP_RES && (unsigned)(s->txn->status - 500) < 100 &&
2018 s->txn->status != 501 && s->txn->status != 505) {
2019 ptr5 = stktable_data_ptr(t, ts, STKTABLE_DT_HTTP_FAIL_CNT);
2020 ptr6 = stktable_data_ptr(t, ts, STKTABLE_DT_HTTP_FAIL_RATE);
2021 }
2022
2023 if (ptr1 || ptr2 || ptr3 || ptr4 || ptr5 || ptr6) {
Christopher Fauletac98d812019-12-18 09:20:16 +01002024 HA_RWLOCK_WRLOCK(STK_SESS_LOCK, &ts->lock);
2025
2026 if (ptr1)
2027 stktable_data_cast(ptr1, http_req_cnt)++;
2028 if (ptr2)
2029 update_freq_ctr_period(&stktable_data_cast(ptr2, http_req_rate),
2030 t->data_arg[STKTABLE_DT_HTTP_REQ_RATE].u, 1);
2031 if (ptr3)
2032 stktable_data_cast(ptr3, http_err_cnt)++;
2033 if (ptr4)
2034 update_freq_ctr_period(&stktable_data_cast(ptr4, http_err_rate),
2035 t->data_arg[STKTABLE_DT_HTTP_ERR_RATE].u, 1);
Willy Tarreau826f3ab2021-02-10 12:07:15 +01002036 if (ptr5)
2037 stktable_data_cast(ptr5, http_fail_cnt)++;
2038 if (ptr6)
2039 update_freq_ctr_period(&stktable_data_cast(ptr6, http_fail_rate),
2040 t->data_arg[STKTABLE_DT_HTTP_FAIL_RATE].u, 1);
Christopher Fauletac98d812019-12-18 09:20:16 +01002041
2042 HA_RWLOCK_WRUNLOCK(STK_SESS_LOCK, &ts->lock);
2043
2044 /* If data was modified, we need to touch to re-schedule sync */
2045 stktable_touch_local(t, ts, 0);
2046 }
2047
2048 stkctr_set_flags(&s->stkctr[rule->action], STKCTR_TRACK_CONTENT);
2049 if (sess->fe != s->be)
2050 stkctr_set_flags(&s->stkctr[rule->action], STKCTR_TRACK_BACKEND);
2051
2052 end:
2053 return ACT_RET_CONT;
2054}
Christopher Faulet81e20172019-12-12 16:40:30 +01002055
Christopher Faulet2eb53962020-01-14 14:47:34 +01002056static void release_http_track_sc(struct act_rule *rule)
2057{
2058 release_sample_expr(rule->arg.trk_ctr.expr);
2059}
2060
Christopher Faulet81e20172019-12-12 16:40:30 +01002061/* Parse a "track-sc*" actions. It returns ACT_RET_PRS_OK on success,
2062 * ACT_RET_PRS_ERR on error.
2063 */
2064static enum act_parse_ret parse_http_track_sc(const char **args, int *orig_arg, struct proxy *px,
2065 struct act_rule *rule, char **err)
2066{
2067 struct sample_expr *expr;
2068 unsigned int where;
2069 unsigned int tsc_num;
2070 const char *tsc_num_str;
2071 int cur_arg;
2072
2073 tsc_num_str = &args[*orig_arg-1][8];
2074 if (cfg_parse_track_sc_num(&tsc_num, tsc_num_str, tsc_num_str + strlen(tsc_num_str), err) == -1)
2075 return ACT_RET_PRS_ERR;
2076
2077 cur_arg = *orig_arg;
2078 expr = sample_parse_expr((char **)args, &cur_arg, px->conf.args.file, px->conf.args.line,
Willy Tarreaue3b57bf2020-02-14 16:50:14 +01002079 err, &px->conf.args, NULL);
Christopher Faulet81e20172019-12-12 16:40:30 +01002080 if (!expr)
2081 return ACT_RET_PRS_ERR;
2082
2083 where = 0;
2084 if (px->cap & PR_CAP_FE)
2085 where |= (rule->from == ACT_F_HTTP_REQ ? SMP_VAL_FE_HRQ_HDR : SMP_VAL_FE_HRS_HDR);
2086 if (px->cap & PR_CAP_BE)
2087 where |= (rule->from == ACT_F_HTTP_REQ ? SMP_VAL_BE_HRQ_HDR : SMP_VAL_BE_HRS_HDR);
2088
2089 if (!(expr->fetch->val & where)) {
2090 memprintf(err, "fetch method '%s' extracts information from '%s', none of which is available here",
2091 args[cur_arg-1], sample_src_names(expr->fetch->use));
Christopher Faulet1337b322020-01-14 14:50:55 +01002092 release_sample_expr(expr);
Christopher Faulet81e20172019-12-12 16:40:30 +01002093 return ACT_RET_PRS_ERR;
2094 }
2095
2096 if (strcmp(args[cur_arg], "table") == 0) {
2097 cur_arg++;
2098 if (!*args[cur_arg]) {
2099 memprintf(err, "missing table name");
Christopher Faulet1337b322020-01-14 14:50:55 +01002100 release_sample_expr(expr);
Christopher Faulet81e20172019-12-12 16:40:30 +01002101 return ACT_RET_PRS_ERR;
2102 }
2103
2104 /* we copy the table name for now, it will be resolved later */
2105 rule->arg.trk_ctr.table.n = strdup(args[cur_arg]);
2106 cur_arg++;
2107 }
2108
Christopher Fauletac98d812019-12-18 09:20:16 +01002109 rule->action = tsc_num;
Christopher Faulet81e20172019-12-12 16:40:30 +01002110 rule->arg.trk_ctr.expr = expr;
Christopher Fauletac98d812019-12-18 09:20:16 +01002111 rule->action_ptr = http_action_track_sc;
Christopher Faulet2eb53962020-01-14 14:47:34 +01002112 rule->release_ptr = release_http_track_sc;
Christopher Faulet81e20172019-12-12 16:40:30 +01002113 rule->check_ptr = check_trk_action;
2114
2115 *orig_arg = cur_arg;
2116 return ACT_RET_PRS_OK;
2117}
2118
Amaury Denoyelle8d228232020-12-10 13:43:54 +01002119static enum act_return action_timeout_set_stream_timeout(struct act_rule *rule,
2120 struct proxy *px,
2121 struct session *sess,
2122 struct stream *s,
2123 int flags)
2124{
2125 struct sample *key;
2126
2127 if (rule->arg.timeout.expr) {
2128 key = sample_fetch_as_type(px, sess, s, SMP_OPT_FINAL, rule->arg.timeout.expr, SMP_T_SINT);
2129 if (!key)
2130 return ACT_RET_CONT;
2131
2132 stream_set_timeout(s, rule->arg.timeout.type, MS_TO_TICKS(key->data.u.sint));
2133 }
2134 else {
2135 stream_set_timeout(s, rule->arg.timeout.type, MS_TO_TICKS(rule->arg.timeout.value));
2136 }
2137
2138 return ACT_RET_CONT;
2139}
2140
2141/* Parse a "set-timeout" action. Returns ACT_RET_PRS_ERR if parsing error.
2142 */
2143static enum act_parse_ret parse_http_set_timeout(const char **args,
2144 int *orig_arg,
2145 struct proxy *px,
2146 struct act_rule *rule, char **err)
2147{
2148 int cur_arg;
2149
2150 rule->action = ACT_CUSTOM;
2151 rule->action_ptr = action_timeout_set_stream_timeout;
2152 rule->release_ptr = release_timeout_action;
2153
2154 cur_arg = *orig_arg;
2155 if (!*args[cur_arg] || !*args[cur_arg + 1]) {
2156 memprintf(err, "expects exactly 2 arguments");
2157 return ACT_RET_PRS_ERR;
2158 }
2159
2160 if (!(px->cap & PR_CAP_BE)) {
2161 memprintf(err, "proxy '%s' has no backend capability", px->id);
2162 return ACT_RET_PRS_ERR;
2163 }
2164
2165 if (cfg_parse_rule_set_timeout(args, cur_arg,
2166 &rule->arg.timeout.value,
2167 &rule->arg.timeout.type,
2168 &rule->arg.timeout.expr,
2169 err,
2170 px->conf.args.file,
2171 px->conf.args.line, &px->conf.args) == -1) {
2172 return ACT_RET_PRS_ERR;
2173 }
2174
2175 *orig_arg = cur_arg + 2;
2176
2177 return ACT_RET_PRS_OK;
2178}
2179
Christopher Faulet46f95542019-12-20 10:07:22 +01002180/* This function executes a strict-mode actions. On success, it always returns
2181 * ACT_RET_CONT
2182 */
2183static enum act_return http_action_strict_mode(struct act_rule *rule, struct proxy *px,
2184 struct session *sess, struct stream *s, int flags)
2185{
2186 struct http_msg *msg = ((rule->from == ACT_F_HTTP_REQ) ? &s->txn->req : &s->txn->rsp);
2187
2188 if (rule->action == 0) // strict-mode on
2189 msg->flags &= ~HTTP_MSGF_SOFT_RW;
2190 else // strict-mode off
2191 msg->flags |= HTTP_MSGF_SOFT_RW;
2192 return ACT_RET_CONT;
2193}
2194
2195/* Parse a "strict-mode" action. It returns ACT_RET_PRS_OK on success,
2196 * ACT_RET_PRS_ERR on error.
2197 */
2198static enum act_parse_ret parse_http_strict_mode(const char **args, int *orig_arg, struct proxy *px,
2199 struct act_rule *rule, char **err)
2200{
2201 int cur_arg;
2202
Christopher Faulet46f95542019-12-20 10:07:22 +01002203 cur_arg = *orig_arg;
2204 if (!*args[cur_arg]) {
2205 memprintf(err, "expects exactly 1 arguments");
2206 return ACT_RET_PRS_ERR;
2207 }
2208
2209 if (strcasecmp(args[cur_arg], "on") == 0)
2210 rule->action = 0; // strict-mode on
2211 else if (strcasecmp(args[cur_arg], "off") == 0)
2212 rule->action = 1; // strict-mode off
2213 else {
2214 memprintf(err, "Unexpected value '%s'. Only 'on' and 'off' are supported", args[cur_arg]);
2215 return ACT_RET_PRS_ERR;
2216 }
2217 rule->action_ptr = http_action_strict_mode;
2218
2219 *orig_arg = cur_arg + 1;
2220 return ACT_RET_PRS_OK;
2221}
2222
Christopher Faulet24231ab2020-01-24 17:44:23 +01002223/* This function executes a return action. It builds an HTX message from an
2224 * errorfile, an raw file or a log-format string, depending on <.action>
2225 * value. On success, it returns ACT_RET_ABRT. If an error occurs ACT_RET_ERR is
2226 * returned.
2227 */
2228static enum act_return http_action_return(struct act_rule *rule, struct proxy *px,
2229 struct session *sess, struct stream *s, int flags)
2230{
2231 struct channel *req = &s->req;
Christopher Faulet24231ab2020-01-24 17:44:23 +01002232
Christopher Faulet2d36df22021-02-19 11:41:01 +01002233 s->txn->status = rule->arg.http_reply->status;
Christopher Faulet0e2ad612020-05-13 16:38:37 +02002234 if (http_reply_message(s, rule->arg.http_reply) == -1)
2235 return ACT_RET_ERR;
Christopher Faulet24231ab2020-01-24 17:44:23 +01002236
Christopher Faulet24231ab2020-01-24 17:44:23 +01002237 if (rule->from == ACT_F_HTTP_REQ) {
2238 /* let's log the request time */
2239 s->logs.tv_request = now;
2240 req->analysers &= AN_REQ_FLT_END;
2241
2242 if (s->sess->fe == s->be) /* report it if the request was intercepted by the frontend */
Willy Tarreau4781b152021-04-06 13:53:36 +02002243 _HA_ATOMIC_INC(&s->sess->fe->fe_counters.intercepted_req);
Christopher Faulet24231ab2020-01-24 17:44:23 +01002244 }
2245
2246 if (!(s->flags & SF_ERR_MASK))
2247 s->flags |= SF_ERR_LOCAL;
2248 if (!(s->flags & SF_FINST_MASK))
2249 s->flags |= ((rule->from == ACT_F_HTTP_REQ) ? SF_FINST_R : SF_FINST_H);
2250
Christopher Faulet0e2ad612020-05-13 16:38:37 +02002251 return ACT_RET_ABRT;
Christopher Faulet24231ab2020-01-24 17:44:23 +01002252}
2253
Christopher Faulet24231ab2020-01-24 17:44:23 +01002254/* Parse a "return" action. It returns ACT_RET_PRS_OK on success,
Christopher Faulet47e791e2020-05-13 14:36:55 +02002255 * ACT_RET_PRS_ERR on error. It relies on http_parse_http_reply() to set
2256 * <.arg.http_reply>.
Christopher Faulet24231ab2020-01-24 17:44:23 +01002257 */
2258static enum act_parse_ret parse_http_return(const char **args, int *orig_arg, struct proxy *px,
2259 struct act_rule *rule, char **err)
2260{
Christopher Faulet47e791e2020-05-13 14:36:55 +02002261 /* Prepare parsing of log-format strings */
2262 px->conf.args.ctx = ((rule->from == ACT_F_HTTP_REQ) ? ARGC_HRQ : ARGC_HRS);
2263 rule->arg.http_reply = http_parse_http_reply(args, orig_arg, px, 200, err);
2264 if (!rule->arg.http_reply)
2265 return ACT_RET_PRS_ERR;
Christopher Faulet24231ab2020-01-24 17:44:23 +01002266
Christopher Fauletba946bf2020-05-13 08:50:07 +02002267 rule->flags |= ACT_FLAG_FINAL;
Christopher Faulet5ff0c642020-05-12 18:33:37 +02002268 rule->action = ACT_CUSTOM;
Christopher Faulet5cb513a2020-05-13 17:56:56 +02002269 rule->check_ptr = check_act_http_reply;
Christopher Faulet24231ab2020-01-24 17:44:23 +01002270 rule->action_ptr = http_action_return;
Christopher Faulet5cb513a2020-05-13 17:56:56 +02002271 rule->release_ptr = release_act_http_reply;
Christopher Faulet24231ab2020-01-24 17:44:23 +01002272 return ACT_RET_PRS_OK;
Christopher Faulet24231ab2020-01-24 17:44:23 +01002273}
2274
Christopher Faulet021a8e42021-03-29 10:46:38 +02002275
2276
2277/* This function executes a wait-for-body action. It waits for the message
2278 * payload for a max configured time (.arg.p[0]) and eventually for only first
2279 * <arg.p[1]> bytes (0 means no limit). It relies on http_wait_for_msg_body()
2280 * function. it returns ACT_RET_CONT when conditions are met to stop to wait.
2281 * Otherwise ACT_RET_YIELD is returned to wait for more data. ACT_RET_INV is
2282 * returned if a parsing error is raised by lower level and ACT_RET_ERR if an
2283 * internal error occured. Finally ACT_RET_ABRT is returned when a timeout
2284 * occured.
2285 */
2286static enum act_return http_action_wait_for_body(struct act_rule *rule, struct proxy *px,
2287 struct session *sess, struct stream *s, int flags)
2288{
2289 struct channel *chn = ((rule->from == ACT_F_HTTP_REQ) ? &s->req : &s->res);
2290 unsigned int time = (uintptr_t)rule->arg.act.p[0];
2291 unsigned int bytes = (uintptr_t)rule->arg.act.p[1];
2292
2293 switch (http_wait_for_msg_body(s, chn, time, bytes)) {
2294 case HTTP_RULE_RES_CONT:
2295 return ACT_RET_CONT;
2296 case HTTP_RULE_RES_YIELD:
2297 return ACT_RET_YIELD;
2298 case HTTP_RULE_RES_BADREQ:
2299 return ACT_RET_INV;
2300 case HTTP_RULE_RES_ERROR:
2301 return ACT_RET_ERR;
2302 case HTTP_RULE_RES_ABRT:
2303 return ACT_RET_ABRT;
2304 default:
2305 return ACT_RET_ERR;
2306 }
2307}
2308
2309/* Parse a "wait-for-body" action. It returns ACT_RET_PRS_OK on success,
2310 * ACT_RET_PRS_ERR on error.
2311 */
2312static enum act_parse_ret parse_http_wait_for_body(const char **args, int *orig_arg, struct proxy *px,
2313 struct act_rule *rule, char **err)
2314{
2315 int cur_arg;
2316 unsigned int time, bytes;
2317 const char *res;
2318
2319 cur_arg = *orig_arg;
2320 if (!*args[cur_arg]) {
2321 memprintf(err, "expects time <time> [ at-least <bytes> ]");
2322 return ACT_RET_PRS_ERR;
2323 }
2324
2325 time = UINT_MAX; /* To be sure it is set */
2326 bytes = 0; /* Default value, wait all the body */
2327 while (*(args[cur_arg])) {
2328 if (strcmp(args[cur_arg], "time") == 0) {
2329 if (!*args[cur_arg + 1]) {
2330 memprintf(err, "missing argument for '%s'", args[cur_arg]);
2331 return ACT_RET_PRS_ERR;
2332 }
2333 res = parse_time_err(args[cur_arg+1], &time, TIME_UNIT_MS);
2334 if (res == PARSE_TIME_OVER) {
2335 memprintf(err, "time overflow (maximum value is 2147483647 ms or ~24.8 days)");
2336 return ACT_RET_PRS_ERR;
2337 }
2338 if (res == PARSE_TIME_UNDER) {
2339 memprintf(err, "time underflow (minimum non-null value is 1 ms)");
2340 return ACT_RET_PRS_ERR;
2341 }
2342 if (res) {
2343 memprintf(err, "unexpected character '%c'", *res);
2344 return ACT_RET_PRS_ERR;
2345 }
2346 cur_arg++;
2347 }
2348 else if (strcmp(args[cur_arg], "at-least") == 0) {
2349 if (!*args[cur_arg + 1]) {
2350 memprintf(err, "missing argument for '%s'", args[cur_arg]);
2351 return ACT_RET_PRS_ERR;
2352 }
2353 res = parse_size_err(args[cur_arg+1], &bytes);
2354 if (res) {
2355 memprintf(err, "unexpected character '%c'", *res);
2356 return ACT_RET_PRS_ERR;
2357 }
2358 cur_arg++;
2359 }
2360 else
2361 break;
2362 cur_arg++;
2363 }
2364
2365 if (time == UINT_MAX) {
2366 memprintf(err, "expects time <time> [ at-least <bytes> ]");
2367 return ACT_RET_PRS_ERR;
2368 }
2369
2370 rule->arg.act.p[0] = (void *)(uintptr_t)time;
2371 rule->arg.act.p[1] = (void *)(uintptr_t)bytes;
2372
2373 *orig_arg = cur_arg;
2374
2375 rule->action = ACT_CUSTOM;
2376 rule->action_ptr = http_action_wait_for_body;
2377 return ACT_RET_PRS_OK;
2378}
2379
Willy Tarreau79e57332018-10-02 16:01:16 +02002380/************************************************************************/
2381/* All supported http-request action keywords must be declared here. */
2382/************************************************************************/
2383
2384static struct action_kw_list http_req_actions = {
2385 .kw = {
Christopher Faulet81e20172019-12-12 16:40:30 +01002386 { "add-acl", parse_http_set_map, 1 },
2387 { "add-header", parse_http_set_header, 0 },
2388 { "allow", parse_http_allow, 0 },
2389 { "auth", parse_http_auth, 0 },
2390 { "capture", parse_http_req_capture, 0 },
2391 { "del-acl", parse_http_set_map, 1 },
2392 { "del-header", parse_http_del_header, 0 },
2393 { "del-map", parse_http_set_map, 1 },
Christopher Faulete0fca292020-01-13 21:49:03 +01002394 { "deny", parse_http_deny, 0 },
Christopher Faulet81e20172019-12-12 16:40:30 +01002395 { "disable-l7-retry", parse_http_req_disable_l7_retry, 0 },
2396 { "early-hint", parse_http_set_header, 0 },
Tim Duesterhusd2bedcc2021-04-15 21:45:57 +02002397 { "normalize-uri", parse_http_normalize_uri, 0 },
Christopher Faulet81e20172019-12-12 16:40:30 +01002398 { "redirect", parse_http_redirect, 0 },
2399 { "reject", parse_http_action_reject, 0 },
2400 { "replace-header", parse_http_replace_header, 0 },
2401 { "replace-path", parse_replace_uri, 0 },
Christopher Faulet312294f2020-09-02 17:17:44 +02002402 { "replace-pathq", parse_replace_uri, 0 },
Christopher Faulet81e20172019-12-12 16:40:30 +01002403 { "replace-uri", parse_replace_uri, 0 },
2404 { "replace-value", parse_http_replace_header, 0 },
Christopher Faulet24231ab2020-01-24 17:44:23 +01002405 { "return", parse_http_return, 0 },
Christopher Faulet81e20172019-12-12 16:40:30 +01002406 { "set-header", parse_http_set_header, 0 },
2407 { "set-log-level", parse_http_set_log_level, 0 },
2408 { "set-map", parse_http_set_map, 1 },
2409 { "set-method", parse_set_req_line, 0 },
2410 { "set-mark", parse_http_set_mark, 0 },
2411 { "set-nice", parse_http_set_nice, 0 },
2412 { "set-path", parse_set_req_line, 0 },
Christopher Faulet312294f2020-09-02 17:17:44 +02002413 { "set-pathq", parse_set_req_line, 0 },
Christopher Faulet81e20172019-12-12 16:40:30 +01002414 { "set-query", parse_set_req_line, 0 },
2415 { "set-tos", parse_http_set_tos, 0 },
2416 { "set-uri", parse_set_req_line, 0 },
Christopher Faulet46f95542019-12-20 10:07:22 +01002417 { "strict-mode", parse_http_strict_mode, 0 },
Christopher Faulete0fca292020-01-13 21:49:03 +01002418 { "tarpit", parse_http_deny, 0 },
Christopher Faulet81e20172019-12-12 16:40:30 +01002419 { "track-sc", parse_http_track_sc, 1 },
Amaury Denoyelle8d228232020-12-10 13:43:54 +01002420 { "set-timeout", parse_http_set_timeout, 0 },
Christopher Faulet021a8e42021-03-29 10:46:38 +02002421 { "wait-for-body", parse_http_wait_for_body, 0 },
Willy Tarreau79e57332018-10-02 16:01:16 +02002422 { NULL, NULL }
2423 }
2424};
2425
Willy Tarreau0108d902018-11-25 19:14:37 +01002426INITCALL1(STG_REGISTER, http_req_keywords_register, &http_req_actions);
2427
Willy Tarreau79e57332018-10-02 16:01:16 +02002428static struct action_kw_list http_res_actions = {
2429 .kw = {
Christopher Faulet81e20172019-12-12 16:40:30 +01002430 { "add-acl", parse_http_set_map, 1 },
2431 { "add-header", parse_http_set_header, 0 },
2432 { "allow", parse_http_allow, 0 },
2433 { "capture", parse_http_res_capture, 0 },
2434 { "del-acl", parse_http_set_map, 1 },
2435 { "del-header", parse_http_del_header, 0 },
2436 { "del-map", parse_http_set_map, 1 },
Christopher Faulete0fca292020-01-13 21:49:03 +01002437 { "deny", parse_http_deny, 0 },
Christopher Faulet81e20172019-12-12 16:40:30 +01002438 { "redirect", parse_http_redirect, 0 },
2439 { "replace-header", parse_http_replace_header, 0 },
2440 { "replace-value", parse_http_replace_header, 0 },
Christopher Faulet24231ab2020-01-24 17:44:23 +01002441 { "return", parse_http_return, 0 },
Christopher Faulet81e20172019-12-12 16:40:30 +01002442 { "set-header", parse_http_set_header, 0 },
2443 { "set-log-level", parse_http_set_log_level, 0 },
2444 { "set-map", parse_http_set_map, 1 },
2445 { "set-mark", parse_http_set_mark, 0 },
2446 { "set-nice", parse_http_set_nice, 0 },
2447 { "set-status", parse_http_set_status, 0 },
2448 { "set-tos", parse_http_set_tos, 0 },
Christopher Faulet46f95542019-12-20 10:07:22 +01002449 { "strict-mode", parse_http_strict_mode, 0 },
Christopher Faulet81e20172019-12-12 16:40:30 +01002450 { "track-sc", parse_http_track_sc, 1 },
Christopher Faulet021a8e42021-03-29 10:46:38 +02002451 { "wait-for-body", parse_http_wait_for_body, 0 },
Willy Tarreau79e57332018-10-02 16:01:16 +02002452 { NULL, NULL }
2453 }
2454};
2455
Willy Tarreau0108d902018-11-25 19:14:37 +01002456INITCALL1(STG_REGISTER, http_res_keywords_register, &http_res_actions);
Willy Tarreau79e57332018-10-02 16:01:16 +02002457
Christopher Faulet6d0c3df2020-01-22 09:26:35 +01002458static struct action_kw_list http_after_res_actions = {
2459 .kw = {
2460 { "add-header", parse_http_set_header, 0 },
2461 { "allow", parse_http_allow, 0 },
2462 { "del-header", parse_http_del_header, 0 },
2463 { "replace-header", parse_http_replace_header, 0 },
2464 { "replace-value", parse_http_replace_header, 0 },
2465 { "set-header", parse_http_set_header, 0 },
2466 { "set-status", parse_http_set_status, 0 },
2467 { "strict-mode", parse_http_strict_mode, 0 },
2468 { NULL, NULL }
2469 }
2470};
2471
2472INITCALL1(STG_REGISTER, http_after_res_keywords_register, &http_after_res_actions);
2473
Willy Tarreau79e57332018-10-02 16:01:16 +02002474/*
2475 * Local variables:
2476 * c-indent-level: 8
2477 * c-basic-offset: 8
2478 * End:
2479 */