blob: f30694e5f439b97194242ecb44a5b5da84b96b5c [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 }
Maximilian Maderff3bb8b2021-04-21 00:22:50 +0200235 case ACT_NORMALIZE_URI_PATH_STRIP_DOT: {
236 const struct ist path = http_get_path(uri);
237 struct ist newpath = ist2(replace->area, replace->size);
238
239 if (!isttest(path))
240 goto leave;
241
242 err = uri_normalizer_path_dot(iststop(path, '?'), &newpath);
243
244 if (err != URI_NORMALIZER_ERR_NONE)
245 break;
246
247 if (!http_replace_req_path(htx, newpath, 0))
248 goto fail_rewrite;
249
250 break;
251 }
Tim Duesterhus5be6ab22021-04-17 11:21:10 +0200252 case ACT_NORMALIZE_URI_PATH_STRIP_DOTDOT:
253 case ACT_NORMALIZE_URI_PATH_STRIP_DOTDOT_FULL: {
Tim Duesterhus9982fc22021-04-15 21:45:59 +0200254 const struct ist path = http_get_path(uri);
255 struct ist newpath = ist2(replace->area, replace->size);
256
257 if (!isttest(path))
258 goto leave;
259
Tim Duesterhus5be6ab22021-04-17 11:21:10 +0200260 err = uri_normalizer_path_dotdot(iststop(path, '?'), rule->action == ACT_NORMALIZE_URI_PATH_STRIP_DOTDOT_FULL, &newpath);
Tim Duesterhus9982fc22021-04-15 21:45:59 +0200261
262 if (err != URI_NORMALIZER_ERR_NONE)
263 break;
264
265 if (!http_replace_req_path(htx, newpath, 0))
266 goto fail_rewrite;
267
268 break;
269 }
Tim Duesterhus5be6ab22021-04-17 11:21:10 +0200270 case ACT_NORMALIZE_URI_QUERY_SORT_BY_NAME: {
Tim Duesterhusd7b89be2021-04-15 21:46:01 +0200271 const struct ist path = http_get_path(uri);
272 struct ist newquery = ist2(replace->area, replace->size);
273
274 if (!isttest(path))
275 goto leave;
276
277 err = uri_normalizer_query_sort(istfind(path, '?'), '&', &newquery);
278
279 if (err != URI_NORMALIZER_ERR_NONE)
280 break;
281
282 if (!http_replace_req_query(htx, newquery))
283 goto fail_rewrite;
284
285 break;
286 }
Tim Duesterhus5be6ab22021-04-17 11:21:10 +0200287 case ACT_NORMALIZE_URI_PERCENT_TO_UPPERCASE:
288 case ACT_NORMALIZE_URI_PERCENT_TO_UPPERCASE_STRICT: {
Tim Duesterhusa4071932021-04-15 21:46:02 +0200289 const struct ist path = http_get_path(uri);
290 struct ist newpath = ist2(replace->area, replace->size);
291
292 if (!isttest(path))
293 goto leave;
294
Tim Duesterhus5be6ab22021-04-17 11:21:10 +0200295 err = uri_normalizer_percent_upper(path, rule->action == ACT_NORMALIZE_URI_PERCENT_TO_UPPERCASE_STRICT, &newpath);
Tim Duesterhusa4071932021-04-15 21:46:02 +0200296
297 if (err != URI_NORMALIZER_ERR_NONE)
298 break;
299
300 if (!http_replace_req_path(htx, newpath, 1))
301 goto fail_rewrite;
302
303 break;
304 }
Tim Duesterhus2e4a18e2021-04-21 21:20:36 +0200305 case ACT_NORMALIZE_URI_PERCENT_DECODE_UNRESERVED:
306 case ACT_NORMALIZE_URI_PERCENT_DECODE_UNRESERVED_STRICT: {
307 const struct ist path = http_get_path(uri);
308 struct ist newpath = ist2(replace->area, replace->size);
309
310 if (!isttest(path))
311 goto leave;
312
313 err = uri_normalizer_percent_decode_unreserved(path, rule->action == ACT_NORMALIZE_URI_PERCENT_DECODE_UNRESERVED_STRICT, &newpath);
314
315 if (err != URI_NORMALIZER_ERR_NONE)
316 break;
317
318 if (!http_replace_req_path(htx, newpath, 1))
319 goto fail_rewrite;
320
321 break;
322 }
Tim Duesterhusc9e05ab2021-05-10 17:28:25 +0200323 case ACT_NORMALIZE_URI_FRAGMENT_STRIP: {
324 const struct ist path = http_get_path(uri);
325 struct ist newpath = ist2(replace->area, replace->size);
326
327 if (!isttest(path))
328 goto leave;
329
330 err = uri_normalizer_fragment_strip(path, &newpath);
331
332 if (err != URI_NORMALIZER_ERR_NONE)
333 break;
334
335 if (!http_replace_req_path(htx, newpath, 1))
336 goto fail_rewrite;
337
338 break;
339 }
Tim Duesterhusd2bedcc2021-04-15 21:45:57 +0200340 }
341
342 switch (err) {
343 case URI_NORMALIZER_ERR_NONE:
344 break;
345 case URI_NORMALIZER_ERR_INTERNAL_ERROR:
346 ret = ACT_RET_ERR;
347 break;
348 case URI_NORMALIZER_ERR_INVALID_INPUT:
349 ret = ACT_RET_INV;
350 break;
351 case URI_NORMALIZER_ERR_ALLOC:
352 goto fail_alloc;
353 }
354
355 leave:
356 free_trash_chunk(replace);
357 return ret;
358
359 fail_alloc:
360 if (!(s->flags & SF_ERR_MASK))
361 s->flags |= SF_ERR_RESOURCE;
362 ret = ACT_RET_ERR;
363 goto leave;
364
365 fail_rewrite:
366 _HA_ATOMIC_ADD(&sess->fe->fe_counters.failed_rewrites, 1);
367 if (s->flags & SF_BE_ASSIGNED)
368 _HA_ATOMIC_ADD(&s->be->be_counters.failed_rewrites, 1);
369 if (sess->listener && sess->listener->counters)
370 _HA_ATOMIC_ADD(&sess->listener->counters->failed_rewrites, 1);
371 if (objt_server(s->target))
372 _HA_ATOMIC_ADD(&__objt_server(s->target)->counters.failed_rewrites, 1);
373
374 if (!(s->txn->req.flags & HTTP_MSGF_SOFT_RW)) {
375 ret = ACT_RET_ERR;
376 if (!(s->flags & SF_ERR_MASK))
377 s->flags |= SF_ERR_PRXCOND;
378 }
379 goto leave;
380}
381
382/* Parses the http-request normalize-uri action. It expects a single <normalizer>
383 * argument, corresponding too a value in `enum act_normalize_uri`.
384 *
385 * It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
386 */
387static enum act_parse_ret parse_http_normalize_uri(const char **args, int *orig_arg, struct proxy *px,
388 struct act_rule *rule, char **err)
389{
390 int cur_arg = *orig_arg;
391
392 rule->action_ptr = http_action_normalize_uri;
393 rule->release_ptr = NULL;
394
395 if (!*args[cur_arg]) {
396 memprintf(err, "missing argument <normalizer>");
397 return ACT_RET_PRS_ERR;
398 }
399
Tim Duesterhus5be6ab22021-04-17 11:21:10 +0200400 if (strcmp(args[cur_arg], "path-merge-slashes") == 0) {
Tim Duesterhusd371e992021-04-15 21:45:58 +0200401 cur_arg++;
Tim Duesterhusd2bedcc2021-04-15 21:45:57 +0200402
Tim Duesterhus5be6ab22021-04-17 11:21:10 +0200403 rule->action = ACT_NORMALIZE_URI_PATH_MERGE_SLASHES;
Tim Duesterhusd2bedcc2021-04-15 21:45:57 +0200404 }
Maximilian Maderff3bb8b2021-04-21 00:22:50 +0200405 else if (strcmp(args[cur_arg], "path-strip-dot") == 0) {
406 cur_arg++;
407
408 rule->action = ACT_NORMALIZE_URI_PATH_STRIP_DOT;
409 }
Tim Duesterhus5be6ab22021-04-17 11:21:10 +0200410 else if (strcmp(args[cur_arg], "path-strip-dotdot") == 0) {
Tim Duesterhus9982fc22021-04-15 21:45:59 +0200411 cur_arg++;
412
Tim Duesterhus560e1a62021-04-15 21:46:00 +0200413 if (strcmp(args[cur_arg], "full") == 0) {
414 cur_arg++;
Tim Duesterhus5be6ab22021-04-17 11:21:10 +0200415 rule->action = ACT_NORMALIZE_URI_PATH_STRIP_DOTDOT_FULL;
Tim Duesterhus560e1a62021-04-15 21:46:00 +0200416 }
417 else if (!*args[cur_arg]) {
Tim Duesterhus5be6ab22021-04-17 11:21:10 +0200418 rule->action = ACT_NORMALIZE_URI_PATH_STRIP_DOTDOT;
Tim Duesterhus560e1a62021-04-15 21:46:00 +0200419 }
420 else if (strcmp(args[cur_arg], "if") != 0 && strcmp(args[cur_arg], "unless") != 0) {
Tim Duesterhus2f413132021-05-10 23:21:20 +0200421 memprintf(err, "unknown argument '%s' for 'path-strip-dotdot' normalizer", args[cur_arg]);
Tim Duesterhus560e1a62021-04-15 21:46:00 +0200422 return ACT_RET_PRS_ERR;
423 }
Tim Duesterhus9982fc22021-04-15 21:45:59 +0200424 }
Tim Duesterhus5be6ab22021-04-17 11:21:10 +0200425 else if (strcmp(args[cur_arg], "query-sort-by-name") == 0) {
Tim Duesterhusd7b89be2021-04-15 21:46:01 +0200426 cur_arg++;
427
Tim Duesterhus5be6ab22021-04-17 11:21:10 +0200428 rule->action = ACT_NORMALIZE_URI_QUERY_SORT_BY_NAME;
Tim Duesterhusd7b89be2021-04-15 21:46:01 +0200429 }
Tim Duesterhus5be6ab22021-04-17 11:21:10 +0200430 else if (strcmp(args[cur_arg], "percent-to-uppercase") == 0) {
Tim Duesterhusa4071932021-04-15 21:46:02 +0200431 cur_arg++;
432
433 if (strcmp(args[cur_arg], "strict") == 0) {
434 cur_arg++;
Tim Duesterhus5be6ab22021-04-17 11:21:10 +0200435 rule->action = ACT_NORMALIZE_URI_PERCENT_TO_UPPERCASE_STRICT;
Tim Duesterhusa4071932021-04-15 21:46:02 +0200436 }
437 else if (!*args[cur_arg]) {
Tim Duesterhus5be6ab22021-04-17 11:21:10 +0200438 rule->action = ACT_NORMALIZE_URI_PERCENT_TO_UPPERCASE;
Tim Duesterhusa4071932021-04-15 21:46:02 +0200439 }
440 else if (strcmp(args[cur_arg], "if") != 0 && strcmp(args[cur_arg], "unless") != 0) {
Tim Duesterhus2f413132021-05-10 23:21:20 +0200441 memprintf(err, "unknown argument '%s' for 'percent-to-uppercase' normalizer", args[cur_arg]);
Tim Duesterhusa4071932021-04-15 21:46:02 +0200442 return ACT_RET_PRS_ERR;
443 }
444 }
Tim Duesterhus2e4a18e2021-04-21 21:20:36 +0200445 else if (strcmp(args[cur_arg], "percent-decode-unreserved") == 0) {
446 cur_arg++;
447
448 if (strcmp(args[cur_arg], "strict") == 0) {
449 cur_arg++;
450 rule->action = ACT_NORMALIZE_URI_PERCENT_DECODE_UNRESERVED_STRICT;
451 }
452 else if (!*args[cur_arg]) {
453 rule->action = ACT_NORMALIZE_URI_PERCENT_DECODE_UNRESERVED;
454 }
455 else if (strcmp(args[cur_arg], "if") != 0 && strcmp(args[cur_arg], "unless") != 0) {
456 memprintf(err, "unknown argument '%s' for 'percent-decode-unreserved' normalizer", args[cur_arg]);
457 return ACT_RET_PRS_ERR;
458 }
459 }
Tim Duesterhusc9e05ab2021-05-10 17:28:25 +0200460 else if (strcmp(args[cur_arg], "fragment-strip") == 0) {
461 cur_arg++;
462
463 rule->action = ACT_NORMALIZE_URI_FRAGMENT_STRIP;
464 }
Tim Duesterhusd2bedcc2021-04-15 21:45:57 +0200465 else {
466 memprintf(err, "unknown normalizer '%s'", args[cur_arg]);
467 return ACT_RET_PRS_ERR;
468 }
469
470 *orig_arg = cur_arg;
471 return ACT_RET_PRS_OK;
472}
473
Willy Tarreau33810222019-06-12 17:44:02 +0200474/* This function executes a replace-uri action. It finds its arguments in
Christopher Faulet96bff762019-12-17 13:46:18 +0100475 * <rule>.arg.http. It builds a string in the trash from the format string
Willy Tarreau33810222019-06-12 17:44:02 +0200476 * previously filled by function parse_replace_uri() and will execute the regex
Christopher Faulet96bff762019-12-17 13:46:18 +0100477 * in <http.re> to replace the URI. It uses the format string present in
Christopher Faulet2c22a692019-12-18 15:39:56 +0100478 * <http.fmt>. The component to act on (path/uri) is taken from <.action> which
Christopher Faulet96bff762019-12-17 13:46:18 +0100479 * contains 1 for the path or 3 for the URI (values used by
480 * http_req_replace_stline()). On success, it returns ACT_RET_CONT. If an error
481 * occurs while soft rewrites are enabled, the action is canceled, but the rule
482 * processing continue. Otherwsize ACT_RET_ERR is returned.
Willy Tarreau33810222019-06-12 17:44:02 +0200483 */
484static enum act_return http_action_replace_uri(struct act_rule *rule, struct proxy *px,
485 struct session *sess, struct stream *s, int flags)
486{
Christopher Faulet13403762019-12-13 09:01:57 +0100487 enum act_return ret = ACT_RET_CONT;
Willy Tarreau33810222019-06-12 17:44:02 +0200488 struct buffer *replace, *output;
489 struct ist uri;
490 int len;
491
492 replace = alloc_trash_chunk();
493 output = alloc_trash_chunk();
494 if (!replace || !output)
Christopher Faulete00d06c2019-12-16 17:18:42 +0100495 goto fail_alloc;
Christopher Faulet12c28b62019-07-15 16:30:24 +0200496 uri = htx_sl_req_uri(http_get_stline(htxbuf(&s->req.buf)));
Willy Tarreau262c3f12019-12-17 06:52:51 +0100497
Christopher Faulet1fa0cc12020-09-02 11:10:38 +0200498 if (rule->action == 1) // replace-path
499 uri = iststop(http_get_path(uri), '?');
Christopher Faulet312294f2020-09-02 17:17:44 +0200500 else if (rule->action == 4) // replace-pathq
501 uri = http_get_path(uri);
Willy Tarreau262c3f12019-12-17 06:52:51 +0100502
Christopher Faulet96bff762019-12-17 13:46:18 +0100503 if (!regex_exec_match2(rule->arg.http.re, uri.ptr, uri.len, MAX_MATCH, pmatch, 0))
Willy Tarreau33810222019-06-12 17:44:02 +0200504 goto leave;
505
Christopher Faulet96bff762019-12-17 13:46:18 +0100506 replace->data = build_logline(s, replace->area, replace->size, &rule->arg.http.fmt);
Willy Tarreau33810222019-06-12 17:44:02 +0200507
508 /* note: uri.ptr doesn't need to be zero-terminated because it will
509 * only be used to pick pmatch references.
510 */
511 len = exp_replace(output->area, output->size, uri.ptr, replace->area, pmatch);
512 if (len == -1)
Christopher Faulete00d06c2019-12-16 17:18:42 +0100513 goto fail_rewrite;
Willy Tarreau33810222019-06-12 17:44:02 +0200514
Christopher Faulet2c22a692019-12-18 15:39:56 +0100515 if (http_req_replace_stline(rule->action, output->area, len, px, s) == -1)
Christopher Faulete00d06c2019-12-16 17:18:42 +0100516 goto fail_rewrite;
Willy Tarreau33810222019-06-12 17:44:02 +0200517
Christopher Faulete00d06c2019-12-16 17:18:42 +0100518 leave:
Willy Tarreau33810222019-06-12 17:44:02 +0200519 free_trash_chunk(output);
520 free_trash_chunk(replace);
521 return ret;
Christopher Faulete00d06c2019-12-16 17:18:42 +0100522
523 fail_alloc:
524 if (!(s->flags & SF_ERR_MASK))
525 s->flags |= SF_ERR_RESOURCE;
526 ret = ACT_RET_ERR;
527 goto leave;
528
529 fail_rewrite:
Willy Tarreau4781b152021-04-06 13:53:36 +0200530 _HA_ATOMIC_INC(&sess->fe->fe_counters.failed_rewrites);
Christopher Faulete00d06c2019-12-16 17:18:42 +0100531 if (s->flags & SF_BE_ASSIGNED)
Willy Tarreau4781b152021-04-06 13:53:36 +0200532 _HA_ATOMIC_INC(&s->be->be_counters.failed_rewrites);
William Lallemand36119de2021-03-08 15:26:48 +0100533 if (sess->listener && sess->listener->counters)
Willy Tarreau4781b152021-04-06 13:53:36 +0200534 _HA_ATOMIC_INC(&sess->listener->counters->failed_rewrites);
Christopher Faulete00d06c2019-12-16 17:18:42 +0100535 if (objt_server(s->target))
Willy Tarreau4781b152021-04-06 13:53:36 +0200536 _HA_ATOMIC_INC(&__objt_server(s->target)->counters.failed_rewrites);
Christopher Faulete00d06c2019-12-16 17:18:42 +0100537
Christopher Faulet333bf8c2020-01-22 14:38:05 +0100538 if (!(s->txn->req.flags & HTTP_MSGF_SOFT_RW)) {
Christopher Faulete00d06c2019-12-16 17:18:42 +0100539 ret = ACT_RET_ERR;
Christopher Faulet333bf8c2020-01-22 14:38:05 +0100540 if (!(s->flags & SF_ERR_MASK))
541 s->flags |= SF_ERR_PRXCOND;
542 }
Christopher Faulete00d06c2019-12-16 17:18:42 +0100543 goto leave;
Willy Tarreau33810222019-06-12 17:44:02 +0200544}
545
Christopher Faulet312294f2020-09-02 17:17:44 +0200546/* parse a "replace-uri", "replace-path" or "replace-pathq"
547 * http-request action.
Willy Tarreau33810222019-06-12 17:44:02 +0200548 * This action takes 2 arguments (a regex and a replacement format string).
Christopher Faulet2c22a692019-12-18 15:39:56 +0100549 * The resulting rule makes use of <.action> to store the action (1/3 for now),
Christopher Faulet96bff762019-12-17 13:46:18 +0100550 * <http.re> to store the compiled regex, and <http.fmt> to store the log-format
Willy Tarreau33810222019-06-12 17:44:02 +0200551 * list head. It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
552 */
553static enum act_parse_ret parse_replace_uri(const char **args, int *orig_arg, struct proxy *px,
554 struct act_rule *rule, char **err)
555{
556 int cur_arg = *orig_arg;
557 char *error = NULL;
558
Christopher Faulet312294f2020-09-02 17:17:44 +0200559 switch (args[0][8]) {
560 case 'p':
561 if (args[0][12] == 'q')
562 rule->action = 4; // replace-pathq, same as set-pathq
563 else
564 rule->action = 1; // replace-path, same as set-path
565 break;
566 case 'u':
Christopher Faulet2c22a692019-12-18 15:39:56 +0100567 rule->action = 3; // replace-uri, same as set-uri
Christopher Faulet312294f2020-09-02 17:17:44 +0200568 break;
569 default:
570 memprintf(err, "internal error: unhandled action '%s'", args[0]);
571 return ACT_RET_PRS_ERR;
572 }
Willy Tarreau262c3f12019-12-17 06:52:51 +0100573
Willy Tarreau33810222019-06-12 17:44:02 +0200574 rule->action_ptr = http_action_replace_uri;
Christopher Faulet2eb53962020-01-14 14:47:34 +0100575 rule->release_ptr = release_http_action;
Willy Tarreau33810222019-06-12 17:44:02 +0200576
577 if (!*args[cur_arg] || !*args[cur_arg+1] ||
578 (*args[cur_arg+2] && strcmp(args[cur_arg+2], "if") != 0 && strcmp(args[cur_arg+2], "unless") != 0)) {
579 memprintf(err, "expects exactly 2 arguments <match-regex> and <replace-format>");
580 return ACT_RET_PRS_ERR;
581 }
582
Christopher Faulet96bff762019-12-17 13:46:18 +0100583 if (!(rule->arg.http.re = regex_comp(args[cur_arg], 1, 1, &error))) {
Willy Tarreau33810222019-06-12 17:44:02 +0200584 memprintf(err, "failed to parse the regex : %s", error);
585 free(error);
586 return ACT_RET_PRS_ERR;
587 }
588
Christopher Faulet96bff762019-12-17 13:46:18 +0100589 LIST_INIT(&rule->arg.http.fmt);
Willy Tarreau33810222019-06-12 17:44:02 +0200590 px->conf.args.ctx = ARGC_HRQ;
Christopher Faulet96bff762019-12-17 13:46:18 +0100591 if (!parse_logformat_string(args[cur_arg + 1], px, &rule->arg.http.fmt, LOG_OPT_HTTP,
Willy Tarreau33810222019-06-12 17:44:02 +0200592 (px->cap & PR_CAP_FE) ? SMP_VAL_FE_HRQ_HDR : SMP_VAL_BE_HRQ_HDR, err)) {
Christopher Faulet1337b322020-01-14 14:50:55 +0100593 regex_free(rule->arg.http.re);
Willy Tarreau33810222019-06-12 17:44:02 +0200594 return ACT_RET_PRS_ERR;
595 }
596
597 (*orig_arg) += 2;
598 return ACT_RET_PRS_OK;
599}
600
Willy Tarreau79e57332018-10-02 16:01:16 +0200601/* This function is just a compliant action wrapper for "set-status". */
602static enum act_return action_http_set_status(struct act_rule *rule, struct proxy *px,
603 struct session *sess, struct stream *s, int flags)
604{
Christopher Faulet96bff762019-12-17 13:46:18 +0100605 if (http_res_set_status(rule->arg.http.i, rule->arg.http.str, s) == -1) {
Willy Tarreau4781b152021-04-06 13:53:36 +0200606 _HA_ATOMIC_INC(&sess->fe->fe_counters.failed_rewrites);
Christopher Faulete00d06c2019-12-16 17:18:42 +0100607 if (s->flags & SF_BE_ASSIGNED)
Willy Tarreau4781b152021-04-06 13:53:36 +0200608 _HA_ATOMIC_INC(&s->be->be_counters.failed_rewrites);
William Lallemand36119de2021-03-08 15:26:48 +0100609 if (sess->listener && sess->listener->counters)
Willy Tarreau4781b152021-04-06 13:53:36 +0200610 _HA_ATOMIC_INC(&sess->listener->counters->failed_rewrites);
Christopher Faulete00d06c2019-12-16 17:18:42 +0100611 if (objt_server(s->target))
Willy Tarreau4781b152021-04-06 13:53:36 +0200612 _HA_ATOMIC_INC(&__objt_server(s->target)->counters.failed_rewrites);
Christopher Faulete00d06c2019-12-16 17:18:42 +0100613
Christopher Faulet333bf8c2020-01-22 14:38:05 +0100614 if (!(s->txn->req.flags & HTTP_MSGF_SOFT_RW)) {
Christopher Faulet333bf8c2020-01-22 14:38:05 +0100615 if (!(s->flags & SF_ERR_MASK))
616 s->flags |= SF_ERR_PRXCOND;
Christopher Faulet692a6c22020-02-07 10:22:31 +0100617 return ACT_RET_ERR;
Christopher Faulet333bf8c2020-01-22 14:38:05 +0100618 }
Christopher Faulete00d06c2019-12-16 17:18:42 +0100619 }
620
Willy Tarreau79e57332018-10-02 16:01:16 +0200621 return ACT_RET_CONT;
622}
623
624/* parse set-status action:
625 * This action accepts a single argument of type int representing
626 * an http status code. It returns ACT_RET_PRS_OK on success,
627 * ACT_RET_PRS_ERR on error.
628 */
629static enum act_parse_ret parse_http_set_status(const char **args, int *orig_arg, struct proxy *px,
630 struct act_rule *rule, char **err)
631{
632 char *error;
633
634 rule->action = ACT_CUSTOM;
635 rule->action_ptr = action_http_set_status;
Christopher Faulet2eb53962020-01-14 14:47:34 +0100636 rule->release_ptr = release_http_action;
Willy Tarreau79e57332018-10-02 16:01:16 +0200637
638 /* Check if an argument is available */
639 if (!*args[*orig_arg]) {
640 memprintf(err, "expects 1 argument: <status>; or 3 arguments: <status> reason <fmt>");
641 return ACT_RET_PRS_ERR;
642 }
643
644 /* convert status code as integer */
Christopher Faulet96bff762019-12-17 13:46:18 +0100645 rule->arg.http.i = strtol(args[*orig_arg], &error, 10);
646 if (*error != '\0' || rule->arg.http.i < 100 || rule->arg.http.i > 999) {
Willy Tarreau79e57332018-10-02 16:01:16 +0200647 memprintf(err, "expects an integer status code between 100 and 999");
648 return ACT_RET_PRS_ERR;
649 }
650
651 (*orig_arg)++;
652
653 /* set custom reason string */
Christopher Faulet96bff762019-12-17 13:46:18 +0100654 rule->arg.http.str = ist(NULL); // If null, we use the default reason for the status code.
Willy Tarreau79e57332018-10-02 16:01:16 +0200655 if (*args[*orig_arg] && strcmp(args[*orig_arg], "reason") == 0 &&
656 (*args[*orig_arg + 1] && strcmp(args[*orig_arg + 1], "if") != 0 && strcmp(args[*orig_arg + 1], "unless") != 0)) {
657 (*orig_arg)++;
Christopher Faulet96bff762019-12-17 13:46:18 +0100658 rule->arg.http.str.ptr = strdup(args[*orig_arg]);
659 rule->arg.http.str.len = strlen(rule->arg.http.str.ptr);
Willy Tarreau79e57332018-10-02 16:01:16 +0200660 (*orig_arg)++;
661 }
662
Christopher Fauletc20b3712020-01-27 15:51:56 +0100663 LIST_INIT(&rule->arg.http.fmt);
Willy Tarreau79e57332018-10-02 16:01:16 +0200664 return ACT_RET_PRS_OK;
665}
666
667/* This function executes the "reject" HTTP action. It clears the request and
668 * response buffer without sending any response. It can be useful as an HTTP
669 * alternative to the silent-drop action to defend against DoS attacks, and may
670 * also be used with HTTP/2 to close a connection instead of just a stream.
671 * The txn status is unchanged, indicating no response was sent. The termination
Christopher Faulet90d22a82020-03-06 11:18:39 +0100672 * flags will indicate "PR". It always returns ACT_RET_ABRT.
Willy Tarreau79e57332018-10-02 16:01:16 +0200673 */
674static enum act_return http_action_reject(struct act_rule *rule, struct proxy *px,
675 struct session *sess, struct stream *s, int flags)
676{
Willy Tarreau0f9cd7b2019-01-31 19:02:43 +0100677 si_must_kill_conn(chn_prod(&s->req));
Willy Tarreau79e57332018-10-02 16:01:16 +0200678 channel_abort(&s->req);
679 channel_abort(&s->res);
Christopher Fauletd4a824e2020-03-06 15:07:09 +0100680 s->req.analysers &= AN_REQ_FLT_END;
681 s->res.analysers &= AN_RES_FLT_END;
Willy Tarreau79e57332018-10-02 16:01:16 +0200682
Willy Tarreau4781b152021-04-06 13:53:36 +0200683 _HA_ATOMIC_INC(&s->be->be_counters.denied_req);
684 _HA_ATOMIC_INC(&sess->fe->fe_counters.denied_req);
Willy Tarreau79e57332018-10-02 16:01:16 +0200685 if (sess->listener && sess->listener->counters)
Willy Tarreau4781b152021-04-06 13:53:36 +0200686 _HA_ATOMIC_INC(&sess->listener->counters->denied_req);
Willy Tarreau79e57332018-10-02 16:01:16 +0200687
688 if (!(s->flags & SF_ERR_MASK))
689 s->flags |= SF_ERR_PRXCOND;
690 if (!(s->flags & SF_FINST_MASK))
691 s->flags |= SF_FINST_R;
692
Christopher Faulet90d22a82020-03-06 11:18:39 +0100693 return ACT_RET_ABRT;
Willy Tarreau79e57332018-10-02 16:01:16 +0200694}
695
696/* parse the "reject" action:
697 * This action takes no argument and returns ACT_RET_PRS_OK on success,
698 * ACT_RET_PRS_ERR on error.
699 */
700static enum act_parse_ret parse_http_action_reject(const char **args, int *orig_arg, struct proxy *px,
701 struct act_rule *rule, char **err)
702{
703 rule->action = ACT_CUSTOM;
704 rule->action_ptr = http_action_reject;
705 return ACT_RET_PRS_OK;
706}
707
Olivier Houchard602bf7d2019-05-10 13:59:15 +0200708/* This function executes the "disable-l7-retry" HTTP action.
709 * It disables L7 retries (all retry except for a connection failure). This
710 * can be useful for example to avoid retrying on POST requests.
711 * It just removes the L7 retry flag on the stream_interface, and always
712 * return ACT_RET_CONT;
713 */
714static enum act_return http_req_disable_l7_retry(struct act_rule *rule, struct proxy *px,
715 struct session *sess, struct stream *s, int flags)
716{
717 struct stream_interface *si = &s->si[1];
718
719 /* In theory, the SI_FL_L7_RETRY flags isn't set at this point, but
720 * let's be future-proof and remove it anyway.
721 */
722 si->flags &= ~SI_FL_L7_RETRY;
723 si->flags |= SI_FL_D_L7_RETRY;
724 return ACT_RET_CONT;
725}
726
727/* parse the "disable-l7-retry" action:
728 * This action takes no argument and returns ACT_RET_PRS_OK on success,
729 * ACT_RET_PRS_ERR on error.
730 */
731static enum act_parse_ret parse_http_req_disable_l7_retry(const char **args,
732 int *orig_args, struct proxy *px,
733 struct act_rule *rule, char **err)
734{
735 rule->action = ACT_CUSTOM;
736 rule->action_ptr = http_req_disable_l7_retry;
737 return ACT_RET_PRS_OK;
738}
739
Willy Tarreau79e57332018-10-02 16:01:16 +0200740/* This function executes the "capture" action. It executes a fetch expression,
741 * turns the result into a string and puts it in a capture slot. It always
742 * returns 1. If an error occurs the action is cancelled, but the rule
743 * processing continues.
744 */
745static enum act_return http_action_req_capture(struct act_rule *rule, struct proxy *px,
746 struct session *sess, struct stream *s, int flags)
747{
748 struct sample *key;
749 struct cap_hdr *h = rule->arg.cap.hdr;
750 char **cap = s->req_cap;
751 int len;
752
753 key = sample_fetch_as_type(s->be, sess, s, SMP_OPT_DIR_REQ|SMP_OPT_FINAL, rule->arg.cap.expr, SMP_T_STR);
754 if (!key)
755 return ACT_RET_CONT;
756
757 if (cap[h->index] == NULL)
758 cap[h->index] = pool_alloc(h->pool);
759
760 if (cap[h->index] == NULL) /* no more capture memory */
761 return ACT_RET_CONT;
762
763 len = key->data.u.str.data;
764 if (len > h->len)
765 len = h->len;
766
767 memcpy(cap[h->index], key->data.u.str.area, len);
768 cap[h->index][len] = 0;
769 return ACT_RET_CONT;
770}
771
772/* This function executes the "capture" action and store the result in a
773 * capture slot if exists. It executes a fetch expression, turns the result
774 * into a string and puts it in a capture slot. It always returns 1. If an
775 * error occurs the action is cancelled, but the rule processing continues.
776 */
777static enum act_return http_action_req_capture_by_id(struct act_rule *rule, struct proxy *px,
778 struct session *sess, struct stream *s, int flags)
779{
780 struct sample *key;
781 struct cap_hdr *h;
782 char **cap = s->req_cap;
783 struct proxy *fe = strm_fe(s);
784 int len;
785 int i;
786
787 /* Look for the original configuration. */
788 for (h = fe->req_cap, i = fe->nb_req_cap - 1;
789 h != NULL && i != rule->arg.capid.idx ;
790 i--, h = h->next);
791 if (!h)
792 return ACT_RET_CONT;
793
794 key = sample_fetch_as_type(s->be, sess, s, SMP_OPT_DIR_REQ|SMP_OPT_FINAL, rule->arg.capid.expr, SMP_T_STR);
795 if (!key)
796 return ACT_RET_CONT;
797
798 if (cap[h->index] == NULL)
799 cap[h->index] = pool_alloc(h->pool);
800
801 if (cap[h->index] == NULL) /* no more capture memory */
802 return ACT_RET_CONT;
803
804 len = key->data.u.str.data;
805 if (len > h->len)
806 len = h->len;
807
808 memcpy(cap[h->index], key->data.u.str.area, len);
809 cap[h->index][len] = 0;
810 return ACT_RET_CONT;
811}
812
813/* Check an "http-request capture" action.
814 *
815 * The function returns 1 in success case, otherwise, it returns 0 and err is
816 * filled.
817 */
818static int check_http_req_capture(struct act_rule *rule, struct proxy *px, char **err)
819{
820 if (rule->action_ptr != http_action_req_capture_by_id)
821 return 1;
822
Baptiste Assmann19a69b32020-01-16 14:34:22 +0100823 /* capture slots can only be declared in frontends, so we can't check their
824 * existence in backends at configuration parsing step
825 */
826 if (px->cap & PR_CAP_FE && rule->arg.capid.idx >= px->nb_req_cap) {
Willy Tarreau79e57332018-10-02 16:01:16 +0200827 memprintf(err, "unable to find capture id '%d' referenced by http-request capture rule",
828 rule->arg.capid.idx);
829 return 0;
830 }
831
832 return 1;
833}
834
Christopher Faulet2eb53962020-01-14 14:47:34 +0100835/* Release memory allocate by an http capture action */
836static void release_http_capture(struct act_rule *rule)
837{
838 if (rule->action_ptr == http_action_req_capture)
839 release_sample_expr(rule->arg.cap.expr);
840 else
841 release_sample_expr(rule->arg.capid.expr);
842}
843
Willy Tarreau79e57332018-10-02 16:01:16 +0200844/* parse an "http-request capture" action. It takes a single argument which is
845 * a sample fetch expression. It stores the expression into arg->act.p[0] and
846 * the allocated hdr_cap struct or the preallocated "id" into arg->act.p[1].
847 * It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
848 */
849static enum act_parse_ret parse_http_req_capture(const char **args, int *orig_arg, struct proxy *px,
850 struct act_rule *rule, char **err)
851{
852 struct sample_expr *expr;
853 struct cap_hdr *hdr;
854 int cur_arg;
855 int len = 0;
856
857 for (cur_arg = *orig_arg; cur_arg < *orig_arg + 3 && *args[cur_arg]; cur_arg++)
858 if (strcmp(args[cur_arg], "if") == 0 ||
859 strcmp(args[cur_arg], "unless") == 0)
860 break;
861
862 if (cur_arg < *orig_arg + 3) {
863 memprintf(err, "expects <expression> [ 'len' <length> | id <idx> ]");
864 return ACT_RET_PRS_ERR;
865 }
866
867 cur_arg = *orig_arg;
Willy Tarreaue3b57bf2020-02-14 16:50:14 +0100868 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 +0200869 if (!expr)
870 return ACT_RET_PRS_ERR;
871
872 if (!(expr->fetch->val & SMP_VAL_FE_HRQ_HDR)) {
873 memprintf(err,
874 "fetch method '%s' extracts information from '%s', none of which is available here",
875 args[cur_arg-1], sample_src_names(expr->fetch->use));
Christopher Faulet1337b322020-01-14 14:50:55 +0100876 release_sample_expr(expr);
Willy Tarreau79e57332018-10-02 16:01:16 +0200877 return ACT_RET_PRS_ERR;
878 }
879
880 if (!args[cur_arg] || !*args[cur_arg]) {
881 memprintf(err, "expects 'len or 'id'");
Christopher Faulet1337b322020-01-14 14:50:55 +0100882 release_sample_expr(expr);
Willy Tarreau79e57332018-10-02 16:01:16 +0200883 return ACT_RET_PRS_ERR;
884 }
885
886 if (strcmp(args[cur_arg], "len") == 0) {
887 cur_arg++;
888
889 if (!(px->cap & PR_CAP_FE)) {
890 memprintf(err, "proxy '%s' has no frontend capability", px->id);
Christopher Faulet1337b322020-01-14 14:50:55 +0100891 release_sample_expr(expr);
Willy Tarreau79e57332018-10-02 16:01:16 +0200892 return ACT_RET_PRS_ERR;
893 }
894
895 px->conf.args.ctx = ARGC_CAP;
896
897 if (!args[cur_arg]) {
898 memprintf(err, "missing length value");
Christopher Faulet1337b322020-01-14 14:50:55 +0100899 release_sample_expr(expr);
Willy Tarreau79e57332018-10-02 16:01:16 +0200900 return ACT_RET_PRS_ERR;
901 }
902 /* we copy the table name for now, it will be resolved later */
903 len = atoi(args[cur_arg]);
904 if (len <= 0) {
905 memprintf(err, "length must be > 0");
Christopher Faulet1337b322020-01-14 14:50:55 +0100906 release_sample_expr(expr);
Willy Tarreau79e57332018-10-02 16:01:16 +0200907 return ACT_RET_PRS_ERR;
908 }
909 cur_arg++;
910
Willy Tarreau79e57332018-10-02 16:01:16 +0200911 hdr = calloc(1, sizeof(*hdr));
912 hdr->next = px->req_cap;
913 hdr->name = NULL; /* not a header capture */
914 hdr->namelen = 0;
915 hdr->len = len;
916 hdr->pool = create_pool("caphdr", hdr->len + 1, MEM_F_SHARED);
917 hdr->index = px->nb_req_cap++;
918
919 px->req_cap = hdr;
920 px->to_log |= LW_REQHDR;
921
922 rule->action = ACT_CUSTOM;
923 rule->action_ptr = http_action_req_capture;
Christopher Faulet2eb53962020-01-14 14:47:34 +0100924 rule->release_ptr = release_http_capture;
Willy Tarreau79e57332018-10-02 16:01:16 +0200925 rule->arg.cap.expr = expr;
926 rule->arg.cap.hdr = hdr;
927 }
928
929 else if (strcmp(args[cur_arg], "id") == 0) {
930 int id;
931 char *error;
932
933 cur_arg++;
934
935 if (!args[cur_arg]) {
936 memprintf(err, "missing id value");
Christopher Faulet1337b322020-01-14 14:50:55 +0100937 release_sample_expr(expr);
Willy Tarreau79e57332018-10-02 16:01:16 +0200938 return ACT_RET_PRS_ERR;
939 }
940
941 id = strtol(args[cur_arg], &error, 10);
942 if (*error != '\0') {
943 memprintf(err, "cannot parse id '%s'", args[cur_arg]);
Christopher Faulet1337b322020-01-14 14:50:55 +0100944 release_sample_expr(expr);
Willy Tarreau79e57332018-10-02 16:01:16 +0200945 return ACT_RET_PRS_ERR;
946 }
947 cur_arg++;
948
949 px->conf.args.ctx = ARGC_CAP;
950
951 rule->action = ACT_CUSTOM;
952 rule->action_ptr = http_action_req_capture_by_id;
953 rule->check_ptr = check_http_req_capture;
Christopher Faulet2eb53962020-01-14 14:47:34 +0100954 rule->release_ptr = release_http_capture;
Willy Tarreau79e57332018-10-02 16:01:16 +0200955 rule->arg.capid.expr = expr;
956 rule->arg.capid.idx = id;
957 }
958
959 else {
960 memprintf(err, "expects 'len' or 'id', found '%s'", args[cur_arg]);
Christopher Faulet1337b322020-01-14 14:50:55 +0100961 release_sample_expr(expr);
Willy Tarreau79e57332018-10-02 16:01:16 +0200962 return ACT_RET_PRS_ERR;
963 }
964
965 *orig_arg = cur_arg;
966 return ACT_RET_PRS_OK;
967}
968
969/* This function executes the "capture" action and store the result in a
970 * capture slot if exists. It executes a fetch expression, turns the result
971 * into a string and puts it in a capture slot. It always returns 1. If an
972 * error occurs the action is cancelled, but the rule processing continues.
973 */
974static enum act_return http_action_res_capture_by_id(struct act_rule *rule, struct proxy *px,
975 struct session *sess, struct stream *s, int flags)
976{
977 struct sample *key;
978 struct cap_hdr *h;
979 char **cap = s->res_cap;
980 struct proxy *fe = strm_fe(s);
981 int len;
982 int i;
983
984 /* Look for the original configuration. */
985 for (h = fe->rsp_cap, i = fe->nb_rsp_cap - 1;
986 h != NULL && i != rule->arg.capid.idx ;
987 i--, h = h->next);
988 if (!h)
989 return ACT_RET_CONT;
990
991 key = sample_fetch_as_type(s->be, sess, s, SMP_OPT_DIR_RES|SMP_OPT_FINAL, rule->arg.capid.expr, SMP_T_STR);
992 if (!key)
993 return ACT_RET_CONT;
994
995 if (cap[h->index] == NULL)
996 cap[h->index] = pool_alloc(h->pool);
997
998 if (cap[h->index] == NULL) /* no more capture memory */
999 return ACT_RET_CONT;
1000
1001 len = key->data.u.str.data;
1002 if (len > h->len)
1003 len = h->len;
1004
1005 memcpy(cap[h->index], key->data.u.str.area, len);
1006 cap[h->index][len] = 0;
1007 return ACT_RET_CONT;
1008}
1009
1010/* Check an "http-response capture" action.
1011 *
1012 * The function returns 1 in success case, otherwise, it returns 0 and err is
1013 * filled.
1014 */
1015static int check_http_res_capture(struct act_rule *rule, struct proxy *px, char **err)
1016{
1017 if (rule->action_ptr != http_action_res_capture_by_id)
1018 return 1;
1019
Tim Duesterhusf3f4aa02020-07-03 13:43:42 +02001020 /* capture slots can only be declared in frontends, so we can't check their
1021 * existence in backends at configuration parsing step
1022 */
1023 if (px->cap & PR_CAP_FE && rule->arg.capid.idx >= px->nb_rsp_cap) {
Willy Tarreau79e57332018-10-02 16:01:16 +02001024 memprintf(err, "unable to find capture id '%d' referenced by http-response capture rule",
1025 rule->arg.capid.idx);
1026 return 0;
1027 }
1028
1029 return 1;
1030}
1031
1032/* parse an "http-response capture" action. It takes a single argument which is
1033 * a sample fetch expression. It stores the expression into arg->act.p[0] and
Thayne McCombs8f0cc5c2021-01-07 21:35:52 -07001034 * the allocated hdr_cap struct of the preallocated id into arg->act.p[1].
Willy Tarreau79e57332018-10-02 16:01:16 +02001035 * It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
1036 */
1037static enum act_parse_ret parse_http_res_capture(const char **args, int *orig_arg, struct proxy *px,
1038 struct act_rule *rule, char **err)
1039{
1040 struct sample_expr *expr;
1041 int cur_arg;
1042 int id;
1043 char *error;
1044
1045 for (cur_arg = *orig_arg; cur_arg < *orig_arg + 3 && *args[cur_arg]; cur_arg++)
1046 if (strcmp(args[cur_arg], "if") == 0 ||
1047 strcmp(args[cur_arg], "unless") == 0)
1048 break;
1049
1050 if (cur_arg < *orig_arg + 3) {
1051 memprintf(err, "expects <expression> id <idx>");
1052 return ACT_RET_PRS_ERR;
1053 }
1054
1055 cur_arg = *orig_arg;
Willy Tarreaue3b57bf2020-02-14 16:50:14 +01001056 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 +02001057 if (!expr)
1058 return ACT_RET_PRS_ERR;
1059
1060 if (!(expr->fetch->val & SMP_VAL_FE_HRS_HDR)) {
1061 memprintf(err,
1062 "fetch method '%s' extracts information from '%s', none of which is available here",
1063 args[cur_arg-1], sample_src_names(expr->fetch->use));
Christopher Faulet1337b322020-01-14 14:50:55 +01001064 release_sample_expr(expr);
Willy Tarreau79e57332018-10-02 16:01:16 +02001065 return ACT_RET_PRS_ERR;
1066 }
1067
1068 if (!args[cur_arg] || !*args[cur_arg]) {
1069 memprintf(err, "expects 'id'");
Christopher Faulet1337b322020-01-14 14:50:55 +01001070 release_sample_expr(expr);
Willy Tarreau79e57332018-10-02 16:01:16 +02001071 return ACT_RET_PRS_ERR;
1072 }
1073
1074 if (strcmp(args[cur_arg], "id") != 0) {
1075 memprintf(err, "expects 'id', found '%s'", args[cur_arg]);
Christopher Faulet1337b322020-01-14 14:50:55 +01001076 release_sample_expr(expr);
Willy Tarreau79e57332018-10-02 16:01:16 +02001077 return ACT_RET_PRS_ERR;
1078 }
1079
1080 cur_arg++;
1081
1082 if (!args[cur_arg]) {
1083 memprintf(err, "missing id value");
Christopher Faulet1337b322020-01-14 14:50:55 +01001084 release_sample_expr(expr);
Willy Tarreau79e57332018-10-02 16:01:16 +02001085 return ACT_RET_PRS_ERR;
1086 }
1087
1088 id = strtol(args[cur_arg], &error, 10);
1089 if (*error != '\0') {
1090 memprintf(err, "cannot parse id '%s'", args[cur_arg]);
Christopher Faulet1337b322020-01-14 14:50:55 +01001091 release_sample_expr(expr);
Willy Tarreau79e57332018-10-02 16:01:16 +02001092 return ACT_RET_PRS_ERR;
1093 }
1094 cur_arg++;
1095
1096 px->conf.args.ctx = ARGC_CAP;
1097
1098 rule->action = ACT_CUSTOM;
1099 rule->action_ptr = http_action_res_capture_by_id;
1100 rule->check_ptr = check_http_res_capture;
Christopher Faulet2eb53962020-01-14 14:47:34 +01001101 rule->release_ptr = release_http_capture;
Willy Tarreau79e57332018-10-02 16:01:16 +02001102 rule->arg.capid.expr = expr;
1103 rule->arg.capid.idx = id;
1104
1105 *orig_arg = cur_arg;
1106 return ACT_RET_PRS_OK;
1107}
1108
Christopher Faulet81e20172019-12-12 16:40:30 +01001109/* Parse a "allow" action for a request or a response rule. It takes no argument. It
1110 * returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
1111 */
1112static enum act_parse_ret parse_http_allow(const char **args, int *orig_arg, struct proxy *px,
1113 struct act_rule *rule, char **err)
1114{
1115 rule->action = ACT_ACTION_ALLOW;
Christopher Faulet245cf792019-12-18 14:58:12 +01001116 rule->flags |= ACT_FLAG_FINAL;
Christopher Faulet81e20172019-12-12 16:40:30 +01001117 return ACT_RET_PRS_OK;
1118}
1119
Christopher Faulete0fca292020-01-13 21:49:03 +01001120/* Parse "deny" or "tarpit" actions for a request rule or "deny" action for a
Christopher Faulet5cb513a2020-05-13 17:56:56 +02001121 * response rule. It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on
1122 * error. It relies on http_parse_http_reply() to set
1123 * <.arg.http_reply>.
Christopher Faulet81e20172019-12-12 16:40:30 +01001124 */
Christopher Faulete0fca292020-01-13 21:49:03 +01001125static enum act_parse_ret parse_http_deny(const char **args, int *orig_arg, struct proxy *px,
1126 struct act_rule *rule, char **err)
Christopher Faulet81e20172019-12-12 16:40:30 +01001127{
Christopher Faulet5cb513a2020-05-13 17:56:56 +02001128 int default_status;
1129 int cur_arg, arg = 0;
Christopher Faulet81e20172019-12-12 16:40:30 +01001130
1131 cur_arg = *orig_arg;
Christopher Faulete0fca292020-01-13 21:49:03 +01001132 if (rule->from == ACT_F_HTTP_REQ) {
Tim Duesterhuse5ff1412021-01-02 22:31:53 +01001133 if (strcmp(args[cur_arg - 1], "tarpit") == 0) {
Christopher Faulete0fca292020-01-13 21:49:03 +01001134 rule->action = ACT_HTTP_REQ_TARPIT;
Christopher Faulet5cb513a2020-05-13 17:56:56 +02001135 default_status = 500;
Christopher Faulet81e20172019-12-12 16:40:30 +01001136 }
Christopher Faulete0fca292020-01-13 21:49:03 +01001137 else {
1138 rule->action = ACT_ACTION_DENY;
Christopher Faulet5cb513a2020-05-13 17:56:56 +02001139 default_status = 403;
Christopher Faulet81e20172019-12-12 16:40:30 +01001140 }
Christopher Faulet81e20172019-12-12 16:40:30 +01001141 }
Christopher Faulete0fca292020-01-13 21:49:03 +01001142 else {
Christopher Faulet554c0eb2020-01-14 12:00:28 +01001143 rule->action = ACT_ACTION_DENY;
Christopher Faulet5cb513a2020-05-13 17:56:56 +02001144 default_status = 502;
Christopher Faulete0fca292020-01-13 21:49:03 +01001145 }
Christopher Faulet040c8cd2020-01-13 16:43:45 +01001146
Christopher Faulet5cb513a2020-05-13 17:56:56 +02001147 /* If no args or only a deny_status specified, fallback on the legacy
1148 * mode and use default error files despite the fact that
1149 * default-errorfiles is not used. Otherwise, parse an http reply.
1150 */
Christopher Faulet040c8cd2020-01-13 16:43:45 +01001151
Christopher Faulet5cb513a2020-05-13 17:56:56 +02001152 /* Prepare parsing of log-format strings */
1153 px->conf.args.ctx = ((rule->from == ACT_F_HTTP_REQ) ? ARGC_HRQ : ARGC_HRS);
Christopher Faulet554c0eb2020-01-14 12:00:28 +01001154
Christopher Faulet9467f182020-06-30 09:32:01 +02001155 if (!*(args[cur_arg]) || strcmp(args[cur_arg], "if") == 0 || strcmp(args[cur_arg], "unless") == 0) {
Christopher Faulet5cb513a2020-05-13 17:56:56 +02001156 rule->arg.http_reply = http_parse_http_reply((const char *[]){"default-errorfiles", ""}, &arg, px, default_status, err);
1157 goto end;
Christopher Faulet554c0eb2020-01-14 12:00:28 +01001158 }
Christopher Faulet5cb513a2020-05-13 17:56:56 +02001159
1160 if (strcmp(args[cur_arg], "deny_status") == 0) {
Christopher Faulet9467f182020-06-30 09:32:01 +02001161 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 +02001162 rule->arg.http_reply = http_parse_http_reply((const char *[]){"status", args[cur_arg+1], "default-errorfiles", ""},
1163 &arg, px, default_status, err);
1164 *orig_arg += 2;
1165 goto end;
Christopher Faulet554c0eb2020-01-14 12:00:28 +01001166 }
Christopher Faulet5cb513a2020-05-13 17:56:56 +02001167 args[cur_arg] += 5; /* skip "deny_" for the parsing */
Christopher Faulet554c0eb2020-01-14 12:00:28 +01001168 }
1169
Christopher Faulet5cb513a2020-05-13 17:56:56 +02001170 rule->arg.http_reply = http_parse_http_reply(args, orig_arg, px, default_status, err);
Christopher Faulet554c0eb2020-01-14 12:00:28 +01001171
Christopher Faulet5cb513a2020-05-13 17:56:56 +02001172 end:
1173 if (!rule->arg.http_reply)
1174 return ACT_RET_PRS_ERR;
1175
1176 rule->flags |= ACT_FLAG_FINAL;
1177 rule->check_ptr = check_act_http_reply;
1178 rule->release_ptr = release_act_http_reply;
Christopher Faulet81e20172019-12-12 16:40:30 +01001179 return ACT_RET_PRS_OK;
1180}
1181
Christopher Fauletb3048832020-05-27 15:26:43 +02001182
1183/* This function executes a auth action. It builds an 401/407 HTX message using
1184 * the corresponding proxy's error message. On success, it returns
1185 * ACT_RET_ABRT. If an error occurs ACT_RET_ERR is returned.
1186 */
1187static enum act_return http_action_auth(struct act_rule *rule, struct proxy *px,
1188 struct session *sess, struct stream *s, int flags)
1189{
1190 struct channel *req = &s->req;
1191 struct channel *res = &s->res;
1192 struct htx *htx = htx_from_buf(&res->buf);
1193 struct http_reply *reply;
1194 const char *auth_realm;
1195 struct http_hdr_ctx ctx;
1196 struct ist hdr;
1197
1198 /* Auth might be performed on regular http-req rules as well as on stats */
1199 auth_realm = rule->arg.http.str.ptr;
1200 if (!auth_realm) {
1201 if (px->uri_auth && s->current_rule_list == &px->uri_auth->http_req_rules)
1202 auth_realm = STATS_DEFAULT_REALM;
1203 else
1204 auth_realm = px->id;
1205 }
1206
1207 if (!(s->txn->flags & TX_USE_PX_CONN)) {
1208 s->txn->status = 401;
1209 hdr = ist("WWW-Authenticate");
1210 }
1211 else {
1212 s->txn->status = 407;
1213 hdr = ist("Proxy-Authenticate");
1214 }
1215 reply = http_error_message(s);
1216 channel_htx_truncate(res, htx);
1217
1218 if (chunk_printf(&trash, "Basic realm=\"%s\"", auth_realm) == -1)
1219 goto fail;
1220
1221 /* Write the generic 40x message */
1222 if (http_reply_to_htx(s, htx, reply) == -1)
1223 goto fail;
1224
1225 /* Remove all existing occurrences of the XXX-Authenticate header */
1226 ctx.blk = NULL;
1227 while (http_find_header(htx, hdr, &ctx, 1))
1228 http_remove_header(htx, &ctx);
1229
1230 /* Now a the right XXX-Authenticate header */
1231 if (!http_add_header(htx, hdr, ist2(b_orig(&trash), b_data(&trash))))
1232 goto fail;
1233
1234 /* Finally forward the reply */
1235 htx_to_buf(htx, &res->buf);
1236 if (!http_forward_proxy_resp(s, 1))
1237 goto fail;
1238
1239 /* Note: Only eval on the request */
1240 s->logs.tv_request = now;
1241 req->analysers &= AN_REQ_FLT_END;
1242
1243 if (s->sess->fe == s->be) /* report it if the request was intercepted by the frontend */
Willy Tarreau4781b152021-04-06 13:53:36 +02001244 _HA_ATOMIC_INC(&s->sess->fe->fe_counters.intercepted_req);
Christopher Fauletb3048832020-05-27 15:26:43 +02001245
1246 if (!(s->flags & SF_ERR_MASK))
1247 s->flags |= SF_ERR_LOCAL;
1248 if (!(s->flags & SF_FINST_MASK))
1249 s->flags |= SF_FINST_R;
1250
1251 stream_inc_http_err_ctr(s);
1252 return ACT_RET_ABRT;
1253
1254 fail:
1255 /* If an error occurred, remove the incomplete HTTP response from the
1256 * buffer */
1257 channel_htx_truncate(res, htx);
1258 return ACT_RET_ERR;
1259}
1260
Christopher Faulet81e20172019-12-12 16:40:30 +01001261/* Parse a "auth" action. It may take 2 optional arguments to define a "realm"
1262 * parameter. It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
1263 */
1264static enum act_parse_ret parse_http_auth(const char **args, int *orig_arg, struct proxy *px,
1265 struct act_rule *rule, char **err)
1266{
1267 int cur_arg;
1268
Christopher Fauletb3048832020-05-27 15:26:43 +02001269 rule->action = ACT_CUSTOM;
Christopher Faulet245cf792019-12-18 14:58:12 +01001270 rule->flags |= ACT_FLAG_FINAL;
Christopher Fauletb3048832020-05-27 15:26:43 +02001271 rule->action_ptr = http_action_auth;
Christopher Faulet2eb53962020-01-14 14:47:34 +01001272 rule->release_ptr = release_http_action;
Christopher Faulet81e20172019-12-12 16:40:30 +01001273
1274 cur_arg = *orig_arg;
Tim Duesterhuse5ff1412021-01-02 22:31:53 +01001275 if (strcmp(args[cur_arg], "realm") == 0) {
Christopher Faulet81e20172019-12-12 16:40:30 +01001276 cur_arg++;
1277 if (!*args[cur_arg]) {
1278 memprintf(err, "missing realm value.\n");
1279 return ACT_RET_PRS_ERR;
1280 }
Christopher Faulet96bff762019-12-17 13:46:18 +01001281 rule->arg.http.str.ptr = strdup(args[cur_arg]);
1282 rule->arg.http.str.len = strlen(rule->arg.http.str.ptr);
Christopher Faulet81e20172019-12-12 16:40:30 +01001283 cur_arg++;
1284 }
1285
Christopher Fauletc20b3712020-01-27 15:51:56 +01001286 LIST_INIT(&rule->arg.http.fmt);
Christopher Faulet81e20172019-12-12 16:40:30 +01001287 *orig_arg = cur_arg;
1288 return ACT_RET_PRS_OK;
1289}
1290
1291/* Parse a "set-nice" action. It takes the nice value as argument. It returns
1292 * ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
1293 */
1294static enum act_parse_ret parse_http_set_nice(const char **args, int *orig_arg, struct proxy *px,
1295 struct act_rule *rule, char **err)
1296{
1297 int cur_arg;
1298
1299 rule->action = ACT_HTTP_SET_NICE;
1300
1301 cur_arg = *orig_arg;
1302 if (!*args[cur_arg]) {
1303 memprintf(err, "expects exactly 1 argument (integer value)");
1304 return ACT_RET_PRS_ERR;
1305 }
Christopher Faulet96bff762019-12-17 13:46:18 +01001306 rule->arg.http.i = atoi(args[cur_arg]);
1307 if (rule->arg.http.i < -1024)
1308 rule->arg.http.i = -1024;
1309 else if (rule->arg.http.i > 1024)
1310 rule->arg.http.i = 1024;
Christopher Faulet81e20172019-12-12 16:40:30 +01001311
Christopher Fauletc20b3712020-01-27 15:51:56 +01001312 LIST_INIT(&rule->arg.http.fmt);
Christopher Faulet81e20172019-12-12 16:40:30 +01001313 *orig_arg = cur_arg + 1;
1314 return ACT_RET_PRS_OK;
1315}
1316
1317/* Parse a "set-tos" action. It takes the TOS value as argument. It returns
1318 * ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
1319 */
1320static enum act_parse_ret parse_http_set_tos(const char **args, int *orig_arg, struct proxy *px,
1321 struct act_rule *rule, char **err)
1322{
1323#ifdef IP_TOS
1324 char *endp;
1325 int cur_arg;
1326
1327 rule->action = ACT_HTTP_SET_TOS;
1328
1329 cur_arg = *orig_arg;
1330 if (!*args[cur_arg]) {
1331 memprintf(err, "expects exactly 1 argument (integer/hex value)");
1332 return ACT_RET_PRS_ERR;
1333 }
Christopher Faulet96bff762019-12-17 13:46:18 +01001334 rule->arg.http.i = strtol(args[cur_arg], &endp, 0);
Christopher Faulet81e20172019-12-12 16:40:30 +01001335 if (endp && *endp != '\0') {
1336 memprintf(err, "invalid character starting at '%s' (integer/hex value expected)", endp);
1337 return ACT_RET_PRS_ERR;
1338 }
1339
Christopher Fauletc20b3712020-01-27 15:51:56 +01001340 LIST_INIT(&rule->arg.http.fmt);
Christopher Faulet81e20172019-12-12 16:40:30 +01001341 *orig_arg = cur_arg + 1;
1342 return ACT_RET_PRS_OK;
1343#else
1344 memprintf(err, "not supported on this platform (IP_TOS undefined)");
1345 return ACT_RET_PRS_ERR;
1346#endif
1347}
1348
1349/* Parse a "set-mark" action. It takes the MARK value as argument. It returns
1350 * ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
1351 */
1352static enum act_parse_ret parse_http_set_mark(const char **args, int *orig_arg, struct proxy *px,
1353 struct act_rule *rule, char **err)
1354{
1355#ifdef SO_MARK
1356 char *endp;
1357 int cur_arg;
1358
1359 rule->action = ACT_HTTP_SET_MARK;
1360
1361 cur_arg = *orig_arg;
1362 if (!*args[cur_arg]) {
1363 memprintf(err, "expects exactly 1 argument (integer/hex value)");
1364 return ACT_RET_PRS_ERR;
1365 }
Christopher Faulet96bff762019-12-17 13:46:18 +01001366 rule->arg.http.i = strtoul(args[cur_arg], &endp, 0);
Christopher Faulet81e20172019-12-12 16:40:30 +01001367 if (endp && *endp != '\0') {
1368 memprintf(err, "invalid character starting at '%s' (integer/hex value expected)", endp);
1369 return ACT_RET_PRS_ERR;
1370 }
1371
Christopher Fauletc20b3712020-01-27 15:51:56 +01001372 LIST_INIT(&rule->arg.http.fmt);
Christopher Faulet81e20172019-12-12 16:40:30 +01001373 *orig_arg = cur_arg + 1;
1374 global.last_checks |= LSTCHK_NETADM;
1375 return ACT_RET_PRS_OK;
1376#else
1377 memprintf(err, "not supported on this platform (SO_MARK undefined)");
1378 return ACT_RET_PRS_ERR;
1379#endif
1380}
1381
1382/* Parse a "set-log-level" action. It takes the level value as argument. It
1383 * returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
1384 */
1385static enum act_parse_ret parse_http_set_log_level(const char **args, int *orig_arg, struct proxy *px,
1386 struct act_rule *rule, char **err)
1387{
1388 int cur_arg;
1389
1390 rule->action = ACT_HTTP_SET_LOGL;
1391
1392 cur_arg = *orig_arg;
1393 if (!*args[cur_arg]) {
1394 bad_log_level:
1395 memprintf(err, "expects exactly 1 argument (log level name or 'silent')");
1396 return ACT_RET_PRS_ERR;
1397 }
1398 if (strcmp(args[cur_arg], "silent") == 0)
Christopher Faulet96bff762019-12-17 13:46:18 +01001399 rule->arg.http.i = -1;
1400 else if ((rule->arg.http.i = get_log_level(args[cur_arg]) + 1) == 0)
Christopher Faulet81e20172019-12-12 16:40:30 +01001401 goto bad_log_level;
1402
Christopher Fauletc20b3712020-01-27 15:51:56 +01001403 LIST_INIT(&rule->arg.http.fmt);
Christopher Faulet81e20172019-12-12 16:40:30 +01001404 *orig_arg = cur_arg + 1;
1405 return ACT_RET_PRS_OK;
1406}
1407
Christopher Faulet91b3ec12020-01-17 22:30:06 +01001408/* This function executes a early-hint action. It adds an HTTP Early Hint HTTP
1409 * 103 response header with <.arg.http.str> name and with a value built
1410 * according to <.arg.http.fmt> log line format. If it is the first early-hint
Ilya Shipitsinc02a23f2020-05-06 00:53:22 +05001411 * rule of series, the 103 response start-line is added first. At the end, if
Christopher Faulet91b3ec12020-01-17 22:30:06 +01001412 * the next rule is not an early-hint rule or if it is the last rule, the EOH
1413 * block is added to terminate the response. On success, it returns
1414 * ACT_RET_CONT. If an error occurs while soft rewrites are enabled, the action
1415 * is canceled, but the rule processing continue. Otherwsize ACT_RET_ERR is
1416 * returned.
1417 */
1418static enum act_return http_action_early_hint(struct act_rule *rule, struct proxy *px,
1419 struct session *sess, struct stream *s, int flags)
1420{
1421 struct act_rule *prev_rule, *next_rule;
1422 struct channel *res = &s->res;
1423 struct htx *htx = htx_from_buf(&res->buf);
1424 struct buffer *value = alloc_trash_chunk();
1425 enum act_return ret = ACT_RET_CONT;
1426
1427 if (!(s->txn->req.flags & HTTP_MSGF_VER_11))
1428 goto leave;
1429
1430 if (!value) {
1431 if (!(s->flags & SF_ERR_MASK))
1432 s->flags |= SF_ERR_RESOURCE;
1433 goto error;
1434 }
1435
1436 /* get previous and next rules */
1437 prev_rule = LIST_PREV(&rule->list, typeof(rule), list);
1438 next_rule = LIST_NEXT(&rule->list, typeof(rule), list);
1439
1440 /* if no previous rule or previous rule is not early-hint, start a new response. Otherwise,
1441 * continue to add link to a previously started response */
1442 if (&prev_rule->list == s->current_rule_list || prev_rule->action_ptr != http_action_early_hint) {
1443 struct htx_sl *sl;
1444 unsigned int flags = (HTX_SL_F_IS_RESP|HTX_SL_F_VER_11|
1445 HTX_SL_F_XFER_LEN|HTX_SL_F_BODYLESS);
1446
1447 sl = htx_add_stline(htx, HTX_BLK_RES_SL, flags,
1448 ist("HTTP/1.1"), ist("103"), ist("Early Hints"));
1449 if (!sl)
1450 goto error;
1451 sl->info.res.status = 103;
1452 }
1453
1454 /* Add the HTTP Early Hint HTTP 103 response heade */
1455 value->data = build_logline(s, b_tail(value), b_room(value), &rule->arg.http.fmt);
1456 if (!htx_add_header(htx, rule->arg.http.str, ist2(b_head(value), b_data(value))))
1457 goto error;
1458
1459 /* if it is the last rule or the next one is not an early-hint, terminate the current
1460 * response. */
1461 if (&next_rule->list == s->current_rule_list || next_rule->action_ptr != http_action_early_hint) {
Christopher Faulet91b3ec12020-01-17 22:30:06 +01001462 if (!htx_add_endof(htx, HTX_BLK_EOH)) {
1463 /* If an error occurred during an Early-hint rule,
1464 * remove the incomplete HTTP 103 response from the
1465 * buffer */
1466 goto error;
1467 }
1468
Christopher Fauleta72a7e42020-01-28 09:28:11 +01001469 if (!http_forward_proxy_resp(s, 0))
1470 goto error;
Christopher Faulet91b3ec12020-01-17 22:30:06 +01001471 }
1472
1473 leave:
1474 free_trash_chunk(value);
1475 return ret;
1476
1477 error:
1478 /* If an error occurred during an Early-hint rule, remove the incomplete
1479 * HTTP 103 response from the buffer */
1480 channel_htx_truncate(res, htx);
1481 ret = ACT_RET_ERR;
1482 goto leave;
1483}
1484
Christopher Fauletd1f27e32019-12-17 09:33:38 +01001485/* This function executes a set-header or add-header actions. It builds a string
1486 * in the trash from the specified format string. It finds the action to be
1487 * performed in <.action>, previously filled by function parse_set_header(). The
Ilya Shipitsinc02a23f2020-05-06 00:53:22 +05001488 * replacement action is executed by the function http_action_set_header(). On
Christopher Fauletd1f27e32019-12-17 09:33:38 +01001489 * success, it returns ACT_RET_CONT. If an error occurs while soft rewrites are
1490 * enabled, the action is canceled, but the rule processing continue. Otherwsize
1491 * ACT_RET_ERR is returned.
1492 */
1493static enum act_return http_action_set_header(struct act_rule *rule, struct proxy *px,
1494 struct session *sess, struct stream *s, int flags)
1495{
Christopher Faulet91e31d82020-01-24 15:37:13 +01001496 struct http_msg *msg = ((rule->from == ACT_F_HTTP_REQ) ? &s->txn->req : &s->txn->rsp);
1497 struct htx *htx = htxbuf(&msg->chn->buf);
Christopher Fauletd1f27e32019-12-17 09:33:38 +01001498 enum act_return ret = ACT_RET_CONT;
1499 struct buffer *replace;
1500 struct http_hdr_ctx ctx;
1501 struct ist n, v;
1502
1503 replace = alloc_trash_chunk();
1504 if (!replace)
1505 goto fail_alloc;
1506
1507 replace->data = build_logline(s, replace->area, replace->size, &rule->arg.http.fmt);
1508 n = rule->arg.http.str;
1509 v = ist2(replace->area, replace->data);
1510
1511 if (rule->action == 0) { // set-header
1512 /* remove all occurrences of the header */
1513 ctx.blk = NULL;
1514 while (http_find_header(htx, n, &ctx, 1))
1515 http_remove_header(htx, &ctx);
1516 }
1517
1518 /* Now add header */
1519 if (!http_add_header(htx, n, v))
1520 goto fail_rewrite;
1521
1522 leave:
1523 free_trash_chunk(replace);
1524 return ret;
1525
1526 fail_alloc:
1527 if (!(s->flags & SF_ERR_MASK))
1528 s->flags |= SF_ERR_RESOURCE;
1529 ret = ACT_RET_ERR;
1530 goto leave;
1531
1532 fail_rewrite:
Willy Tarreau4781b152021-04-06 13:53:36 +02001533 _HA_ATOMIC_INC(&sess->fe->fe_counters.failed_rewrites);
Christopher Fauletd1f27e32019-12-17 09:33:38 +01001534 if (s->flags & SF_BE_ASSIGNED)
Willy Tarreau4781b152021-04-06 13:53:36 +02001535 _HA_ATOMIC_INC(&s->be->be_counters.failed_rewrites);
William Lallemand36119de2021-03-08 15:26:48 +01001536 if (sess->listener && sess->listener->counters)
Willy Tarreau4781b152021-04-06 13:53:36 +02001537 _HA_ATOMIC_INC(&sess->listener->counters->failed_rewrites);
Christopher Fauletd1f27e32019-12-17 09:33:38 +01001538 if (objt_server(s->target))
Willy Tarreau4781b152021-04-06 13:53:36 +02001539 _HA_ATOMIC_INC(&__objt_server(s->target)->counters.failed_rewrites);
Christopher Fauletd1f27e32019-12-17 09:33:38 +01001540
Christopher Faulet333bf8c2020-01-22 14:38:05 +01001541 if (!(msg->flags & HTTP_MSGF_SOFT_RW)) {
Christopher Fauletd1f27e32019-12-17 09:33:38 +01001542 ret = ACT_RET_ERR;
Christopher Faulet333bf8c2020-01-22 14:38:05 +01001543 if (!(s->flags & SF_ERR_MASK))
1544 s->flags |= SF_ERR_PRXCOND;
1545 }
Christopher Fauletd1f27e32019-12-17 09:33:38 +01001546 goto leave;
1547}
1548
Christopher Faulet81e20172019-12-12 16:40:30 +01001549/* Parse a "set-header", "add-header" or "early-hint" actions. It takes an
1550 * header name and a log-format string as arguments. It returns ACT_RET_PRS_OK
1551 * on success, ACT_RET_PRS_ERR on error.
1552 *
1553 * Note: same function is used for the request and the response. However
1554 * "early-hint" rules are only supported for request rules.
1555 */
1556static enum act_parse_ret parse_http_set_header(const char **args, int *orig_arg, struct proxy *px,
1557 struct act_rule *rule, char **err)
1558{
Christopher Faulet81e20172019-12-12 16:40:30 +01001559 int cap, cur_arg;
1560
Christopher Faulet91b3ec12020-01-17 22:30:06 +01001561 if (args[*orig_arg-1][0] == 'e') {
1562 rule->action = ACT_CUSTOM;
1563 rule->action_ptr = http_action_early_hint;
1564 }
Christopher Fauletd1f27e32019-12-17 09:33:38 +01001565 else {
1566 if (args[*orig_arg-1][0] == 's')
1567 rule->action = 0; // set-header
1568 else
1569 rule->action = 1; // add-header
1570 rule->action_ptr = http_action_set_header;
1571 }
Christopher Faulet2eb53962020-01-14 14:47:34 +01001572 rule->release_ptr = release_http_action;
Christopher Faulet81e20172019-12-12 16:40:30 +01001573
1574 cur_arg = *orig_arg;
1575 if (!*args[cur_arg] || !*args[cur_arg+1]) {
1576 memprintf(err, "expects exactly 2 arguments");
1577 return ACT_RET_PRS_ERR;
1578 }
1579
Christopher Faulet81e20172019-12-12 16:40:30 +01001580
Christopher Faulet96bff762019-12-17 13:46:18 +01001581 rule->arg.http.str.ptr = strdup(args[cur_arg]);
1582 rule->arg.http.str.len = strlen(rule->arg.http.str.ptr);
1583 LIST_INIT(&rule->arg.http.fmt);
Christopher Faulet81e20172019-12-12 16:40:30 +01001584
1585 if (rule->from == ACT_F_HTTP_REQ) {
1586 px->conf.args.ctx = ARGC_HRQ;
1587 cap = (px->cap & PR_CAP_FE) ? SMP_VAL_FE_HRQ_HDR : SMP_VAL_BE_HRQ_HDR;
1588 }
1589 else{
1590 px->conf.args.ctx = ARGC_HRS;
1591 cap = (px->cap & PR_CAP_BE) ? SMP_VAL_BE_HRS_HDR : SMP_VAL_FE_HRS_HDR;
1592 }
1593
1594 cur_arg++;
Christopher Faulet1337b322020-01-14 14:50:55 +01001595 if (!parse_logformat_string(args[cur_arg], px, &rule->arg.http.fmt, LOG_OPT_HTTP, cap, err)) {
Tim Duesterhused526372020-03-05 17:56:33 +01001596 istfree(&rule->arg.http.str);
Christopher Faulet81e20172019-12-12 16:40:30 +01001597 return ACT_RET_PRS_ERR;
Christopher Faulet1337b322020-01-14 14:50:55 +01001598 }
Christopher Faulet81e20172019-12-12 16:40:30 +01001599
1600 free(px->conf.lfs_file);
1601 px->conf.lfs_file = strdup(px->conf.args.file);
1602 px->conf.lfs_line = px->conf.args.line;
1603
1604 *orig_arg = cur_arg + 1;
1605 return ACT_RET_PRS_OK;
1606}
1607
Christopher Faulet92d34fe2019-12-17 09:20:34 +01001608/* This function executes a replace-header or replace-value actions. It
1609 * builds a string in the trash from the specified format string. It finds
1610 * the action to be performed in <.action>, previously filled by function
Ilya Shipitsinc02a23f2020-05-06 00:53:22 +05001611 * parse_replace_header(). The replacement action is executed by the function
Christopher Faulet92d34fe2019-12-17 09:20:34 +01001612 * http_action_replace_header(). On success, it returns ACT_RET_CONT. If an error
1613 * occurs while soft rewrites are enabled, the action is canceled, but the rule
1614 * processing continue. Otherwsize ACT_RET_ERR is returned.
1615 */
1616static enum act_return http_action_replace_header(struct act_rule *rule, struct proxy *px,
1617 struct session *sess, struct stream *s, int flags)
1618{
Christopher Faulet91e31d82020-01-24 15:37:13 +01001619 struct http_msg *msg = ((rule->from == ACT_F_HTTP_REQ) ? &s->txn->req : &s->txn->rsp);
1620 struct htx *htx = htxbuf(&msg->chn->buf);
Christopher Faulet92d34fe2019-12-17 09:20:34 +01001621 enum act_return ret = ACT_RET_CONT;
1622 struct buffer *replace;
1623 int r;
1624
1625 replace = alloc_trash_chunk();
1626 if (!replace)
1627 goto fail_alloc;
1628
1629 replace->data = build_logline(s, replace->area, replace->size, &rule->arg.http.fmt);
1630
1631 r = http_replace_hdrs(s, htx, rule->arg.http.str, replace->area, rule->arg.http.re, (rule->action == 0));
1632 if (r == -1)
1633 goto fail_rewrite;
1634
1635 leave:
1636 free_trash_chunk(replace);
1637 return ret;
1638
1639 fail_alloc:
1640 if (!(s->flags & SF_ERR_MASK))
1641 s->flags |= SF_ERR_RESOURCE;
1642 ret = ACT_RET_ERR;
1643 goto leave;
1644
1645 fail_rewrite:
Willy Tarreau4781b152021-04-06 13:53:36 +02001646 _HA_ATOMIC_INC(&sess->fe->fe_counters.failed_rewrites);
Christopher Faulet92d34fe2019-12-17 09:20:34 +01001647 if (s->flags & SF_BE_ASSIGNED)
Willy Tarreau4781b152021-04-06 13:53:36 +02001648 _HA_ATOMIC_INC(&s->be->be_counters.failed_rewrites);
William Lallemand36119de2021-03-08 15:26:48 +01001649 if (sess->listener && sess->listener->counters)
Willy Tarreau4781b152021-04-06 13:53:36 +02001650 _HA_ATOMIC_INC(&sess->listener->counters->failed_rewrites);
Christopher Faulet92d34fe2019-12-17 09:20:34 +01001651 if (objt_server(s->target))
Willy Tarreau4781b152021-04-06 13:53:36 +02001652 _HA_ATOMIC_INC(&__objt_server(s->target)->counters.failed_rewrites);
Christopher Faulet92d34fe2019-12-17 09:20:34 +01001653
Christopher Faulet333bf8c2020-01-22 14:38:05 +01001654 if (!(msg->flags & HTTP_MSGF_SOFT_RW)) {
Christopher Faulet92d34fe2019-12-17 09:20:34 +01001655 ret = ACT_RET_ERR;
Christopher Faulet333bf8c2020-01-22 14:38:05 +01001656 if (!(s->flags & SF_ERR_MASK))
1657 s->flags |= SF_ERR_PRXCOND;
1658 }
Christopher Faulet92d34fe2019-12-17 09:20:34 +01001659 goto leave;
1660}
1661
Christopher Faulet81e20172019-12-12 16:40:30 +01001662/* Parse a "replace-header" or "replace-value" actions. It takes an header name,
1663 * a regex and replacement string as arguments. It returns ACT_RET_PRS_OK on
1664 * success, ACT_RET_PRS_ERR on error.
1665 */
1666static enum act_parse_ret parse_http_replace_header(const char **args, int *orig_arg, struct proxy *px,
1667 struct act_rule *rule, char **err)
1668{
1669 int cap, cur_arg;
1670
Christopher Faulet92d34fe2019-12-17 09:20:34 +01001671 if (args[*orig_arg-1][8] == 'h')
1672 rule->action = 0; // replace-header
1673 else
1674 rule->action = 1; // replace-value
1675 rule->action_ptr = http_action_replace_header;
Christopher Faulet2eb53962020-01-14 14:47:34 +01001676 rule->release_ptr = release_http_action;
Christopher Faulet81e20172019-12-12 16:40:30 +01001677
1678 cur_arg = *orig_arg;
1679 if (!*args[cur_arg] || !*args[cur_arg+1] || !*args[cur_arg+2]) {
1680 memprintf(err, "expects exactly 3 arguments");
1681 return ACT_RET_PRS_ERR;
1682 }
1683
Christopher Faulet96bff762019-12-17 13:46:18 +01001684 rule->arg.http.str.ptr = strdup(args[cur_arg]);
1685 rule->arg.http.str.len = strlen(rule->arg.http.str.ptr);
1686 LIST_INIT(&rule->arg.http.fmt);
Christopher Faulet81e20172019-12-12 16:40:30 +01001687
1688 cur_arg++;
Christopher Faulet1337b322020-01-14 14:50:55 +01001689 if (!(rule->arg.http.re = regex_comp(args[cur_arg], 1, 1, err))) {
Tim Duesterhused526372020-03-05 17:56:33 +01001690 istfree(&rule->arg.http.str);
Christopher Faulet81e20172019-12-12 16:40:30 +01001691 return ACT_RET_PRS_ERR;
Christopher Faulet1337b322020-01-14 14:50:55 +01001692 }
Christopher Faulet81e20172019-12-12 16:40:30 +01001693
1694 if (rule->from == ACT_F_HTTP_REQ) {
1695 px->conf.args.ctx = ARGC_HRQ;
1696 cap = (px->cap & PR_CAP_FE) ? SMP_VAL_FE_HRQ_HDR : SMP_VAL_BE_HRQ_HDR;
1697 }
1698 else{
1699 px->conf.args.ctx = ARGC_HRS;
1700 cap = (px->cap & PR_CAP_BE) ? SMP_VAL_BE_HRS_HDR : SMP_VAL_FE_HRS_HDR;
1701 }
1702
1703 cur_arg++;
Christopher Faulet1337b322020-01-14 14:50:55 +01001704 if (!parse_logformat_string(args[cur_arg], px, &rule->arg.http.fmt, LOG_OPT_HTTP, cap, err)) {
Tim Duesterhused526372020-03-05 17:56:33 +01001705 istfree(&rule->arg.http.str);
Christopher Faulet1337b322020-01-14 14:50:55 +01001706 regex_free(rule->arg.http.re);
Christopher Faulet81e20172019-12-12 16:40:30 +01001707 return ACT_RET_PRS_ERR;
Christopher Faulet1337b322020-01-14 14:50:55 +01001708 }
Christopher Faulet81e20172019-12-12 16:40:30 +01001709
1710 free(px->conf.lfs_file);
1711 px->conf.lfs_file = strdup(px->conf.args.file);
1712 px->conf.lfs_line = px->conf.args.line;
1713
1714 *orig_arg = cur_arg + 1;
1715 return ACT_RET_PRS_OK;
1716}
1717
Maciej Zdebebdd4c52020-11-20 13:58:48 +00001718/* This function executes a del-header action with selected matching mode for
1719 * header name. It finds the matching method to be performed in <.action>, previously
1720 * filled by function parse_http_del_header(). On success, it returns ACT_RET_CONT.
1721 * Otherwise ACT_RET_ERR is returned.
1722 */
1723static enum act_return http_action_del_header(struct act_rule *rule, struct proxy *px,
1724 struct session *sess, struct stream *s, int flags)
1725{
1726 struct http_hdr_ctx ctx;
1727 struct http_msg *msg = ((rule->from == ACT_F_HTTP_REQ) ? &s->txn->req : &s->txn->rsp);
1728 struct htx *htx = htxbuf(&msg->chn->buf);
1729 enum act_return ret = ACT_RET_CONT;
1730
1731 /* remove all occurrences of the header */
1732 ctx.blk = NULL;
1733 switch (rule->action) {
1734 case PAT_MATCH_STR:
1735 while (http_find_header(htx, rule->arg.http.str, &ctx, 1))
1736 http_remove_header(htx, &ctx);
1737 break;
1738 case PAT_MATCH_BEG:
1739 while (http_find_pfx_header(htx, rule->arg.http.str, &ctx, 1))
1740 http_remove_header(htx, &ctx);
1741 break;
1742 case PAT_MATCH_END:
1743 while (http_find_sfx_header(htx, rule->arg.http.str, &ctx, 1))
1744 http_remove_header(htx, &ctx);
1745 break;
1746 case PAT_MATCH_SUB:
1747 while (http_find_sub_header(htx, rule->arg.http.str, &ctx, 1))
1748 http_remove_header(htx, &ctx);
1749 break;
1750 case PAT_MATCH_REG:
1751 while (http_match_header(htx, rule->arg.http.re, &ctx, 1))
1752 http_remove_header(htx, &ctx);
1753 break;
1754 default:
1755 return ACT_RET_ERR;
1756 }
1757 return ret;
1758}
1759
1760/* Parse a "del-header" action. It takes string as a required argument,
1761 * optional flag (currently only -m) and optional matching method of input string
1762 * with header name to be deleted. Default matching method is exact match (-m str).
1763 * It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
Christopher Faulet81e20172019-12-12 16:40:30 +01001764 */
1765static enum act_parse_ret parse_http_del_header(const char **args, int *orig_arg, struct proxy *px,
1766 struct act_rule *rule, char **err)
1767{
1768 int cur_arg;
Maciej Zdebebdd4c52020-11-20 13:58:48 +00001769 int pat_idx;
Christopher Faulet81e20172019-12-12 16:40:30 +01001770
Maciej Zdebebdd4c52020-11-20 13:58:48 +00001771 /* set exact matching (-m str) as default */
1772 rule->action = PAT_MATCH_STR;
1773 rule->action_ptr = http_action_del_header;
Christopher Faulet2eb53962020-01-14 14:47:34 +01001774 rule->release_ptr = release_http_action;
Christopher Faulet81e20172019-12-12 16:40:30 +01001775
1776 cur_arg = *orig_arg;
1777 if (!*args[cur_arg]) {
Maciej Zdebebdd4c52020-11-20 13:58:48 +00001778 memprintf(err, "expects at least 1 argument");
Christopher Faulet81e20172019-12-12 16:40:30 +01001779 return ACT_RET_PRS_ERR;
1780 }
1781
Christopher Faulet96bff762019-12-17 13:46:18 +01001782 rule->arg.http.str.ptr = strdup(args[cur_arg]);
1783 rule->arg.http.str.len = strlen(rule->arg.http.str.ptr);
Christopher Faulet81e20172019-12-12 16:40:30 +01001784 px->conf.args.ctx = (rule->from == ACT_F_HTTP_REQ ? ARGC_HRQ : ARGC_HRS);
1785
Maciej Zdeb6dee9962020-11-23 16:03:09 +00001786 LIST_INIT(&rule->arg.http.fmt);
Maciej Zdebebdd4c52020-11-20 13:58:48 +00001787 if (strcmp(args[cur_arg+1], "-m") == 0) {
1788 cur_arg++;
1789 if (!*args[cur_arg+1]) {
1790 memprintf(err, "-m flag expects exactly 1 argument");
1791 return ACT_RET_PRS_ERR;
1792 }
1793
1794 cur_arg++;
1795 pat_idx = pat_find_match_name(args[cur_arg]);
1796 switch (pat_idx) {
1797 case PAT_MATCH_REG:
1798 if (!(rule->arg.http.re = regex_comp(rule->arg.http.str.ptr, 1, 1, err)))
1799 return ACT_RET_PRS_ERR;
1800 /* fall through */
1801 case PAT_MATCH_STR:
1802 case PAT_MATCH_BEG:
1803 case PAT_MATCH_END:
1804 case PAT_MATCH_SUB:
1805 rule->action = pat_idx;
1806 break;
1807 default:
1808 memprintf(err, "-m with unsupported matching method '%s'", args[cur_arg]);
1809 return ACT_RET_PRS_ERR;
1810 }
1811 }
1812
Christopher Faulet81e20172019-12-12 16:40:30 +01001813 *orig_arg = cur_arg + 1;
1814 return ACT_RET_PRS_OK;
1815}
1816
Christopher Faulet2eb53962020-01-14 14:47:34 +01001817/* Release memory allocated by an http redirect action. */
1818static void release_http_redir(struct act_rule *rule)
1819{
1820 struct logformat_node *lf, *lfb;
1821 struct redirect_rule *redir;
1822
1823 redir = rule->arg.redir;
Willy Tarreau2b718102021-04-21 07:32:39 +02001824 LIST_DELETE(&redir->list);
Christopher Faulet2eb53962020-01-14 14:47:34 +01001825 if (redir->cond) {
1826 prune_acl_cond(redir->cond);
1827 free(redir->cond);
1828 }
1829 free(redir->rdr_str);
1830 free(redir->cookie_str);
1831 list_for_each_entry_safe(lf, lfb, &redir->rdr_fmt, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02001832 LIST_DELETE(&lf->list);
Christopher Faulet2eb53962020-01-14 14:47:34 +01001833 free(lf);
1834 }
1835 free(redir);
1836}
1837
Christopher Faulet81e20172019-12-12 16:40:30 +01001838/* Parse a "redirect" action. It returns ACT_RET_PRS_OK on success,
1839 * ACT_RET_PRS_ERR on error.
1840 */
1841static enum act_parse_ret parse_http_redirect(const char **args, int *orig_arg, struct proxy *px,
1842 struct act_rule *rule, char **err)
1843{
1844 struct redirect_rule *redir;
1845 int dir, cur_arg;
1846
1847 rule->action = ACT_HTTP_REDIR;
Christopher Faulet245cf792019-12-18 14:58:12 +01001848 rule->flags |= ACT_FLAG_FINAL;
Christopher Faulet2eb53962020-01-14 14:47:34 +01001849 rule->release_ptr = release_http_redir;
Christopher Faulet81e20172019-12-12 16:40:30 +01001850
1851 cur_arg = *orig_arg;
1852
1853 dir = (rule->from == ACT_F_HTTP_REQ ? 0 : 1);
1854 if ((redir = http_parse_redirect_rule(px->conf.args.file, px->conf.args.line, px, &args[cur_arg], err, 1, dir)) == NULL)
1855 return ACT_RET_PRS_ERR;
1856
1857 rule->arg.redir = redir;
1858 rule->cond = redir->cond;
1859 redir->cond = NULL;
1860
1861 /* skip all arguments */
1862 while (*args[cur_arg])
1863 cur_arg++;
1864
1865 *orig_arg = cur_arg;
1866 return ACT_RET_PRS_OK;
1867}
1868
Christopher Faulet046cf442019-12-17 15:45:23 +01001869/* This function executes a add-acl, del-acl, set-map or del-map actions. On
1870 * success, it returns ACT_RET_CONT. Otherwsize ACT_RET_ERR is returned.
1871 */
1872static enum act_return http_action_set_map(struct act_rule *rule, struct proxy *px,
1873 struct session *sess, struct stream *s, int flags)
1874{
1875 struct pat_ref *ref;
1876 struct buffer *key = NULL, *value = NULL;
1877 enum act_return ret = ACT_RET_CONT;
1878
1879 /* collect reference */
1880 ref = pat_ref_lookup(rule->arg.map.ref);
1881 if (!ref)
1882 goto leave;
1883
1884 /* allocate key */
1885 key = alloc_trash_chunk();
1886 if (!key)
1887 goto fail_alloc;
1888
1889 /* collect key */
1890 key->data = build_logline(s, key->area, key->size, &rule->arg.map.key);
1891 key->area[key->data] = '\0';
1892
1893 switch (rule->action) {
1894 case 0: // add-acl
1895 /* add entry only if it does not already exist */
1896 HA_SPIN_LOCK(PATREF_LOCK, &ref->lock);
1897 if (pat_ref_find_elt(ref, key->area) == NULL)
1898 pat_ref_add(ref, key->area, NULL, NULL);
1899 HA_SPIN_UNLOCK(PATREF_LOCK, &ref->lock);
1900 break;
1901
1902 case 1: // set-map
1903 /* allocate value */
1904 value = alloc_trash_chunk();
1905 if (!value)
1906 goto fail_alloc;
1907
1908 /* collect value */
1909 value->data = build_logline(s, value->area, value->size, &rule->arg.map.value);
1910 value->area[value->data] = '\0';
1911
1912 HA_SPIN_LOCK(PATREF_LOCK, &ref->lock);
1913 if (pat_ref_find_elt(ref, key->area) != NULL) {
1914 /* update entry if it exists */
1915 pat_ref_set(ref, key->area, value->area, NULL);
1916 }
1917 else {
1918 /* insert a new entry */
1919 pat_ref_add(ref, key->area, value->area, NULL);
1920 }
1921 HA_SPIN_UNLOCK(PATREF_LOCK, &ref->lock);
1922 break;
1923
1924 case 2: // del-acl
1925 case 3: // del-map
1926 /* returned code: 1=ok, 0=ko */
1927 HA_SPIN_LOCK(PATREF_LOCK, &ref->lock);
1928 pat_ref_delete(ref, key->area);
1929 HA_SPIN_UNLOCK(PATREF_LOCK, &ref->lock);
1930 break;
1931
1932 default:
1933 ret = ACT_RET_ERR;
1934 }
1935
1936
1937 leave:
1938 free_trash_chunk(key);
1939 free_trash_chunk(value);
1940 return ret;
1941
1942 fail_alloc:
1943 if (!(s->flags & SF_ERR_MASK))
1944 s->flags |= SF_ERR_RESOURCE;
1945 ret = ACT_RET_ERR;
1946 goto leave;
1947}
1948
Christopher Faulet2eb53962020-01-14 14:47:34 +01001949/* Release memory allocated by an http map/acl action. */
1950static void release_http_map(struct act_rule *rule)
1951{
1952 struct logformat_node *lf, *lfb;
1953
1954 free(rule->arg.map.ref);
1955 list_for_each_entry_safe(lf, lfb, &rule->arg.map.key, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02001956 LIST_DELETE(&lf->list);
Christopher Faulet2eb53962020-01-14 14:47:34 +01001957 release_sample_expr(lf->expr);
1958 free(lf->arg);
1959 free(lf);
1960 }
1961 if (rule->action == 1) {
1962 list_for_each_entry_safe(lf, lfb, &rule->arg.map.value, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02001963 LIST_DELETE(&lf->list);
Christopher Faulet2eb53962020-01-14 14:47:34 +01001964 release_sample_expr(lf->expr);
1965 free(lf->arg);
1966 free(lf);
1967 }
1968 }
1969}
1970
Christopher Faulet81e20172019-12-12 16:40:30 +01001971/* Parse a "add-acl", "del-acl", "set-map" or "del-map" actions. It takes one or
Christopher Faulet046cf442019-12-17 15:45:23 +01001972 * two log-format string as argument depending on the action. The action is
1973 * stored in <.action> as an int (0=add-acl, 1=set-map, 2=del-acl,
1974 * 3=del-map). It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
Christopher Faulet81e20172019-12-12 16:40:30 +01001975 */
1976static enum act_parse_ret parse_http_set_map(const char **args, int *orig_arg, struct proxy *px,
1977 struct act_rule *rule, char **err)
1978{
1979 int cap, cur_arg;
1980
Christopher Faulet046cf442019-12-17 15:45:23 +01001981 if (args[*orig_arg-1][0] == 'a') // add-acl
1982 rule->action = 0;
1983 else if (args[*orig_arg-1][0] == 's') // set-map
1984 rule->action = 1;
1985 else if (args[*orig_arg-1][4] == 'a') // del-acl
1986 rule->action = 2;
1987 else if (args[*orig_arg-1][4] == 'm') // del-map
1988 rule->action = 3;
1989 else {
1990 memprintf(err, "internal error: unhandled action '%s'", args[0]);
1991 return ACT_RET_PRS_ERR;
1992 }
1993 rule->action_ptr = http_action_set_map;
Christopher Faulet2eb53962020-01-14 14:47:34 +01001994 rule->release_ptr = release_http_map;
Christopher Faulet81e20172019-12-12 16:40:30 +01001995
1996 cur_arg = *orig_arg;
Christopher Faulet046cf442019-12-17 15:45:23 +01001997 if (rule->action == 1 && (!*args[cur_arg] || !*args[cur_arg+1])) {
1998 /* 2 args for set-map */
Christopher Faulet81e20172019-12-12 16:40:30 +01001999 memprintf(err, "expects exactly 2 arguments");
2000 return ACT_RET_PRS_ERR;
2001 }
2002 else if (!*args[cur_arg]) {
Christopher Faulet046cf442019-12-17 15:45:23 +01002003 /* only one arg for other actions */
Christopher Faulet81e20172019-12-12 16:40:30 +01002004 memprintf(err, "expects exactly 1 arguments");
2005 return ACT_RET_PRS_ERR;
2006 }
2007
2008 /*
2009 * '+ 8' for 'set-map(' (same for del-map)
2010 * '- 9' for 'set-map(' + trailing ')' (same for del-map)
2011 */
2012 rule->arg.map.ref = my_strndup(args[cur_arg-1] + 8, strlen(args[cur_arg-1]) - 9);
2013
2014 if (rule->from == ACT_F_HTTP_REQ) {
2015 px->conf.args.ctx = ARGC_HRQ;
2016 cap = (px->cap & PR_CAP_FE) ? SMP_VAL_FE_HRQ_HDR : SMP_VAL_BE_HRQ_HDR;
2017 }
2018 else{
2019 px->conf.args.ctx = ARGC_HRS;
2020 cap = (px->cap & PR_CAP_BE) ? SMP_VAL_BE_HRS_HDR : SMP_VAL_FE_HRS_HDR;
2021 }
2022
2023 /* key pattern */
2024 LIST_INIT(&rule->arg.map.key);
Christopher Faulet1337b322020-01-14 14:50:55 +01002025 if (!parse_logformat_string(args[cur_arg], px, &rule->arg.map.key, LOG_OPT_HTTP, cap, err)) {
2026 free(rule->arg.map.ref);
Christopher Faulet81e20172019-12-12 16:40:30 +01002027 return ACT_RET_PRS_ERR;
Christopher Faulet1337b322020-01-14 14:50:55 +01002028 }
Christopher Faulet81e20172019-12-12 16:40:30 +01002029
Christopher Faulet046cf442019-12-17 15:45:23 +01002030 if (rule->action == 1) {
Christopher Faulet81e20172019-12-12 16:40:30 +01002031 /* value pattern for set-map only */
2032 cur_arg++;
2033 LIST_INIT(&rule->arg.map.value);
Christopher Faulet1337b322020-01-14 14:50:55 +01002034 if (!parse_logformat_string(args[cur_arg], px, &rule->arg.map.value, LOG_OPT_HTTP, cap, err)) {
2035 free(rule->arg.map.ref);
Christopher Faulet81e20172019-12-12 16:40:30 +01002036 return ACT_RET_PRS_ERR;
Christopher Faulet1337b322020-01-14 14:50:55 +01002037 }
Christopher Faulet81e20172019-12-12 16:40:30 +01002038 }
2039
2040 free(px->conf.lfs_file);
2041 px->conf.lfs_file = strdup(px->conf.args.file);
2042 px->conf.lfs_line = px->conf.args.line;
2043
2044 *orig_arg = cur_arg + 1;
2045 return ACT_RET_PRS_OK;
2046}
2047
Christopher Fauletac98d812019-12-18 09:20:16 +01002048/* This function executes a track-sc* actions. On success, it returns
2049 * ACT_RET_CONT. Otherwsize ACT_RET_ERR is returned.
2050 */
2051static enum act_return http_action_track_sc(struct act_rule *rule, struct proxy *px,
2052 struct session *sess, struct stream *s, int flags)
2053{
2054 struct stktable *t;
2055 struct stksess *ts;
2056 struct stktable_key *key;
Willy Tarreau826f3ab2021-02-10 12:07:15 +01002057 void *ptr1, *ptr2, *ptr3, *ptr4, *ptr5, *ptr6;
Christopher Fauletac98d812019-12-18 09:20:16 +01002058 int opt;
2059
Willy Tarreau826f3ab2021-02-10 12:07:15 +01002060 ptr1 = ptr2 = ptr3 = ptr4 = ptr5 = ptr6 = NULL;
Christopher Fauletac98d812019-12-18 09:20:16 +01002061 opt = ((rule->from == ACT_F_HTTP_REQ) ? SMP_OPT_DIR_REQ : SMP_OPT_DIR_RES) | SMP_OPT_FINAL;
2062
2063 t = rule->arg.trk_ctr.table.t;
Emeric Brun362d25e2021-03-10 16:58:03 +01002064
2065 if (stkctr_entry(&s->stkctr[rule->action]))
2066 goto end;
2067
Christopher Fauletac98d812019-12-18 09:20:16 +01002068 key = stktable_fetch_key(t, s->be, sess, s, opt, rule->arg.trk_ctr.expr, NULL);
2069
2070 if (!key)
2071 goto end;
2072 ts = stktable_get_entry(t, key);
2073 if (!ts)
2074 goto end;
2075
2076 stream_track_stkctr(&s->stkctr[rule->action], t, ts);
2077
2078 /* let's count a new HTTP request as it's the first time we do it */
2079 ptr1 = stktable_data_ptr(t, ts, STKTABLE_DT_HTTP_REQ_CNT);
2080 ptr2 = stktable_data_ptr(t, ts, STKTABLE_DT_HTTP_REQ_RATE);
2081
2082 /* When the client triggers a 4xx from the server, it's most often due
2083 * to a missing object or permission. These events should be tracked
2084 * because if they happen often, it may indicate a brute force or a
2085 * vulnerability scan. Normally this is done when receiving the response
2086 * but here we're tracking after this ought to have been done so we have
2087 * to do it on purpose.
2088 */
2089 if (rule->from == ACT_F_HTTP_RES && (unsigned)(s->txn->status - 400) < 100) {
2090 ptr3 = stktable_data_ptr(t, ts, STKTABLE_DT_HTTP_ERR_CNT);
2091 ptr4 = stktable_data_ptr(t, ts, STKTABLE_DT_HTTP_ERR_RATE);
2092 }
2093
Willy Tarreau826f3ab2021-02-10 12:07:15 +01002094 if (rule->from == ACT_F_HTTP_RES && (unsigned)(s->txn->status - 500) < 100 &&
2095 s->txn->status != 501 && s->txn->status != 505) {
2096 ptr5 = stktable_data_ptr(t, ts, STKTABLE_DT_HTTP_FAIL_CNT);
2097 ptr6 = stktable_data_ptr(t, ts, STKTABLE_DT_HTTP_FAIL_RATE);
2098 }
2099
2100 if (ptr1 || ptr2 || ptr3 || ptr4 || ptr5 || ptr6) {
Christopher Fauletac98d812019-12-18 09:20:16 +01002101 HA_RWLOCK_WRLOCK(STK_SESS_LOCK, &ts->lock);
2102
2103 if (ptr1)
2104 stktable_data_cast(ptr1, http_req_cnt)++;
2105 if (ptr2)
2106 update_freq_ctr_period(&stktable_data_cast(ptr2, http_req_rate),
2107 t->data_arg[STKTABLE_DT_HTTP_REQ_RATE].u, 1);
2108 if (ptr3)
2109 stktable_data_cast(ptr3, http_err_cnt)++;
2110 if (ptr4)
2111 update_freq_ctr_period(&stktable_data_cast(ptr4, http_err_rate),
2112 t->data_arg[STKTABLE_DT_HTTP_ERR_RATE].u, 1);
Willy Tarreau826f3ab2021-02-10 12:07:15 +01002113 if (ptr5)
2114 stktable_data_cast(ptr5, http_fail_cnt)++;
2115 if (ptr6)
2116 update_freq_ctr_period(&stktable_data_cast(ptr6, http_fail_rate),
2117 t->data_arg[STKTABLE_DT_HTTP_FAIL_RATE].u, 1);
Christopher Fauletac98d812019-12-18 09:20:16 +01002118
2119 HA_RWLOCK_WRUNLOCK(STK_SESS_LOCK, &ts->lock);
2120
2121 /* If data was modified, we need to touch to re-schedule sync */
2122 stktable_touch_local(t, ts, 0);
2123 }
2124
2125 stkctr_set_flags(&s->stkctr[rule->action], STKCTR_TRACK_CONTENT);
2126 if (sess->fe != s->be)
2127 stkctr_set_flags(&s->stkctr[rule->action], STKCTR_TRACK_BACKEND);
2128
2129 end:
2130 return ACT_RET_CONT;
2131}
Christopher Faulet81e20172019-12-12 16:40:30 +01002132
Christopher Faulet2eb53962020-01-14 14:47:34 +01002133static void release_http_track_sc(struct act_rule *rule)
2134{
2135 release_sample_expr(rule->arg.trk_ctr.expr);
2136}
2137
Christopher Faulet81e20172019-12-12 16:40:30 +01002138/* Parse a "track-sc*" actions. It returns ACT_RET_PRS_OK on success,
2139 * ACT_RET_PRS_ERR on error.
2140 */
2141static enum act_parse_ret parse_http_track_sc(const char **args, int *orig_arg, struct proxy *px,
2142 struct act_rule *rule, char **err)
2143{
2144 struct sample_expr *expr;
2145 unsigned int where;
2146 unsigned int tsc_num;
2147 const char *tsc_num_str;
2148 int cur_arg;
2149
2150 tsc_num_str = &args[*orig_arg-1][8];
2151 if (cfg_parse_track_sc_num(&tsc_num, tsc_num_str, tsc_num_str + strlen(tsc_num_str), err) == -1)
2152 return ACT_RET_PRS_ERR;
2153
2154 cur_arg = *orig_arg;
2155 expr = sample_parse_expr((char **)args, &cur_arg, px->conf.args.file, px->conf.args.line,
Willy Tarreaue3b57bf2020-02-14 16:50:14 +01002156 err, &px->conf.args, NULL);
Christopher Faulet81e20172019-12-12 16:40:30 +01002157 if (!expr)
2158 return ACT_RET_PRS_ERR;
2159
2160 where = 0;
2161 if (px->cap & PR_CAP_FE)
2162 where |= (rule->from == ACT_F_HTTP_REQ ? SMP_VAL_FE_HRQ_HDR : SMP_VAL_FE_HRS_HDR);
2163 if (px->cap & PR_CAP_BE)
2164 where |= (rule->from == ACT_F_HTTP_REQ ? SMP_VAL_BE_HRQ_HDR : SMP_VAL_BE_HRS_HDR);
2165
2166 if (!(expr->fetch->val & where)) {
2167 memprintf(err, "fetch method '%s' extracts information from '%s', none of which is available here",
2168 args[cur_arg-1], sample_src_names(expr->fetch->use));
Christopher Faulet1337b322020-01-14 14:50:55 +01002169 release_sample_expr(expr);
Christopher Faulet81e20172019-12-12 16:40:30 +01002170 return ACT_RET_PRS_ERR;
2171 }
2172
2173 if (strcmp(args[cur_arg], "table") == 0) {
2174 cur_arg++;
2175 if (!*args[cur_arg]) {
2176 memprintf(err, "missing table name");
Christopher Faulet1337b322020-01-14 14:50:55 +01002177 release_sample_expr(expr);
Christopher Faulet81e20172019-12-12 16:40:30 +01002178 return ACT_RET_PRS_ERR;
2179 }
2180
2181 /* we copy the table name for now, it will be resolved later */
2182 rule->arg.trk_ctr.table.n = strdup(args[cur_arg]);
2183 cur_arg++;
2184 }
2185
Christopher Fauletac98d812019-12-18 09:20:16 +01002186 rule->action = tsc_num;
Christopher Faulet81e20172019-12-12 16:40:30 +01002187 rule->arg.trk_ctr.expr = expr;
Christopher Fauletac98d812019-12-18 09:20:16 +01002188 rule->action_ptr = http_action_track_sc;
Christopher Faulet2eb53962020-01-14 14:47:34 +01002189 rule->release_ptr = release_http_track_sc;
Christopher Faulet81e20172019-12-12 16:40:30 +01002190 rule->check_ptr = check_trk_action;
2191
2192 *orig_arg = cur_arg;
2193 return ACT_RET_PRS_OK;
2194}
2195
Amaury Denoyelle8d228232020-12-10 13:43:54 +01002196static enum act_return action_timeout_set_stream_timeout(struct act_rule *rule,
2197 struct proxy *px,
2198 struct session *sess,
2199 struct stream *s,
2200 int flags)
2201{
2202 struct sample *key;
2203
2204 if (rule->arg.timeout.expr) {
2205 key = sample_fetch_as_type(px, sess, s, SMP_OPT_FINAL, rule->arg.timeout.expr, SMP_T_SINT);
2206 if (!key)
2207 return ACT_RET_CONT;
2208
2209 stream_set_timeout(s, rule->arg.timeout.type, MS_TO_TICKS(key->data.u.sint));
2210 }
2211 else {
2212 stream_set_timeout(s, rule->arg.timeout.type, MS_TO_TICKS(rule->arg.timeout.value));
2213 }
2214
2215 return ACT_RET_CONT;
2216}
2217
2218/* Parse a "set-timeout" action. Returns ACT_RET_PRS_ERR if parsing error.
2219 */
2220static enum act_parse_ret parse_http_set_timeout(const char **args,
2221 int *orig_arg,
2222 struct proxy *px,
2223 struct act_rule *rule, char **err)
2224{
2225 int cur_arg;
2226
2227 rule->action = ACT_CUSTOM;
2228 rule->action_ptr = action_timeout_set_stream_timeout;
2229 rule->release_ptr = release_timeout_action;
2230
2231 cur_arg = *orig_arg;
2232 if (!*args[cur_arg] || !*args[cur_arg + 1]) {
2233 memprintf(err, "expects exactly 2 arguments");
2234 return ACT_RET_PRS_ERR;
2235 }
2236
2237 if (!(px->cap & PR_CAP_BE)) {
2238 memprintf(err, "proxy '%s' has no backend capability", px->id);
2239 return ACT_RET_PRS_ERR;
2240 }
2241
2242 if (cfg_parse_rule_set_timeout(args, cur_arg,
2243 &rule->arg.timeout.value,
2244 &rule->arg.timeout.type,
2245 &rule->arg.timeout.expr,
2246 err,
2247 px->conf.args.file,
2248 px->conf.args.line, &px->conf.args) == -1) {
2249 return ACT_RET_PRS_ERR;
2250 }
2251
2252 *orig_arg = cur_arg + 2;
2253
2254 return ACT_RET_PRS_OK;
2255}
2256
Christopher Faulet46f95542019-12-20 10:07:22 +01002257/* This function executes a strict-mode actions. On success, it always returns
2258 * ACT_RET_CONT
2259 */
2260static enum act_return http_action_strict_mode(struct act_rule *rule, struct proxy *px,
2261 struct session *sess, struct stream *s, int flags)
2262{
2263 struct http_msg *msg = ((rule->from == ACT_F_HTTP_REQ) ? &s->txn->req : &s->txn->rsp);
2264
2265 if (rule->action == 0) // strict-mode on
2266 msg->flags &= ~HTTP_MSGF_SOFT_RW;
2267 else // strict-mode off
2268 msg->flags |= HTTP_MSGF_SOFT_RW;
2269 return ACT_RET_CONT;
2270}
2271
2272/* Parse a "strict-mode" action. It returns ACT_RET_PRS_OK on success,
2273 * ACT_RET_PRS_ERR on error.
2274 */
2275static enum act_parse_ret parse_http_strict_mode(const char **args, int *orig_arg, struct proxy *px,
2276 struct act_rule *rule, char **err)
2277{
2278 int cur_arg;
2279
Christopher Faulet46f95542019-12-20 10:07:22 +01002280 cur_arg = *orig_arg;
2281 if (!*args[cur_arg]) {
2282 memprintf(err, "expects exactly 1 arguments");
2283 return ACT_RET_PRS_ERR;
2284 }
2285
2286 if (strcasecmp(args[cur_arg], "on") == 0)
2287 rule->action = 0; // strict-mode on
2288 else if (strcasecmp(args[cur_arg], "off") == 0)
2289 rule->action = 1; // strict-mode off
2290 else {
2291 memprintf(err, "Unexpected value '%s'. Only 'on' and 'off' are supported", args[cur_arg]);
2292 return ACT_RET_PRS_ERR;
2293 }
2294 rule->action_ptr = http_action_strict_mode;
2295
2296 *orig_arg = cur_arg + 1;
2297 return ACT_RET_PRS_OK;
2298}
2299
Christopher Faulet24231ab2020-01-24 17:44:23 +01002300/* This function executes a return action. It builds an HTX message from an
2301 * errorfile, an raw file or a log-format string, depending on <.action>
2302 * value. On success, it returns ACT_RET_ABRT. If an error occurs ACT_RET_ERR is
2303 * returned.
2304 */
2305static enum act_return http_action_return(struct act_rule *rule, struct proxy *px,
2306 struct session *sess, struct stream *s, int flags)
2307{
2308 struct channel *req = &s->req;
Christopher Faulet24231ab2020-01-24 17:44:23 +01002309
Christopher Faulet2d36df22021-02-19 11:41:01 +01002310 s->txn->status = rule->arg.http_reply->status;
Christopher Faulet0e2ad612020-05-13 16:38:37 +02002311 if (http_reply_message(s, rule->arg.http_reply) == -1)
2312 return ACT_RET_ERR;
Christopher Faulet24231ab2020-01-24 17:44:23 +01002313
Christopher Faulet24231ab2020-01-24 17:44:23 +01002314 if (rule->from == ACT_F_HTTP_REQ) {
2315 /* let's log the request time */
2316 s->logs.tv_request = now;
2317 req->analysers &= AN_REQ_FLT_END;
2318
2319 if (s->sess->fe == s->be) /* report it if the request was intercepted by the frontend */
Willy Tarreau4781b152021-04-06 13:53:36 +02002320 _HA_ATOMIC_INC(&s->sess->fe->fe_counters.intercepted_req);
Christopher Faulet24231ab2020-01-24 17:44:23 +01002321 }
2322
2323 if (!(s->flags & SF_ERR_MASK))
2324 s->flags |= SF_ERR_LOCAL;
2325 if (!(s->flags & SF_FINST_MASK))
2326 s->flags |= ((rule->from == ACT_F_HTTP_REQ) ? SF_FINST_R : SF_FINST_H);
2327
Christopher Faulet0e2ad612020-05-13 16:38:37 +02002328 return ACT_RET_ABRT;
Christopher Faulet24231ab2020-01-24 17:44:23 +01002329}
2330
Christopher Faulet24231ab2020-01-24 17:44:23 +01002331/* Parse a "return" action. It returns ACT_RET_PRS_OK on success,
Christopher Faulet47e791e2020-05-13 14:36:55 +02002332 * ACT_RET_PRS_ERR on error. It relies on http_parse_http_reply() to set
2333 * <.arg.http_reply>.
Christopher Faulet24231ab2020-01-24 17:44:23 +01002334 */
2335static enum act_parse_ret parse_http_return(const char **args, int *orig_arg, struct proxy *px,
2336 struct act_rule *rule, char **err)
2337{
Christopher Faulet47e791e2020-05-13 14:36:55 +02002338 /* Prepare parsing of log-format strings */
2339 px->conf.args.ctx = ((rule->from == ACT_F_HTTP_REQ) ? ARGC_HRQ : ARGC_HRS);
2340 rule->arg.http_reply = http_parse_http_reply(args, orig_arg, px, 200, err);
2341 if (!rule->arg.http_reply)
2342 return ACT_RET_PRS_ERR;
Christopher Faulet24231ab2020-01-24 17:44:23 +01002343
Christopher Fauletba946bf2020-05-13 08:50:07 +02002344 rule->flags |= ACT_FLAG_FINAL;
Christopher Faulet5ff0c642020-05-12 18:33:37 +02002345 rule->action = ACT_CUSTOM;
Christopher Faulet5cb513a2020-05-13 17:56:56 +02002346 rule->check_ptr = check_act_http_reply;
Christopher Faulet24231ab2020-01-24 17:44:23 +01002347 rule->action_ptr = http_action_return;
Christopher Faulet5cb513a2020-05-13 17:56:56 +02002348 rule->release_ptr = release_act_http_reply;
Christopher Faulet24231ab2020-01-24 17:44:23 +01002349 return ACT_RET_PRS_OK;
Christopher Faulet24231ab2020-01-24 17:44:23 +01002350}
2351
Christopher Faulet021a8e42021-03-29 10:46:38 +02002352
2353
2354/* This function executes a wait-for-body action. It waits for the message
2355 * payload for a max configured time (.arg.p[0]) and eventually for only first
2356 * <arg.p[1]> bytes (0 means no limit). It relies on http_wait_for_msg_body()
2357 * function. it returns ACT_RET_CONT when conditions are met to stop to wait.
2358 * Otherwise ACT_RET_YIELD is returned to wait for more data. ACT_RET_INV is
2359 * returned if a parsing error is raised by lower level and ACT_RET_ERR if an
Ilya Shipitsinb2be9a12021-04-24 13:25:42 +05002360 * internal error occurred. Finally ACT_RET_ABRT is returned when a timeout
2361 * occurred.
Christopher Faulet021a8e42021-03-29 10:46:38 +02002362 */
2363static enum act_return http_action_wait_for_body(struct act_rule *rule, struct proxy *px,
2364 struct session *sess, struct stream *s, int flags)
2365{
2366 struct channel *chn = ((rule->from == ACT_F_HTTP_REQ) ? &s->req : &s->res);
2367 unsigned int time = (uintptr_t)rule->arg.act.p[0];
2368 unsigned int bytes = (uintptr_t)rule->arg.act.p[1];
2369
2370 switch (http_wait_for_msg_body(s, chn, time, bytes)) {
2371 case HTTP_RULE_RES_CONT:
2372 return ACT_RET_CONT;
2373 case HTTP_RULE_RES_YIELD:
2374 return ACT_RET_YIELD;
2375 case HTTP_RULE_RES_BADREQ:
2376 return ACT_RET_INV;
2377 case HTTP_RULE_RES_ERROR:
2378 return ACT_RET_ERR;
2379 case HTTP_RULE_RES_ABRT:
2380 return ACT_RET_ABRT;
2381 default:
2382 return ACT_RET_ERR;
2383 }
2384}
2385
2386/* Parse a "wait-for-body" action. It returns ACT_RET_PRS_OK on success,
2387 * ACT_RET_PRS_ERR on error.
2388 */
2389static enum act_parse_ret parse_http_wait_for_body(const char **args, int *orig_arg, struct proxy *px,
2390 struct act_rule *rule, char **err)
2391{
2392 int cur_arg;
2393 unsigned int time, bytes;
2394 const char *res;
2395
2396 cur_arg = *orig_arg;
2397 if (!*args[cur_arg]) {
2398 memprintf(err, "expects time <time> [ at-least <bytes> ]");
2399 return ACT_RET_PRS_ERR;
2400 }
2401
2402 time = UINT_MAX; /* To be sure it is set */
2403 bytes = 0; /* Default value, wait all the body */
2404 while (*(args[cur_arg])) {
2405 if (strcmp(args[cur_arg], "time") == 0) {
2406 if (!*args[cur_arg + 1]) {
2407 memprintf(err, "missing argument for '%s'", args[cur_arg]);
2408 return ACT_RET_PRS_ERR;
2409 }
2410 res = parse_time_err(args[cur_arg+1], &time, TIME_UNIT_MS);
2411 if (res == PARSE_TIME_OVER) {
2412 memprintf(err, "time overflow (maximum value is 2147483647 ms or ~24.8 days)");
2413 return ACT_RET_PRS_ERR;
2414 }
2415 if (res == PARSE_TIME_UNDER) {
2416 memprintf(err, "time underflow (minimum non-null value is 1 ms)");
2417 return ACT_RET_PRS_ERR;
2418 }
2419 if (res) {
2420 memprintf(err, "unexpected character '%c'", *res);
2421 return ACT_RET_PRS_ERR;
2422 }
2423 cur_arg++;
2424 }
2425 else if (strcmp(args[cur_arg], "at-least") == 0) {
2426 if (!*args[cur_arg + 1]) {
2427 memprintf(err, "missing argument for '%s'", args[cur_arg]);
2428 return ACT_RET_PRS_ERR;
2429 }
2430 res = parse_size_err(args[cur_arg+1], &bytes);
2431 if (res) {
2432 memprintf(err, "unexpected character '%c'", *res);
2433 return ACT_RET_PRS_ERR;
2434 }
2435 cur_arg++;
2436 }
2437 else
2438 break;
2439 cur_arg++;
2440 }
2441
2442 if (time == UINT_MAX) {
2443 memprintf(err, "expects time <time> [ at-least <bytes> ]");
2444 return ACT_RET_PRS_ERR;
2445 }
2446
2447 rule->arg.act.p[0] = (void *)(uintptr_t)time;
2448 rule->arg.act.p[1] = (void *)(uintptr_t)bytes;
2449
2450 *orig_arg = cur_arg;
2451
2452 rule->action = ACT_CUSTOM;
2453 rule->action_ptr = http_action_wait_for_body;
2454 return ACT_RET_PRS_OK;
2455}
2456
Willy Tarreau79e57332018-10-02 16:01:16 +02002457/************************************************************************/
2458/* All supported http-request action keywords must be declared here. */
2459/************************************************************************/
2460
2461static struct action_kw_list http_req_actions = {
2462 .kw = {
Amaury Denoyellee4a617c2021-05-06 15:33:09 +02002463 { "add-acl", parse_http_set_map, KWF_MATCH_PREFIX },
Christopher Faulet81e20172019-12-12 16:40:30 +01002464 { "add-header", parse_http_set_header, 0 },
2465 { "allow", parse_http_allow, 0 },
2466 { "auth", parse_http_auth, 0 },
2467 { "capture", parse_http_req_capture, 0 },
Amaury Denoyellee4a617c2021-05-06 15:33:09 +02002468 { "del-acl", parse_http_set_map, KWF_MATCH_PREFIX },
Christopher Faulet81e20172019-12-12 16:40:30 +01002469 { "del-header", parse_http_del_header, 0 },
Amaury Denoyellee4a617c2021-05-06 15:33:09 +02002470 { "del-map", parse_http_set_map, KWF_MATCH_PREFIX },
Christopher Faulete0fca292020-01-13 21:49:03 +01002471 { "deny", parse_http_deny, 0 },
Christopher Faulet81e20172019-12-12 16:40:30 +01002472 { "disable-l7-retry", parse_http_req_disable_l7_retry, 0 },
2473 { "early-hint", parse_http_set_header, 0 },
Amaury Denoyellea9e639a2021-05-06 15:50:12 +02002474 { "normalize-uri", parse_http_normalize_uri, KWF_EXPERIMENTAL },
Christopher Faulet81e20172019-12-12 16:40:30 +01002475 { "redirect", parse_http_redirect, 0 },
2476 { "reject", parse_http_action_reject, 0 },
2477 { "replace-header", parse_http_replace_header, 0 },
2478 { "replace-path", parse_replace_uri, 0 },
Christopher Faulet312294f2020-09-02 17:17:44 +02002479 { "replace-pathq", parse_replace_uri, 0 },
Christopher Faulet81e20172019-12-12 16:40:30 +01002480 { "replace-uri", parse_replace_uri, 0 },
2481 { "replace-value", parse_http_replace_header, 0 },
Christopher Faulet24231ab2020-01-24 17:44:23 +01002482 { "return", parse_http_return, 0 },
Christopher Faulet81e20172019-12-12 16:40:30 +01002483 { "set-header", parse_http_set_header, 0 },
2484 { "set-log-level", parse_http_set_log_level, 0 },
Amaury Denoyellee4a617c2021-05-06 15:33:09 +02002485 { "set-map", parse_http_set_map, KWF_MATCH_PREFIX },
Christopher Faulet81e20172019-12-12 16:40:30 +01002486 { "set-method", parse_set_req_line, 0 },
2487 { "set-mark", parse_http_set_mark, 0 },
2488 { "set-nice", parse_http_set_nice, 0 },
2489 { "set-path", parse_set_req_line, 0 },
Christopher Faulet312294f2020-09-02 17:17:44 +02002490 { "set-pathq", parse_set_req_line, 0 },
Christopher Faulet81e20172019-12-12 16:40:30 +01002491 { "set-query", parse_set_req_line, 0 },
2492 { "set-tos", parse_http_set_tos, 0 },
2493 { "set-uri", parse_set_req_line, 0 },
Christopher Faulet46f95542019-12-20 10:07:22 +01002494 { "strict-mode", parse_http_strict_mode, 0 },
Christopher Faulete0fca292020-01-13 21:49:03 +01002495 { "tarpit", parse_http_deny, 0 },
Amaury Denoyellee4a617c2021-05-06 15:33:09 +02002496 { "track-sc", parse_http_track_sc, KWF_MATCH_PREFIX },
Amaury Denoyelle8d228232020-12-10 13:43:54 +01002497 { "set-timeout", parse_http_set_timeout, 0 },
Christopher Faulet021a8e42021-03-29 10:46:38 +02002498 { "wait-for-body", parse_http_wait_for_body, 0 },
Willy Tarreau79e57332018-10-02 16:01:16 +02002499 { NULL, NULL }
2500 }
2501};
2502
Willy Tarreau0108d902018-11-25 19:14:37 +01002503INITCALL1(STG_REGISTER, http_req_keywords_register, &http_req_actions);
2504
Willy Tarreau79e57332018-10-02 16:01:16 +02002505static struct action_kw_list http_res_actions = {
2506 .kw = {
Amaury Denoyellee4a617c2021-05-06 15:33:09 +02002507 { "add-acl", parse_http_set_map, KWF_MATCH_PREFIX },
Christopher Faulet81e20172019-12-12 16:40:30 +01002508 { "add-header", parse_http_set_header, 0 },
2509 { "allow", parse_http_allow, 0 },
2510 { "capture", parse_http_res_capture, 0 },
Amaury Denoyellee4a617c2021-05-06 15:33:09 +02002511 { "del-acl", parse_http_set_map, KWF_MATCH_PREFIX },
Christopher Faulet81e20172019-12-12 16:40:30 +01002512 { "del-header", parse_http_del_header, 0 },
Amaury Denoyellee4a617c2021-05-06 15:33:09 +02002513 { "del-map", parse_http_set_map, KWF_MATCH_PREFIX },
Christopher Faulete0fca292020-01-13 21:49:03 +01002514 { "deny", parse_http_deny, 0 },
Christopher Faulet81e20172019-12-12 16:40:30 +01002515 { "redirect", parse_http_redirect, 0 },
2516 { "replace-header", parse_http_replace_header, 0 },
2517 { "replace-value", parse_http_replace_header, 0 },
Christopher Faulet24231ab2020-01-24 17:44:23 +01002518 { "return", parse_http_return, 0 },
Christopher Faulet81e20172019-12-12 16:40:30 +01002519 { "set-header", parse_http_set_header, 0 },
2520 { "set-log-level", parse_http_set_log_level, 0 },
Amaury Denoyellee4a617c2021-05-06 15:33:09 +02002521 { "set-map", parse_http_set_map, KWF_MATCH_PREFIX },
Christopher Faulet81e20172019-12-12 16:40:30 +01002522 { "set-mark", parse_http_set_mark, 0 },
2523 { "set-nice", parse_http_set_nice, 0 },
2524 { "set-status", parse_http_set_status, 0 },
2525 { "set-tos", parse_http_set_tos, 0 },
Christopher Faulet46f95542019-12-20 10:07:22 +01002526 { "strict-mode", parse_http_strict_mode, 0 },
Amaury Denoyellee4a617c2021-05-06 15:33:09 +02002527 { "track-sc", parse_http_track_sc, KWF_MATCH_PREFIX },
Christopher Faulet021a8e42021-03-29 10:46:38 +02002528 { "wait-for-body", parse_http_wait_for_body, 0 },
Willy Tarreau79e57332018-10-02 16:01:16 +02002529 { NULL, NULL }
2530 }
2531};
2532
Willy Tarreau0108d902018-11-25 19:14:37 +01002533INITCALL1(STG_REGISTER, http_res_keywords_register, &http_res_actions);
Willy Tarreau79e57332018-10-02 16:01:16 +02002534
Christopher Faulet6d0c3df2020-01-22 09:26:35 +01002535static struct action_kw_list http_after_res_actions = {
2536 .kw = {
2537 { "add-header", parse_http_set_header, 0 },
2538 { "allow", parse_http_allow, 0 },
2539 { "del-header", parse_http_del_header, 0 },
2540 { "replace-header", parse_http_replace_header, 0 },
2541 { "replace-value", parse_http_replace_header, 0 },
2542 { "set-header", parse_http_set_header, 0 },
2543 { "set-status", parse_http_set_status, 0 },
2544 { "strict-mode", parse_http_strict_mode, 0 },
2545 { NULL, NULL }
2546 }
2547};
2548
2549INITCALL1(STG_REGISTER, http_after_res_keywords_register, &http_after_res_actions);
2550
Willy Tarreau79e57332018-10-02 16:01:16 +02002551/*
2552 * Local variables:
2553 * c-indent-level: 8
2554 * c-basic-offset: 8
2555 * End:
2556 */