blob: 774df140adb73b078103d17d2d09b37fd83700ac [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
Christopher Faulet81e20172019-12-12 16:40:30 +010019#include <common/cfgparse.h>
Willy Tarreau79e57332018-10-02 16:01:16 +020020#include <common/chunk.h>
21#include <common/compat.h>
22#include <common/config.h>
23#include <common/debug.h>
24#include <common/http.h>
Willy Tarreau0108d902018-11-25 19:14:37 +010025#include <common/initcall.h>
Willy Tarreau79e57332018-10-02 16:01:16 +020026#include <common/memory.h>
27#include <common/standard.h>
28#include <common/version.h>
29
30#include <types/capture.h>
31#include <types/global.h>
32
33#include <proto/acl.h>
34#include <proto/arg.h>
Christopher Faulet81e20172019-12-12 16:40:30 +010035#include <proto/action.h>
Willy Tarreau61c112a2018-10-02 16:43:32 +020036#include <proto/http_rules.h>
Willy Tarreau33810222019-06-12 17:44:02 +020037#include <proto/http_htx.h>
Willy Tarreau79e57332018-10-02 16:01:16 +020038#include <proto/log.h>
Christopher Fauletfc9cfe42019-07-16 14:54:53 +020039#include <proto/http_ana.h>
Christopher Faulet046cf442019-12-17 15:45:23 +010040#include <proto/pattern.h>
Willy Tarreau0f9cd7b2019-01-31 19:02:43 +010041#include <proto/stream_interface.h>
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
50 if (rule->arg.http.str.ptr)
51 free(rule->arg.http.str.ptr);
52 if (rule->arg.http.re)
53 regex_free(rule->arg.http.re);
54 list_for_each_entry_safe(lf, lfb, &rule->arg.http.fmt, list) {
55 LIST_DEL(&lf->list);
56 release_sample_expr(lf->expr);
57 free(lf->arg);
58 free(lf);
59 }
60}
61
Willy Tarreau79e57332018-10-02 16:01:16 +020062
63/* This function executes one of the set-{method,path,query,uri} actions. It
64 * builds a string in the trash from the specified format string. It finds
Christopher Faulet2c22a692019-12-18 15:39:56 +010065 * the action to be performed in <.action>, previously filled by function
Willy Tarreau79e57332018-10-02 16:01:16 +020066 * parse_set_req_line(). The replacement action is excuted by the function
Christopher Faulete00d06c2019-12-16 17:18:42 +010067 * http_action_set_req_line(). On success, it returns ACT_RET_CONT. If an error
68 * occurs while soft rewrites are enabled, the action is canceled, but the rule
69 * processing continue. Otherwsize ACT_RET_ERR is returned.
Willy Tarreau79e57332018-10-02 16:01:16 +020070 */
71static enum act_return http_action_set_req_line(struct act_rule *rule, struct proxy *px,
72 struct session *sess, struct stream *s, int flags)
73{
74 struct buffer *replace;
Christopher Faulet13403762019-12-13 09:01:57 +010075 enum act_return ret = ACT_RET_CONT;
Willy Tarreau79e57332018-10-02 16:01:16 +020076
77 replace = alloc_trash_chunk();
78 if (!replace)
Christopher Faulete00d06c2019-12-16 17:18:42 +010079 goto fail_alloc;
Willy Tarreau79e57332018-10-02 16:01:16 +020080
81 /* If we have to create a query string, prepare a '?'. */
Christopher Faulet2c22a692019-12-18 15:39:56 +010082 if (rule->action == 2) // set-query
Willy Tarreau79e57332018-10-02 16:01:16 +020083 replace->area[replace->data++] = '?';
84 replace->data += build_logline(s, replace->area + replace->data,
85 replace->size - replace->data,
Christopher Faulet96bff762019-12-17 13:46:18 +010086 &rule->arg.http.fmt);
Willy Tarreau79e57332018-10-02 16:01:16 +020087
Christopher Faulet2c22a692019-12-18 15:39:56 +010088 if (http_req_replace_stline(rule->action, replace->area, replace->data, px, s) == -1)
Christopher Faulete00d06c2019-12-16 17:18:42 +010089 goto fail_rewrite;
Willy Tarreau79e57332018-10-02 16:01:16 +020090
Christopher Faulete00d06c2019-12-16 17:18:42 +010091 leave:
Willy Tarreau79e57332018-10-02 16:01:16 +020092 free_trash_chunk(replace);
93 return ret;
Christopher Faulete00d06c2019-12-16 17:18:42 +010094
95 fail_alloc:
96 if (!(s->flags & SF_ERR_MASK))
97 s->flags |= SF_ERR_RESOURCE;
98 ret = ACT_RET_ERR;
99 goto leave;
100
101 fail_rewrite:
102 _HA_ATOMIC_ADD(&sess->fe->fe_counters.failed_rewrites, 1);
103 if (s->flags & SF_BE_ASSIGNED)
104 _HA_ATOMIC_ADD(&s->be->be_counters.failed_rewrites, 1);
105 if (sess->listener->counters)
106 _HA_ATOMIC_ADD(&sess->listener->counters->failed_rewrites, 1);
107 if (objt_server(s->target))
108 _HA_ATOMIC_ADD(&__objt_server(s->target)->counters.failed_rewrites, 1);
109
110 if (!(s->txn->req.flags & HTTP_MSGF_SOFT_RW))
111 ret = ACT_RET_ERR;
112 goto leave;
Willy Tarreau79e57332018-10-02 16:01:16 +0200113}
114
115/* parse an http-request action among :
116 * set-method
117 * set-path
118 * set-query
119 * set-uri
120 *
121 * All of them accept a single argument of type string representing a log-format.
Christopher Faulet96bff762019-12-17 13:46:18 +0100122 * The resulting rule makes use of <http.fmt> to store the log-format list head,
Christopher Faulet2c22a692019-12-18 15:39:56 +0100123 * and <.action> to store the action type as an int (0=method, 1=path, 2=query,
124 * 3=uri). It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
Willy Tarreau79e57332018-10-02 16:01:16 +0200125 */
126static enum act_parse_ret parse_set_req_line(const char **args, int *orig_arg, struct proxy *px,
127 struct act_rule *rule, char **err)
128{
129 int cur_arg = *orig_arg;
130
Willy Tarreau79e57332018-10-02 16:01:16 +0200131 switch (args[0][4]) {
132 case 'm' :
Christopher Faulet2c22a692019-12-18 15:39:56 +0100133 rule->action = 0; // set-method
Willy Tarreau79e57332018-10-02 16:01:16 +0200134 break;
135 case 'p' :
Christopher Faulet2c22a692019-12-18 15:39:56 +0100136 rule->action = 1; // set-path
Willy Tarreau79e57332018-10-02 16:01:16 +0200137 break;
138 case 'q' :
Christopher Faulet2c22a692019-12-18 15:39:56 +0100139 rule->action = 2; // set-query
Willy Tarreau79e57332018-10-02 16:01:16 +0200140 break;
141 case 'u' :
Christopher Faulet2c22a692019-12-18 15:39:56 +0100142 rule->action = 3; // set-uri
Willy Tarreau79e57332018-10-02 16:01:16 +0200143 break;
144 default:
145 memprintf(err, "internal error: unhandled action '%s'", args[0]);
146 return ACT_RET_PRS_ERR;
147 }
Christopher Faulet96bff762019-12-17 13:46:18 +0100148 rule->action_ptr = http_action_set_req_line;
Christopher Faulet2eb53962020-01-14 14:47:34 +0100149 rule->release_ptr = release_http_action;
Willy Tarreau79e57332018-10-02 16:01:16 +0200150
151 if (!*args[cur_arg] ||
152 (*args[cur_arg + 1] && strcmp(args[cur_arg + 1], "if") != 0 && strcmp(args[cur_arg + 1], "unless") != 0)) {
153 memprintf(err, "expects exactly 1 argument <format>");
154 return ACT_RET_PRS_ERR;
155 }
156
Christopher Faulet96bff762019-12-17 13:46:18 +0100157 LIST_INIT(&rule->arg.http.fmt);
Willy Tarreau79e57332018-10-02 16:01:16 +0200158 px->conf.args.ctx = ARGC_HRQ;
Christopher Faulet96bff762019-12-17 13:46:18 +0100159 if (!parse_logformat_string(args[cur_arg], px, &rule->arg.http.fmt, LOG_OPT_HTTP,
Willy Tarreau79e57332018-10-02 16:01:16 +0200160 (px->cap & PR_CAP_FE) ? SMP_VAL_FE_HRQ_HDR : SMP_VAL_BE_HRQ_HDR, err)) {
161 return ACT_RET_PRS_ERR;
162 }
163
164 (*orig_arg)++;
165 return ACT_RET_PRS_OK;
166}
167
Willy Tarreau33810222019-06-12 17:44:02 +0200168/* This function executes a replace-uri action. It finds its arguments in
Christopher Faulet96bff762019-12-17 13:46:18 +0100169 * <rule>.arg.http. It builds a string in the trash from the format string
Willy Tarreau33810222019-06-12 17:44:02 +0200170 * previously filled by function parse_replace_uri() and will execute the regex
Christopher Faulet96bff762019-12-17 13:46:18 +0100171 * in <http.re> to replace the URI. It uses the format string present in
Christopher Faulet2c22a692019-12-18 15:39:56 +0100172 * <http.fmt>. The component to act on (path/uri) is taken from <.action> which
Christopher Faulet96bff762019-12-17 13:46:18 +0100173 * contains 1 for the path or 3 for the URI (values used by
174 * http_req_replace_stline()). On success, it returns ACT_RET_CONT. If an error
175 * occurs while soft rewrites are enabled, the action is canceled, but the rule
176 * processing continue. Otherwsize ACT_RET_ERR is returned.
Willy Tarreau33810222019-06-12 17:44:02 +0200177 */
178static enum act_return http_action_replace_uri(struct act_rule *rule, struct proxy *px,
179 struct session *sess, struct stream *s, int flags)
180{
Christopher Faulet13403762019-12-13 09:01:57 +0100181 enum act_return ret = ACT_RET_CONT;
Willy Tarreau33810222019-06-12 17:44:02 +0200182 struct buffer *replace, *output;
183 struct ist uri;
184 int len;
185
186 replace = alloc_trash_chunk();
187 output = alloc_trash_chunk();
188 if (!replace || !output)
Christopher Faulete00d06c2019-12-16 17:18:42 +0100189 goto fail_alloc;
Christopher Faulet12c28b62019-07-15 16:30:24 +0200190 uri = htx_sl_req_uri(http_get_stline(htxbuf(&s->req.buf)));
Willy Tarreau262c3f12019-12-17 06:52:51 +0100191
Christopher Faulet2c22a692019-12-18 15:39:56 +0100192 if (rule->action == 1) // replace-path
Christopher Faulet96bff762019-12-17 13:46:18 +0100193 uri = http_get_path(uri);
Willy Tarreau262c3f12019-12-17 06:52:51 +0100194
Christopher Faulet96bff762019-12-17 13:46:18 +0100195 if (!regex_exec_match2(rule->arg.http.re, uri.ptr, uri.len, MAX_MATCH, pmatch, 0))
Willy Tarreau33810222019-06-12 17:44:02 +0200196 goto leave;
197
Christopher Faulet96bff762019-12-17 13:46:18 +0100198 replace->data = build_logline(s, replace->area, replace->size, &rule->arg.http.fmt);
Willy Tarreau33810222019-06-12 17:44:02 +0200199
200 /* note: uri.ptr doesn't need to be zero-terminated because it will
201 * only be used to pick pmatch references.
202 */
203 len = exp_replace(output->area, output->size, uri.ptr, replace->area, pmatch);
204 if (len == -1)
Christopher Faulete00d06c2019-12-16 17:18:42 +0100205 goto fail_rewrite;
Willy Tarreau33810222019-06-12 17:44:02 +0200206
Christopher Faulet2c22a692019-12-18 15:39:56 +0100207 if (http_req_replace_stline(rule->action, output->area, len, px, s) == -1)
Christopher Faulete00d06c2019-12-16 17:18:42 +0100208 goto fail_rewrite;
Willy Tarreau33810222019-06-12 17:44:02 +0200209
Christopher Faulete00d06c2019-12-16 17:18:42 +0100210 leave:
Willy Tarreau33810222019-06-12 17:44:02 +0200211 free_trash_chunk(output);
212 free_trash_chunk(replace);
213 return ret;
Christopher Faulete00d06c2019-12-16 17:18:42 +0100214
215 fail_alloc:
216 if (!(s->flags & SF_ERR_MASK))
217 s->flags |= SF_ERR_RESOURCE;
218 ret = ACT_RET_ERR;
219 goto leave;
220
221 fail_rewrite:
222 _HA_ATOMIC_ADD(&sess->fe->fe_counters.failed_rewrites, 1);
223 if (s->flags & SF_BE_ASSIGNED)
224 _HA_ATOMIC_ADD(&s->be->be_counters.failed_rewrites, 1);
225 if (sess->listener->counters)
226 _HA_ATOMIC_ADD(&sess->listener->counters->failed_rewrites, 1);
227 if (objt_server(s->target))
228 _HA_ATOMIC_ADD(&__objt_server(s->target)->counters.failed_rewrites, 1);
229
230 if (!(s->txn->req.flags & HTTP_MSGF_SOFT_RW))
231 ret = ACT_RET_ERR;
232 goto leave;
Willy Tarreau33810222019-06-12 17:44:02 +0200233}
234
Willy Tarreau262c3f12019-12-17 06:52:51 +0100235/* parse a "replace-uri" or "replace-path" http-request action.
Willy Tarreau33810222019-06-12 17:44:02 +0200236 * This action takes 2 arguments (a regex and a replacement format string).
Christopher Faulet2c22a692019-12-18 15:39:56 +0100237 * The resulting rule makes use of <.action> to store the action (1/3 for now),
Christopher Faulet96bff762019-12-17 13:46:18 +0100238 * <http.re> to store the compiled regex, and <http.fmt> to store the log-format
Willy Tarreau33810222019-06-12 17:44:02 +0200239 * list head. It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
240 */
241static enum act_parse_ret parse_replace_uri(const char **args, int *orig_arg, struct proxy *px,
242 struct act_rule *rule, char **err)
243{
244 int cur_arg = *orig_arg;
245 char *error = NULL;
246
Willy Tarreau262c3f12019-12-17 06:52:51 +0100247 if (strcmp(args[cur_arg-1], "replace-path") == 0)
Christopher Faulet2c22a692019-12-18 15:39:56 +0100248 rule->action = 1; // replace-path, same as set-path
Willy Tarreau262c3f12019-12-17 06:52:51 +0100249 else
Christopher Faulet2c22a692019-12-18 15:39:56 +0100250 rule->action = 3; // replace-uri, same as set-uri
Willy Tarreau262c3f12019-12-17 06:52:51 +0100251
Willy Tarreau33810222019-06-12 17:44:02 +0200252 rule->action_ptr = http_action_replace_uri;
Christopher Faulet2eb53962020-01-14 14:47:34 +0100253 rule->release_ptr = release_http_action;
Willy Tarreau33810222019-06-12 17:44:02 +0200254
255 if (!*args[cur_arg] || !*args[cur_arg+1] ||
256 (*args[cur_arg+2] && strcmp(args[cur_arg+2], "if") != 0 && strcmp(args[cur_arg+2], "unless") != 0)) {
257 memprintf(err, "expects exactly 2 arguments <match-regex> and <replace-format>");
258 return ACT_RET_PRS_ERR;
259 }
260
Christopher Faulet96bff762019-12-17 13:46:18 +0100261 if (!(rule->arg.http.re = regex_comp(args[cur_arg], 1, 1, &error))) {
Willy Tarreau33810222019-06-12 17:44:02 +0200262 memprintf(err, "failed to parse the regex : %s", error);
263 free(error);
264 return ACT_RET_PRS_ERR;
265 }
266
Christopher Faulet96bff762019-12-17 13:46:18 +0100267 LIST_INIT(&rule->arg.http.fmt);
Willy Tarreau33810222019-06-12 17:44:02 +0200268 px->conf.args.ctx = ARGC_HRQ;
Christopher Faulet96bff762019-12-17 13:46:18 +0100269 if (!parse_logformat_string(args[cur_arg + 1], px, &rule->arg.http.fmt, LOG_OPT_HTTP,
Willy Tarreau33810222019-06-12 17:44:02 +0200270 (px->cap & PR_CAP_FE) ? SMP_VAL_FE_HRQ_HDR : SMP_VAL_BE_HRQ_HDR, err)) {
Christopher Faulet1337b322020-01-14 14:50:55 +0100271 regex_free(rule->arg.http.re);
Willy Tarreau33810222019-06-12 17:44:02 +0200272 return ACT_RET_PRS_ERR;
273 }
274
275 (*orig_arg) += 2;
276 return ACT_RET_PRS_OK;
277}
278
Willy Tarreau79e57332018-10-02 16:01:16 +0200279/* This function is just a compliant action wrapper for "set-status". */
280static enum act_return action_http_set_status(struct act_rule *rule, struct proxy *px,
281 struct session *sess, struct stream *s, int flags)
282{
Christopher Faulet96bff762019-12-17 13:46:18 +0100283 if (http_res_set_status(rule->arg.http.i, rule->arg.http.str, s) == -1) {
Christopher Faulete00d06c2019-12-16 17:18:42 +0100284 _HA_ATOMIC_ADD(&sess->fe->fe_counters.failed_rewrites, 1);
285 if (s->flags & SF_BE_ASSIGNED)
286 _HA_ATOMIC_ADD(&s->be->be_counters.failed_rewrites, 1);
287 if (sess->listener->counters)
288 _HA_ATOMIC_ADD(&sess->listener->counters->failed_rewrites, 1);
289 if (objt_server(s->target))
290 _HA_ATOMIC_ADD(&__objt_server(s->target)->counters.failed_rewrites, 1);
291
292 if (!(s->txn->req.flags & HTTP_MSGF_SOFT_RW))
293 return ACT_RET_ERR;
294 }
295
Willy Tarreau79e57332018-10-02 16:01:16 +0200296 return ACT_RET_CONT;
297}
298
299/* parse set-status action:
300 * This action accepts a single argument of type int representing
301 * an http status code. It returns ACT_RET_PRS_OK on success,
302 * ACT_RET_PRS_ERR on error.
303 */
304static enum act_parse_ret parse_http_set_status(const char **args, int *orig_arg, struct proxy *px,
305 struct act_rule *rule, char **err)
306{
307 char *error;
308
309 rule->action = ACT_CUSTOM;
310 rule->action_ptr = action_http_set_status;
Christopher Faulet2eb53962020-01-14 14:47:34 +0100311 rule->release_ptr = release_http_action;
Willy Tarreau79e57332018-10-02 16:01:16 +0200312
313 /* Check if an argument is available */
314 if (!*args[*orig_arg]) {
315 memprintf(err, "expects 1 argument: <status>; or 3 arguments: <status> reason <fmt>");
316 return ACT_RET_PRS_ERR;
317 }
318
319 /* convert status code as integer */
Christopher Faulet96bff762019-12-17 13:46:18 +0100320 rule->arg.http.i = strtol(args[*orig_arg], &error, 10);
321 if (*error != '\0' || rule->arg.http.i < 100 || rule->arg.http.i > 999) {
Willy Tarreau79e57332018-10-02 16:01:16 +0200322 memprintf(err, "expects an integer status code between 100 and 999");
323 return ACT_RET_PRS_ERR;
324 }
325
326 (*orig_arg)++;
327
328 /* set custom reason string */
Christopher Faulet96bff762019-12-17 13:46:18 +0100329 rule->arg.http.str = ist(NULL); // If null, we use the default reason for the status code.
Willy Tarreau79e57332018-10-02 16:01:16 +0200330 if (*args[*orig_arg] && strcmp(args[*orig_arg], "reason") == 0 &&
331 (*args[*orig_arg + 1] && strcmp(args[*orig_arg + 1], "if") != 0 && strcmp(args[*orig_arg + 1], "unless") != 0)) {
332 (*orig_arg)++;
Christopher Faulet96bff762019-12-17 13:46:18 +0100333 rule->arg.http.str.ptr = strdup(args[*orig_arg]);
334 rule->arg.http.str.len = strlen(rule->arg.http.str.ptr);
Willy Tarreau79e57332018-10-02 16:01:16 +0200335 (*orig_arg)++;
336 }
337
338 return ACT_RET_PRS_OK;
339}
340
341/* This function executes the "reject" HTTP action. It clears the request and
342 * response buffer without sending any response. It can be useful as an HTTP
343 * alternative to the silent-drop action to defend against DoS attacks, and may
344 * also be used with HTTP/2 to close a connection instead of just a stream.
345 * The txn status is unchanged, indicating no response was sent. The termination
Christopher Faulet8f1aa772019-07-04 11:27:15 +0200346 * flags will indicate "PR". It always returns ACT_RET_DONE.
Willy Tarreau79e57332018-10-02 16:01:16 +0200347 */
348static enum act_return http_action_reject(struct act_rule *rule, struct proxy *px,
349 struct session *sess, struct stream *s, int flags)
350{
Willy Tarreau0f9cd7b2019-01-31 19:02:43 +0100351 si_must_kill_conn(chn_prod(&s->req));
Willy Tarreau79e57332018-10-02 16:01:16 +0200352 channel_abort(&s->req);
353 channel_abort(&s->res);
354 s->req.analysers = 0;
355 s->res.analysers = 0;
356
Olivier Houcharda798bf52019-03-08 18:52:00 +0100357 _HA_ATOMIC_ADD(&s->be->be_counters.denied_req, 1);
358 _HA_ATOMIC_ADD(&sess->fe->fe_counters.denied_req, 1);
Willy Tarreau79e57332018-10-02 16:01:16 +0200359 if (sess->listener && sess->listener->counters)
Olivier Houcharda798bf52019-03-08 18:52:00 +0100360 _HA_ATOMIC_ADD(&sess->listener->counters->denied_req, 1);
Willy Tarreau79e57332018-10-02 16:01:16 +0200361
362 if (!(s->flags & SF_ERR_MASK))
363 s->flags |= SF_ERR_PRXCOND;
364 if (!(s->flags & SF_FINST_MASK))
365 s->flags |= SF_FINST_R;
366
Christopher Faulet8f1aa772019-07-04 11:27:15 +0200367 return ACT_RET_DONE;
Willy Tarreau79e57332018-10-02 16:01:16 +0200368}
369
370/* parse the "reject" action:
371 * This action takes no argument and returns ACT_RET_PRS_OK on success,
372 * ACT_RET_PRS_ERR on error.
373 */
374static enum act_parse_ret parse_http_action_reject(const char **args, int *orig_arg, struct proxy *px,
375 struct act_rule *rule, char **err)
376{
377 rule->action = ACT_CUSTOM;
378 rule->action_ptr = http_action_reject;
379 return ACT_RET_PRS_OK;
380}
381
Olivier Houchard602bf7d2019-05-10 13:59:15 +0200382/* This function executes the "disable-l7-retry" HTTP action.
383 * It disables L7 retries (all retry except for a connection failure). This
384 * can be useful for example to avoid retrying on POST requests.
385 * It just removes the L7 retry flag on the stream_interface, and always
386 * return ACT_RET_CONT;
387 */
388static enum act_return http_req_disable_l7_retry(struct act_rule *rule, struct proxy *px,
389 struct session *sess, struct stream *s, int flags)
390{
391 struct stream_interface *si = &s->si[1];
392
393 /* In theory, the SI_FL_L7_RETRY flags isn't set at this point, but
394 * let's be future-proof and remove it anyway.
395 */
396 si->flags &= ~SI_FL_L7_RETRY;
397 si->flags |= SI_FL_D_L7_RETRY;
398 return ACT_RET_CONT;
399}
400
401/* parse the "disable-l7-retry" action:
402 * This action takes no argument and returns ACT_RET_PRS_OK on success,
403 * ACT_RET_PRS_ERR on error.
404 */
405static enum act_parse_ret parse_http_req_disable_l7_retry(const char **args,
406 int *orig_args, struct proxy *px,
407 struct act_rule *rule, char **err)
408{
409 rule->action = ACT_CUSTOM;
410 rule->action_ptr = http_req_disable_l7_retry;
411 return ACT_RET_PRS_OK;
412}
413
Willy Tarreau79e57332018-10-02 16:01:16 +0200414/* This function executes the "capture" action. It executes a fetch expression,
415 * turns the result into a string and puts it in a capture slot. It always
416 * returns 1. If an error occurs the action is cancelled, but the rule
417 * processing continues.
418 */
419static enum act_return http_action_req_capture(struct act_rule *rule, struct proxy *px,
420 struct session *sess, struct stream *s, int flags)
421{
422 struct sample *key;
423 struct cap_hdr *h = rule->arg.cap.hdr;
424 char **cap = s->req_cap;
425 int len;
426
427 key = sample_fetch_as_type(s->be, sess, s, SMP_OPT_DIR_REQ|SMP_OPT_FINAL, rule->arg.cap.expr, SMP_T_STR);
428 if (!key)
429 return ACT_RET_CONT;
430
431 if (cap[h->index] == NULL)
432 cap[h->index] = pool_alloc(h->pool);
433
434 if (cap[h->index] == NULL) /* no more capture memory */
435 return ACT_RET_CONT;
436
437 len = key->data.u.str.data;
438 if (len > h->len)
439 len = h->len;
440
441 memcpy(cap[h->index], key->data.u.str.area, len);
442 cap[h->index][len] = 0;
443 return ACT_RET_CONT;
444}
445
446/* This function executes the "capture" action and store the result in a
447 * capture slot if exists. It executes a fetch expression, turns the result
448 * into a string and puts it in a capture slot. It always returns 1. If an
449 * error occurs the action is cancelled, but the rule processing continues.
450 */
451static enum act_return http_action_req_capture_by_id(struct act_rule *rule, struct proxy *px,
452 struct session *sess, struct stream *s, int flags)
453{
454 struct sample *key;
455 struct cap_hdr *h;
456 char **cap = s->req_cap;
457 struct proxy *fe = strm_fe(s);
458 int len;
459 int i;
460
461 /* Look for the original configuration. */
462 for (h = fe->req_cap, i = fe->nb_req_cap - 1;
463 h != NULL && i != rule->arg.capid.idx ;
464 i--, h = h->next);
465 if (!h)
466 return ACT_RET_CONT;
467
468 key = sample_fetch_as_type(s->be, sess, s, SMP_OPT_DIR_REQ|SMP_OPT_FINAL, rule->arg.capid.expr, SMP_T_STR);
469 if (!key)
470 return ACT_RET_CONT;
471
472 if (cap[h->index] == NULL)
473 cap[h->index] = pool_alloc(h->pool);
474
475 if (cap[h->index] == NULL) /* no more capture memory */
476 return ACT_RET_CONT;
477
478 len = key->data.u.str.data;
479 if (len > h->len)
480 len = h->len;
481
482 memcpy(cap[h->index], key->data.u.str.area, len);
483 cap[h->index][len] = 0;
484 return ACT_RET_CONT;
485}
486
487/* Check an "http-request capture" action.
488 *
489 * The function returns 1 in success case, otherwise, it returns 0 and err is
490 * filled.
491 */
492static int check_http_req_capture(struct act_rule *rule, struct proxy *px, char **err)
493{
494 if (rule->action_ptr != http_action_req_capture_by_id)
495 return 1;
496
497 if (rule->arg.capid.idx >= px->nb_req_cap) {
498 memprintf(err, "unable to find capture id '%d' referenced by http-request capture rule",
499 rule->arg.capid.idx);
500 return 0;
501 }
502
503 return 1;
504}
505
Christopher Faulet2eb53962020-01-14 14:47:34 +0100506/* Release memory allocate by an http capture action */
507static void release_http_capture(struct act_rule *rule)
508{
509 if (rule->action_ptr == http_action_req_capture)
510 release_sample_expr(rule->arg.cap.expr);
511 else
512 release_sample_expr(rule->arg.capid.expr);
513}
514
Willy Tarreau79e57332018-10-02 16:01:16 +0200515/* parse an "http-request capture" action. It takes a single argument which is
516 * a sample fetch expression. It stores the expression into arg->act.p[0] and
517 * the allocated hdr_cap struct or the preallocated "id" into arg->act.p[1].
518 * It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
519 */
520static enum act_parse_ret parse_http_req_capture(const char **args, int *orig_arg, struct proxy *px,
521 struct act_rule *rule, char **err)
522{
523 struct sample_expr *expr;
524 struct cap_hdr *hdr;
525 int cur_arg;
526 int len = 0;
527
528 for (cur_arg = *orig_arg; cur_arg < *orig_arg + 3 && *args[cur_arg]; cur_arg++)
529 if (strcmp(args[cur_arg], "if") == 0 ||
530 strcmp(args[cur_arg], "unless") == 0)
531 break;
532
533 if (cur_arg < *orig_arg + 3) {
534 memprintf(err, "expects <expression> [ 'len' <length> | id <idx> ]");
535 return ACT_RET_PRS_ERR;
536 }
537
538 cur_arg = *orig_arg;
539 expr = sample_parse_expr((char **)args, &cur_arg, px->conf.args.file, px->conf.args.line, err, &px->conf.args);
540 if (!expr)
541 return ACT_RET_PRS_ERR;
542
543 if (!(expr->fetch->val & SMP_VAL_FE_HRQ_HDR)) {
544 memprintf(err,
545 "fetch method '%s' extracts information from '%s', none of which is available here",
546 args[cur_arg-1], sample_src_names(expr->fetch->use));
Christopher Faulet1337b322020-01-14 14:50:55 +0100547 release_sample_expr(expr);
Willy Tarreau79e57332018-10-02 16:01:16 +0200548 return ACT_RET_PRS_ERR;
549 }
550
551 if (!args[cur_arg] || !*args[cur_arg]) {
552 memprintf(err, "expects 'len or 'id'");
Christopher Faulet1337b322020-01-14 14:50:55 +0100553 release_sample_expr(expr);
Willy Tarreau79e57332018-10-02 16:01:16 +0200554 return ACT_RET_PRS_ERR;
555 }
556
557 if (strcmp(args[cur_arg], "len") == 0) {
558 cur_arg++;
559
560 if (!(px->cap & PR_CAP_FE)) {
561 memprintf(err, "proxy '%s' has no frontend capability", px->id);
Christopher Faulet1337b322020-01-14 14:50:55 +0100562 release_sample_expr(expr);
Willy Tarreau79e57332018-10-02 16:01:16 +0200563 return ACT_RET_PRS_ERR;
564 }
565
566 px->conf.args.ctx = ARGC_CAP;
567
568 if (!args[cur_arg]) {
569 memprintf(err, "missing length value");
Christopher Faulet1337b322020-01-14 14:50:55 +0100570 release_sample_expr(expr);
Willy Tarreau79e57332018-10-02 16:01:16 +0200571 return ACT_RET_PRS_ERR;
572 }
573 /* we copy the table name for now, it will be resolved later */
574 len = atoi(args[cur_arg]);
575 if (len <= 0) {
576 memprintf(err, "length must be > 0");
Christopher Faulet1337b322020-01-14 14:50:55 +0100577 release_sample_expr(expr);
Willy Tarreau79e57332018-10-02 16:01:16 +0200578 return ACT_RET_PRS_ERR;
579 }
580 cur_arg++;
581
Willy Tarreau79e57332018-10-02 16:01:16 +0200582 hdr = calloc(1, sizeof(*hdr));
583 hdr->next = px->req_cap;
584 hdr->name = NULL; /* not a header capture */
585 hdr->namelen = 0;
586 hdr->len = len;
587 hdr->pool = create_pool("caphdr", hdr->len + 1, MEM_F_SHARED);
588 hdr->index = px->nb_req_cap++;
589
590 px->req_cap = hdr;
591 px->to_log |= LW_REQHDR;
592
593 rule->action = ACT_CUSTOM;
594 rule->action_ptr = http_action_req_capture;
Christopher Faulet2eb53962020-01-14 14:47:34 +0100595 rule->release_ptr = release_http_capture;
Willy Tarreau79e57332018-10-02 16:01:16 +0200596 rule->arg.cap.expr = expr;
597 rule->arg.cap.hdr = hdr;
598 }
599
600 else if (strcmp(args[cur_arg], "id") == 0) {
601 int id;
602 char *error;
603
604 cur_arg++;
605
606 if (!args[cur_arg]) {
607 memprintf(err, "missing id value");
Christopher Faulet1337b322020-01-14 14:50:55 +0100608 release_sample_expr(expr);
Willy Tarreau79e57332018-10-02 16:01:16 +0200609 return ACT_RET_PRS_ERR;
610 }
611
612 id = strtol(args[cur_arg], &error, 10);
613 if (*error != '\0') {
614 memprintf(err, "cannot parse id '%s'", args[cur_arg]);
Christopher Faulet1337b322020-01-14 14:50:55 +0100615 release_sample_expr(expr);
Willy Tarreau79e57332018-10-02 16:01:16 +0200616 return ACT_RET_PRS_ERR;
617 }
618 cur_arg++;
619
620 px->conf.args.ctx = ARGC_CAP;
621
622 rule->action = ACT_CUSTOM;
623 rule->action_ptr = http_action_req_capture_by_id;
624 rule->check_ptr = check_http_req_capture;
Christopher Faulet2eb53962020-01-14 14:47:34 +0100625 rule->release_ptr = release_http_capture;
Willy Tarreau79e57332018-10-02 16:01:16 +0200626 rule->arg.capid.expr = expr;
627 rule->arg.capid.idx = id;
628 }
629
630 else {
631 memprintf(err, "expects 'len' or 'id', found '%s'", args[cur_arg]);
Christopher Faulet1337b322020-01-14 14:50:55 +0100632 release_sample_expr(expr);
Willy Tarreau79e57332018-10-02 16:01:16 +0200633 return ACT_RET_PRS_ERR;
634 }
635
636 *orig_arg = cur_arg;
637 return ACT_RET_PRS_OK;
638}
639
640/* This function executes the "capture" action and store the result in a
641 * capture slot if exists. It executes a fetch expression, turns the result
642 * into a string and puts it in a capture slot. It always returns 1. If an
643 * error occurs the action is cancelled, but the rule processing continues.
644 */
645static enum act_return http_action_res_capture_by_id(struct act_rule *rule, struct proxy *px,
646 struct session *sess, struct stream *s, int flags)
647{
648 struct sample *key;
649 struct cap_hdr *h;
650 char **cap = s->res_cap;
651 struct proxy *fe = strm_fe(s);
652 int len;
653 int i;
654
655 /* Look for the original configuration. */
656 for (h = fe->rsp_cap, i = fe->nb_rsp_cap - 1;
657 h != NULL && i != rule->arg.capid.idx ;
658 i--, h = h->next);
659 if (!h)
660 return ACT_RET_CONT;
661
662 key = sample_fetch_as_type(s->be, sess, s, SMP_OPT_DIR_RES|SMP_OPT_FINAL, rule->arg.capid.expr, SMP_T_STR);
663 if (!key)
664 return ACT_RET_CONT;
665
666 if (cap[h->index] == NULL)
667 cap[h->index] = pool_alloc(h->pool);
668
669 if (cap[h->index] == NULL) /* no more capture memory */
670 return ACT_RET_CONT;
671
672 len = key->data.u.str.data;
673 if (len > h->len)
674 len = h->len;
675
676 memcpy(cap[h->index], key->data.u.str.area, len);
677 cap[h->index][len] = 0;
678 return ACT_RET_CONT;
679}
680
681/* Check an "http-response capture" action.
682 *
683 * The function returns 1 in success case, otherwise, it returns 0 and err is
684 * filled.
685 */
686static int check_http_res_capture(struct act_rule *rule, struct proxy *px, char **err)
687{
688 if (rule->action_ptr != http_action_res_capture_by_id)
689 return 1;
690
691 if (rule->arg.capid.idx >= px->nb_rsp_cap) {
692 memprintf(err, "unable to find capture id '%d' referenced by http-response capture rule",
693 rule->arg.capid.idx);
694 return 0;
695 }
696
697 return 1;
698}
699
700/* parse an "http-response capture" action. It takes a single argument which is
701 * a sample fetch expression. It stores the expression into arg->act.p[0] and
702 * the allocated hdr_cap struct od the preallocated id into arg->act.p[1].
703 * It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
704 */
705static enum act_parse_ret parse_http_res_capture(const char **args, int *orig_arg, struct proxy *px,
706 struct act_rule *rule, char **err)
707{
708 struct sample_expr *expr;
709 int cur_arg;
710 int id;
711 char *error;
712
713 for (cur_arg = *orig_arg; cur_arg < *orig_arg + 3 && *args[cur_arg]; cur_arg++)
714 if (strcmp(args[cur_arg], "if") == 0 ||
715 strcmp(args[cur_arg], "unless") == 0)
716 break;
717
718 if (cur_arg < *orig_arg + 3) {
719 memprintf(err, "expects <expression> id <idx>");
720 return ACT_RET_PRS_ERR;
721 }
722
723 cur_arg = *orig_arg;
724 expr = sample_parse_expr((char **)args, &cur_arg, px->conf.args.file, px->conf.args.line, err, &px->conf.args);
725 if (!expr)
726 return ACT_RET_PRS_ERR;
727
728 if (!(expr->fetch->val & SMP_VAL_FE_HRS_HDR)) {
729 memprintf(err,
730 "fetch method '%s' extracts information from '%s', none of which is available here",
731 args[cur_arg-1], sample_src_names(expr->fetch->use));
Christopher Faulet1337b322020-01-14 14:50:55 +0100732 release_sample_expr(expr);
Willy Tarreau79e57332018-10-02 16:01:16 +0200733 return ACT_RET_PRS_ERR;
734 }
735
736 if (!args[cur_arg] || !*args[cur_arg]) {
737 memprintf(err, "expects 'id'");
Christopher Faulet1337b322020-01-14 14:50:55 +0100738 release_sample_expr(expr);
Willy Tarreau79e57332018-10-02 16:01:16 +0200739 return ACT_RET_PRS_ERR;
740 }
741
742 if (strcmp(args[cur_arg], "id") != 0) {
743 memprintf(err, "expects 'id', found '%s'", args[cur_arg]);
Christopher Faulet1337b322020-01-14 14:50:55 +0100744 release_sample_expr(expr);
Willy Tarreau79e57332018-10-02 16:01:16 +0200745 return ACT_RET_PRS_ERR;
746 }
747
748 cur_arg++;
749
750 if (!args[cur_arg]) {
751 memprintf(err, "missing id value");
Christopher Faulet1337b322020-01-14 14:50:55 +0100752 release_sample_expr(expr);
Willy Tarreau79e57332018-10-02 16:01:16 +0200753 return ACT_RET_PRS_ERR;
754 }
755
756 id = strtol(args[cur_arg], &error, 10);
757 if (*error != '\0') {
758 memprintf(err, "cannot parse id '%s'", args[cur_arg]);
Christopher Faulet1337b322020-01-14 14:50:55 +0100759 release_sample_expr(expr);
Willy Tarreau79e57332018-10-02 16:01:16 +0200760 return ACT_RET_PRS_ERR;
761 }
762 cur_arg++;
763
764 px->conf.args.ctx = ARGC_CAP;
765
766 rule->action = ACT_CUSTOM;
767 rule->action_ptr = http_action_res_capture_by_id;
768 rule->check_ptr = check_http_res_capture;
Christopher Faulet2eb53962020-01-14 14:47:34 +0100769 rule->release_ptr = release_http_capture;
Willy Tarreau79e57332018-10-02 16:01:16 +0200770 rule->arg.capid.expr = expr;
771 rule->arg.capid.idx = id;
772
773 *orig_arg = cur_arg;
774 return ACT_RET_PRS_OK;
775}
776
Christopher Faulet81e20172019-12-12 16:40:30 +0100777/* Parse a "allow" action for a request or a response rule. It takes no argument. It
778 * returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
779 */
780static enum act_parse_ret parse_http_allow(const char **args, int *orig_arg, struct proxy *px,
781 struct act_rule *rule, char **err)
782{
783 rule->action = ACT_ACTION_ALLOW;
Christopher Faulet245cf792019-12-18 14:58:12 +0100784 rule->flags |= ACT_FLAG_FINAL;
Christopher Faulet81e20172019-12-12 16:40:30 +0100785 return ACT_RET_PRS_OK;
786}
787
Christopher Faulet554c0eb2020-01-14 12:00:28 +0100788/* Check an "http-request deny" action when an http-errors section is referenced.
789 *
790 * The function returns 1 in success case, otherwise, it returns 0 and err is
791 * filled.
792 */
793static int check_http_deny_action(struct act_rule *rule, struct proxy *px, char **err)
794{
795 struct http_errors *http_errs;
796 int status = (intptr_t)(rule->arg.act.p[0]);
797 int ret = 1;
798
799 list_for_each_entry(http_errs, &http_errors_list, list) {
800 if (strcmp(http_errs->id, (char *)rule->arg.act.p[1]) == 0) {
801 free(rule->arg.act.p[1]);
802 rule->arg.http_deny.status = status;
803 rule->arg.http_deny.errmsg = http_errs->errmsg[http_get_status_idx(status)];
804 if (!rule->arg.http_deny.errmsg)
805 ha_warning("Proxy '%s': status '%d' referenced by http deny rule "
806 "not declared in http-errors section '%s'.\n",
807 px->id, status, http_errs->id);
808 break;
809 }
810 }
811
812 if (&http_errs->list == &http_errors_list) {
813 memprintf(err, "unknown http-errors section '%s' referenced by http deny rule",
814 (char *)rule->arg.act.p[1]);
815 free(rule->arg.act.p[1]);
816 ret = 0;
817 }
818
819 return ret;
820}
821
Christopher Faulete0fca292020-01-13 21:49:03 +0100822/* Parse "deny" or "tarpit" actions for a request rule or "deny" action for a
Christopher Faulet554c0eb2020-01-14 12:00:28 +0100823 * response rule. It may take optional arguments to define the status code, the
824 * error file or the http-errors section to use. It returns ACT_RET_PRS_OK on
825 * success, ACT_RET_PRS_ERR on error.
Christopher Faulet81e20172019-12-12 16:40:30 +0100826 */
Christopher Faulete0fca292020-01-13 21:49:03 +0100827static enum act_parse_ret parse_http_deny(const char **args, int *orig_arg, struct proxy *px,
828 struct act_rule *rule, char **err)
Christopher Faulet81e20172019-12-12 16:40:30 +0100829{
Christopher Faulet554c0eb2020-01-14 12:00:28 +0100830 int default_status, status, hc, cur_arg;
831
Christopher Faulet81e20172019-12-12 16:40:30 +0100832
833 cur_arg = *orig_arg;
Christopher Faulete0fca292020-01-13 21:49:03 +0100834 if (rule->from == ACT_F_HTTP_REQ) {
835 if (!strcmp(args[cur_arg-1], "tarpit")) {
836 rule->action = ACT_HTTP_REQ_TARPIT;
Christopher Faulet554c0eb2020-01-14 12:00:28 +0100837 default_status = status = 500;
Christopher Faulet81e20172019-12-12 16:40:30 +0100838 }
Christopher Faulete0fca292020-01-13 21:49:03 +0100839 else {
840 rule->action = ACT_ACTION_DENY;
Christopher Faulet554c0eb2020-01-14 12:00:28 +0100841 default_status = status = 403;
Christopher Faulet81e20172019-12-12 16:40:30 +0100842 }
Christopher Faulet81e20172019-12-12 16:40:30 +0100843 }
Christopher Faulete0fca292020-01-13 21:49:03 +0100844 else {
Christopher Faulet554c0eb2020-01-14 12:00:28 +0100845 rule->action = ACT_ACTION_DENY;
846 default_status = status = 502;
Christopher Faulete0fca292020-01-13 21:49:03 +0100847 }
Christopher Faulet245cf792019-12-18 14:58:12 +0100848 rule->flags |= ACT_FLAG_FINAL;
Christopher Faulet040c8cd2020-01-13 16:43:45 +0100849
850 if (strcmp(args[cur_arg], "deny_status") == 0) {
851 cur_arg++;
852 if (!*args[cur_arg]) {
Christopher Faulet554c0eb2020-01-14 12:00:28 +0100853 memprintf(err, "'%s' expects <status_code> as argument", args[cur_arg-1]);
Christopher Faulet040c8cd2020-01-13 16:43:45 +0100854 return ACT_RET_PRS_ERR;
855 }
856
Christopher Faulet554c0eb2020-01-14 12:00:28 +0100857 status = atol(args[cur_arg]);
Christopher Faulet040c8cd2020-01-13 16:43:45 +0100858 cur_arg++;
859 for (hc = 0; hc < HTTP_ERR_SIZE; hc++) {
Christopher Faulet554c0eb2020-01-14 12:00:28 +0100860 if (http_err_codes[hc] == status)
Christopher Faulet040c8cd2020-01-13 16:43:45 +0100861 break;
Christopher Faulet040c8cd2020-01-13 16:43:45 +0100862 }
Christopher Faulet554c0eb2020-01-14 12:00:28 +0100863 if (hc >= HTTP_ERR_SIZE) {
864 memprintf(err, "status code '%d' not handled, using default code '%d'",
865 status, default_status);
866 status = default_status;
867 hc = http_get_status_idx(status);
868 }
Christopher Faulet040c8cd2020-01-13 16:43:45 +0100869 }
870
Christopher Faulet554c0eb2020-01-14 12:00:28 +0100871 if (strcmp(args[cur_arg], "errorfile") == 0) {
872 cur_arg++;
873 if (!*args[cur_arg]) {
874 memprintf(err, "'%s' expects <file> as argument", args[cur_arg-1]);
875 return ACT_RET_PRS_ERR;
876 }
877
878 rule->arg.http_deny.errmsg = http_load_errorfile(args[cur_arg], err);
879 if (!rule->arg.http_deny.errmsg)
880 return ACT_RET_PRS_ERR;
881 cur_arg++;
882 }
883 else if (strcmp(args[cur_arg], "errorfiles") == 0) {
884 cur_arg++;
885 if (!*args[cur_arg]) {
886 memprintf(err, "'%s' expects <http_errors_name> as argument", args[cur_arg-1]);
887 return ACT_RET_PRS_ERR;
888 }
889 /* Must be resolved during the config validity check */
890 rule->arg.act.p[0] = (void *)((intptr_t)status);
891 rule->arg.act.p[1] = strdup(args[cur_arg]);
892 rule->check_ptr = check_http_deny_action;
893 cur_arg++;
894 goto out;
895 }
896
897 rule->arg.http_deny.status = status;
898
899 out:
Christopher Faulet040c8cd2020-01-13 16:43:45 +0100900 *orig_arg = cur_arg;
Christopher Faulet81e20172019-12-12 16:40:30 +0100901 return ACT_RET_PRS_OK;
902}
903
904/* Parse a "auth" action. It may take 2 optional arguments to define a "realm"
905 * parameter. It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
906 */
907static enum act_parse_ret parse_http_auth(const char **args, int *orig_arg, struct proxy *px,
908 struct act_rule *rule, char **err)
909{
910 int cur_arg;
911
912 rule->action = ACT_HTTP_REQ_AUTH;
Christopher Faulet245cf792019-12-18 14:58:12 +0100913 rule->flags |= ACT_FLAG_FINAL;
Christopher Faulet2eb53962020-01-14 14:47:34 +0100914 rule->release_ptr = release_http_action;
Christopher Faulet81e20172019-12-12 16:40:30 +0100915
916 cur_arg = *orig_arg;
917 if (!strcmp(args[cur_arg], "realm")) {
918 cur_arg++;
919 if (!*args[cur_arg]) {
920 memprintf(err, "missing realm value.\n");
921 return ACT_RET_PRS_ERR;
922 }
Christopher Faulet96bff762019-12-17 13:46:18 +0100923 rule->arg.http.str.ptr = strdup(args[cur_arg]);
924 rule->arg.http.str.len = strlen(rule->arg.http.str.ptr);
Christopher Faulet81e20172019-12-12 16:40:30 +0100925 cur_arg++;
926 }
927
928 *orig_arg = cur_arg;
929 return ACT_RET_PRS_OK;
930}
931
932/* Parse a "set-nice" action. It takes the nice value as argument. It returns
933 * ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
934 */
935static enum act_parse_ret parse_http_set_nice(const char **args, int *orig_arg, struct proxy *px,
936 struct act_rule *rule, char **err)
937{
938 int cur_arg;
939
940 rule->action = ACT_HTTP_SET_NICE;
941
942 cur_arg = *orig_arg;
943 if (!*args[cur_arg]) {
944 memprintf(err, "expects exactly 1 argument (integer value)");
945 return ACT_RET_PRS_ERR;
946 }
Christopher Faulet96bff762019-12-17 13:46:18 +0100947 rule->arg.http.i = atoi(args[cur_arg]);
948 if (rule->arg.http.i < -1024)
949 rule->arg.http.i = -1024;
950 else if (rule->arg.http.i > 1024)
951 rule->arg.http.i = 1024;
Christopher Faulet81e20172019-12-12 16:40:30 +0100952
953 *orig_arg = cur_arg + 1;
954 return ACT_RET_PRS_OK;
955}
956
957/* Parse a "set-tos" action. It takes the TOS value as argument. It returns
958 * ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
959 */
960static enum act_parse_ret parse_http_set_tos(const char **args, int *orig_arg, struct proxy *px,
961 struct act_rule *rule, char **err)
962{
963#ifdef IP_TOS
964 char *endp;
965 int cur_arg;
966
967 rule->action = ACT_HTTP_SET_TOS;
968
969 cur_arg = *orig_arg;
970 if (!*args[cur_arg]) {
971 memprintf(err, "expects exactly 1 argument (integer/hex value)");
972 return ACT_RET_PRS_ERR;
973 }
Christopher Faulet96bff762019-12-17 13:46:18 +0100974 rule->arg.http.i = strtol(args[cur_arg], &endp, 0);
Christopher Faulet81e20172019-12-12 16:40:30 +0100975 if (endp && *endp != '\0') {
976 memprintf(err, "invalid character starting at '%s' (integer/hex value expected)", endp);
977 return ACT_RET_PRS_ERR;
978 }
979
980 *orig_arg = cur_arg + 1;
981 return ACT_RET_PRS_OK;
982#else
983 memprintf(err, "not supported on this platform (IP_TOS undefined)");
984 return ACT_RET_PRS_ERR;
985#endif
986}
987
988/* Parse a "set-mark" action. It takes the MARK value as argument. It returns
989 * ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
990 */
991static enum act_parse_ret parse_http_set_mark(const char **args, int *orig_arg, struct proxy *px,
992 struct act_rule *rule, char **err)
993{
994#ifdef SO_MARK
995 char *endp;
996 int cur_arg;
997
998 rule->action = ACT_HTTP_SET_MARK;
999
1000 cur_arg = *orig_arg;
1001 if (!*args[cur_arg]) {
1002 memprintf(err, "expects exactly 1 argument (integer/hex value)");
1003 return ACT_RET_PRS_ERR;
1004 }
Christopher Faulet96bff762019-12-17 13:46:18 +01001005 rule->arg.http.i = strtoul(args[cur_arg], &endp, 0);
Christopher Faulet81e20172019-12-12 16:40:30 +01001006 if (endp && *endp != '\0') {
1007 memprintf(err, "invalid character starting at '%s' (integer/hex value expected)", endp);
1008 return ACT_RET_PRS_ERR;
1009 }
1010
1011 *orig_arg = cur_arg + 1;
1012 global.last_checks |= LSTCHK_NETADM;
1013 return ACT_RET_PRS_OK;
1014#else
1015 memprintf(err, "not supported on this platform (SO_MARK undefined)");
1016 return ACT_RET_PRS_ERR;
1017#endif
1018}
1019
1020/* Parse a "set-log-level" action. It takes the level value as argument. It
1021 * returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
1022 */
1023static enum act_parse_ret parse_http_set_log_level(const char **args, int *orig_arg, struct proxy *px,
1024 struct act_rule *rule, char **err)
1025{
1026 int cur_arg;
1027
1028 rule->action = ACT_HTTP_SET_LOGL;
1029
1030 cur_arg = *orig_arg;
1031 if (!*args[cur_arg]) {
1032 bad_log_level:
1033 memprintf(err, "expects exactly 1 argument (log level name or 'silent')");
1034 return ACT_RET_PRS_ERR;
1035 }
1036 if (strcmp(args[cur_arg], "silent") == 0)
Christopher Faulet96bff762019-12-17 13:46:18 +01001037 rule->arg.http.i = -1;
1038 else if ((rule->arg.http.i = get_log_level(args[cur_arg]) + 1) == 0)
Christopher Faulet81e20172019-12-12 16:40:30 +01001039 goto bad_log_level;
1040
1041 *orig_arg = cur_arg + 1;
1042 return ACT_RET_PRS_OK;
1043}
1044
Christopher Faulet91b3ec12020-01-17 22:30:06 +01001045/* This function executes a early-hint action. It adds an HTTP Early Hint HTTP
1046 * 103 response header with <.arg.http.str> name and with a value built
1047 * according to <.arg.http.fmt> log line format. If it is the first early-hint
1048 * rule of a serie, the 103 response start-line is added first. At the end, if
1049 * the next rule is not an early-hint rule or if it is the last rule, the EOH
1050 * block is added to terminate the response. On success, it returns
1051 * ACT_RET_CONT. If an error occurs while soft rewrites are enabled, the action
1052 * is canceled, but the rule processing continue. Otherwsize ACT_RET_ERR is
1053 * returned.
1054 */
1055static enum act_return http_action_early_hint(struct act_rule *rule, struct proxy *px,
1056 struct session *sess, struct stream *s, int flags)
1057{
1058 struct act_rule *prev_rule, *next_rule;
1059 struct channel *res = &s->res;
1060 struct htx *htx = htx_from_buf(&res->buf);
1061 struct buffer *value = alloc_trash_chunk();
1062 enum act_return ret = ACT_RET_CONT;
1063
1064 if (!(s->txn->req.flags & HTTP_MSGF_VER_11))
1065 goto leave;
1066
1067 if (!value) {
1068 if (!(s->flags & SF_ERR_MASK))
1069 s->flags |= SF_ERR_RESOURCE;
1070 goto error;
1071 }
1072
1073 /* get previous and next rules */
1074 prev_rule = LIST_PREV(&rule->list, typeof(rule), list);
1075 next_rule = LIST_NEXT(&rule->list, typeof(rule), list);
1076
1077 /* if no previous rule or previous rule is not early-hint, start a new response. Otherwise,
1078 * continue to add link to a previously started response */
1079 if (&prev_rule->list == s->current_rule_list || prev_rule->action_ptr != http_action_early_hint) {
1080 struct htx_sl *sl;
1081 unsigned int flags = (HTX_SL_F_IS_RESP|HTX_SL_F_VER_11|
1082 HTX_SL_F_XFER_LEN|HTX_SL_F_BODYLESS);
1083
1084 sl = htx_add_stline(htx, HTX_BLK_RES_SL, flags,
1085 ist("HTTP/1.1"), ist("103"), ist("Early Hints"));
1086 if (!sl)
1087 goto error;
1088 sl->info.res.status = 103;
1089 }
1090
1091 /* Add the HTTP Early Hint HTTP 103 response heade */
1092 value->data = build_logline(s, b_tail(value), b_room(value), &rule->arg.http.fmt);
1093 if (!htx_add_header(htx, rule->arg.http.str, ist2(b_head(value), b_data(value))))
1094 goto error;
1095
1096 /* if it is the last rule or the next one is not an early-hint, terminate the current
1097 * response. */
1098 if (&next_rule->list == s->current_rule_list || next_rule->action_ptr != http_action_early_hint) {
1099 size_t data;
1100
1101 if (!htx_add_endof(htx, HTX_BLK_EOH)) {
1102 /* If an error occurred during an Early-hint rule,
1103 * remove the incomplete HTTP 103 response from the
1104 * buffer */
1105 goto error;
1106 }
1107
1108 data = htx->data - co_data(res);
1109 c_adv(res, data);
1110 res->total += data;
1111 }
1112
1113 leave:
1114 free_trash_chunk(value);
1115 return ret;
1116
1117 error:
1118 /* If an error occurred during an Early-hint rule, remove the incomplete
1119 * HTTP 103 response from the buffer */
1120 channel_htx_truncate(res, htx);
1121 ret = ACT_RET_ERR;
1122 goto leave;
1123}
1124
Christopher Fauletd1f27e32019-12-17 09:33:38 +01001125/* This function executes a set-header or add-header actions. It builds a string
1126 * in the trash from the specified format string. It finds the action to be
1127 * performed in <.action>, previously filled by function parse_set_header(). The
1128 * replacement action is excuted by the function http_action_set_header(). On
1129 * success, it returns ACT_RET_CONT. If an error occurs while soft rewrites are
1130 * enabled, the action is canceled, but the rule processing continue. Otherwsize
1131 * ACT_RET_ERR is returned.
1132 */
1133static enum act_return http_action_set_header(struct act_rule *rule, struct proxy *px,
1134 struct session *sess, struct stream *s, int flags)
1135{
1136 struct htx *htx = htxbuf((rule->from == ACT_F_HTTP_REQ) ? &s->req.buf : &s->res.buf);
1137 enum act_return ret = ACT_RET_CONT;
1138 struct buffer *replace;
1139 struct http_hdr_ctx ctx;
1140 struct ist n, v;
1141
1142 replace = alloc_trash_chunk();
1143 if (!replace)
1144 goto fail_alloc;
1145
1146 replace->data = build_logline(s, replace->area, replace->size, &rule->arg.http.fmt);
1147 n = rule->arg.http.str;
1148 v = ist2(replace->area, replace->data);
1149
1150 if (rule->action == 0) { // set-header
1151 /* remove all occurrences of the header */
1152 ctx.blk = NULL;
1153 while (http_find_header(htx, n, &ctx, 1))
1154 http_remove_header(htx, &ctx);
1155 }
1156
1157 /* Now add header */
1158 if (!http_add_header(htx, n, v))
1159 goto fail_rewrite;
1160
1161 leave:
1162 free_trash_chunk(replace);
1163 return ret;
1164
1165 fail_alloc:
1166 if (!(s->flags & SF_ERR_MASK))
1167 s->flags |= SF_ERR_RESOURCE;
1168 ret = ACT_RET_ERR;
1169 goto leave;
1170
1171 fail_rewrite:
1172 _HA_ATOMIC_ADD(&sess->fe->fe_counters.failed_rewrites, 1);
1173 if (s->flags & SF_BE_ASSIGNED)
1174 _HA_ATOMIC_ADD(&s->be->be_counters.failed_rewrites, 1);
1175 if (sess->listener->counters)
1176 _HA_ATOMIC_ADD(&sess->listener->counters->failed_rewrites, 1);
1177 if (objt_server(s->target))
1178 _HA_ATOMIC_ADD(&__objt_server(s->target)->counters.failed_rewrites, 1);
1179
1180 if (!(s->txn->req.flags & HTTP_MSGF_SOFT_RW))
1181 ret = ACT_RET_ERR;
1182 goto leave;
1183}
1184
Christopher Faulet81e20172019-12-12 16:40:30 +01001185/* Parse a "set-header", "add-header" or "early-hint" actions. It takes an
1186 * header name and a log-format string as arguments. It returns ACT_RET_PRS_OK
1187 * on success, ACT_RET_PRS_ERR on error.
1188 *
1189 * Note: same function is used for the request and the response. However
1190 * "early-hint" rules are only supported for request rules.
1191 */
1192static enum act_parse_ret parse_http_set_header(const char **args, int *orig_arg, struct proxy *px,
1193 struct act_rule *rule, char **err)
1194{
Christopher Faulet81e20172019-12-12 16:40:30 +01001195 int cap, cur_arg;
1196
Christopher Faulet91b3ec12020-01-17 22:30:06 +01001197 if (args[*orig_arg-1][0] == 'e') {
1198 rule->action = ACT_CUSTOM;
1199 rule->action_ptr = http_action_early_hint;
1200 }
Christopher Fauletd1f27e32019-12-17 09:33:38 +01001201 else {
1202 if (args[*orig_arg-1][0] == 's')
1203 rule->action = 0; // set-header
1204 else
1205 rule->action = 1; // add-header
1206 rule->action_ptr = http_action_set_header;
1207 }
Christopher Faulet2eb53962020-01-14 14:47:34 +01001208 rule->release_ptr = release_http_action;
Christopher Faulet81e20172019-12-12 16:40:30 +01001209
1210 cur_arg = *orig_arg;
1211 if (!*args[cur_arg] || !*args[cur_arg+1]) {
1212 memprintf(err, "expects exactly 2 arguments");
1213 return ACT_RET_PRS_ERR;
1214 }
1215
Christopher Faulet81e20172019-12-12 16:40:30 +01001216
Christopher Faulet96bff762019-12-17 13:46:18 +01001217 rule->arg.http.str.ptr = strdup(args[cur_arg]);
1218 rule->arg.http.str.len = strlen(rule->arg.http.str.ptr);
1219 LIST_INIT(&rule->arg.http.fmt);
Christopher Faulet81e20172019-12-12 16:40:30 +01001220
1221 if (rule->from == ACT_F_HTTP_REQ) {
1222 px->conf.args.ctx = ARGC_HRQ;
1223 cap = (px->cap & PR_CAP_FE) ? SMP_VAL_FE_HRQ_HDR : SMP_VAL_BE_HRQ_HDR;
1224 }
1225 else{
1226 px->conf.args.ctx = ARGC_HRS;
1227 cap = (px->cap & PR_CAP_BE) ? SMP_VAL_BE_HRS_HDR : SMP_VAL_FE_HRS_HDR;
1228 }
1229
1230 cur_arg++;
Christopher Faulet1337b322020-01-14 14:50:55 +01001231 if (!parse_logformat_string(args[cur_arg], px, &rule->arg.http.fmt, LOG_OPT_HTTP, cap, err)) {
1232 free(rule->arg.http.str.ptr);
Christopher Faulet81e20172019-12-12 16:40:30 +01001233 return ACT_RET_PRS_ERR;
Christopher Faulet1337b322020-01-14 14:50:55 +01001234 }
Christopher Faulet81e20172019-12-12 16:40:30 +01001235
1236 free(px->conf.lfs_file);
1237 px->conf.lfs_file = strdup(px->conf.args.file);
1238 px->conf.lfs_line = px->conf.args.line;
1239
1240 *orig_arg = cur_arg + 1;
1241 return ACT_RET_PRS_OK;
1242}
1243
Christopher Faulet92d34fe2019-12-17 09:20:34 +01001244/* This function executes a replace-header or replace-value actions. It
1245 * builds a string in the trash from the specified format string. It finds
1246 * the action to be performed in <.action>, previously filled by function
1247 * parse_replace_header(). The replacement action is excuted by the function
1248 * http_action_replace_header(). On success, it returns ACT_RET_CONT. If an error
1249 * occurs while soft rewrites are enabled, the action is canceled, but the rule
1250 * processing continue. Otherwsize ACT_RET_ERR is returned.
1251 */
1252static enum act_return http_action_replace_header(struct act_rule *rule, struct proxy *px,
1253 struct session *sess, struct stream *s, int flags)
1254{
1255 struct htx *htx = htxbuf((rule->from == ACT_F_HTTP_REQ) ? &s->req.buf : &s->res.buf);
1256 enum act_return ret = ACT_RET_CONT;
1257 struct buffer *replace;
1258 int r;
1259
1260 replace = alloc_trash_chunk();
1261 if (!replace)
1262 goto fail_alloc;
1263
1264 replace->data = build_logline(s, replace->area, replace->size, &rule->arg.http.fmt);
1265
1266 r = http_replace_hdrs(s, htx, rule->arg.http.str, replace->area, rule->arg.http.re, (rule->action == 0));
1267 if (r == -1)
1268 goto fail_rewrite;
1269
1270 leave:
1271 free_trash_chunk(replace);
1272 return ret;
1273
1274 fail_alloc:
1275 if (!(s->flags & SF_ERR_MASK))
1276 s->flags |= SF_ERR_RESOURCE;
1277 ret = ACT_RET_ERR;
1278 goto leave;
1279
1280 fail_rewrite:
1281 _HA_ATOMIC_ADD(&sess->fe->fe_counters.failed_rewrites, 1);
1282 if (s->flags & SF_BE_ASSIGNED)
1283 _HA_ATOMIC_ADD(&s->be->be_counters.failed_rewrites, 1);
1284 if (sess->listener->counters)
1285 _HA_ATOMIC_ADD(&sess->listener->counters->failed_rewrites, 1);
1286 if (objt_server(s->target))
1287 _HA_ATOMIC_ADD(&__objt_server(s->target)->counters.failed_rewrites, 1);
1288
1289 if (!(s->txn->req.flags & HTTP_MSGF_SOFT_RW))
1290 ret = ACT_RET_ERR;
1291 goto leave;
1292}
1293
Christopher Faulet81e20172019-12-12 16:40:30 +01001294/* Parse a "replace-header" or "replace-value" actions. It takes an header name,
1295 * a regex and replacement string as arguments. It returns ACT_RET_PRS_OK on
1296 * success, ACT_RET_PRS_ERR on error.
1297 */
1298static enum act_parse_ret parse_http_replace_header(const char **args, int *orig_arg, struct proxy *px,
1299 struct act_rule *rule, char **err)
1300{
1301 int cap, cur_arg;
1302
Christopher Faulet92d34fe2019-12-17 09:20:34 +01001303 if (args[*orig_arg-1][8] == 'h')
1304 rule->action = 0; // replace-header
1305 else
1306 rule->action = 1; // replace-value
1307 rule->action_ptr = http_action_replace_header;
Christopher Faulet2eb53962020-01-14 14:47:34 +01001308 rule->release_ptr = release_http_action;
Christopher Faulet81e20172019-12-12 16:40:30 +01001309
1310 cur_arg = *orig_arg;
1311 if (!*args[cur_arg] || !*args[cur_arg+1] || !*args[cur_arg+2]) {
1312 memprintf(err, "expects exactly 3 arguments");
1313 return ACT_RET_PRS_ERR;
1314 }
1315
Christopher Faulet96bff762019-12-17 13:46:18 +01001316 rule->arg.http.str.ptr = strdup(args[cur_arg]);
1317 rule->arg.http.str.len = strlen(rule->arg.http.str.ptr);
1318 LIST_INIT(&rule->arg.http.fmt);
Christopher Faulet81e20172019-12-12 16:40:30 +01001319
1320 cur_arg++;
Christopher Faulet1337b322020-01-14 14:50:55 +01001321 if (!(rule->arg.http.re = regex_comp(args[cur_arg], 1, 1, err))) {
1322 free(rule->arg.http.str.ptr);
Christopher Faulet81e20172019-12-12 16:40:30 +01001323 return ACT_RET_PRS_ERR;
Christopher Faulet1337b322020-01-14 14:50:55 +01001324 }
Christopher Faulet81e20172019-12-12 16:40:30 +01001325
1326 if (rule->from == ACT_F_HTTP_REQ) {
1327 px->conf.args.ctx = ARGC_HRQ;
1328 cap = (px->cap & PR_CAP_FE) ? SMP_VAL_FE_HRQ_HDR : SMP_VAL_BE_HRQ_HDR;
1329 }
1330 else{
1331 px->conf.args.ctx = ARGC_HRS;
1332 cap = (px->cap & PR_CAP_BE) ? SMP_VAL_BE_HRS_HDR : SMP_VAL_FE_HRS_HDR;
1333 }
1334
1335 cur_arg++;
Christopher Faulet1337b322020-01-14 14:50:55 +01001336 if (!parse_logformat_string(args[cur_arg], px, &rule->arg.http.fmt, LOG_OPT_HTTP, cap, err)) {
1337 free(rule->arg.http.str.ptr);
1338 regex_free(rule->arg.http.re);
Christopher Faulet81e20172019-12-12 16:40:30 +01001339 return ACT_RET_PRS_ERR;
Christopher Faulet1337b322020-01-14 14:50:55 +01001340 }
Christopher Faulet81e20172019-12-12 16:40:30 +01001341
1342 free(px->conf.lfs_file);
1343 px->conf.lfs_file = strdup(px->conf.args.file);
1344 px->conf.lfs_line = px->conf.args.line;
1345
1346 *orig_arg = cur_arg + 1;
1347 return ACT_RET_PRS_OK;
1348}
1349
1350/* Parse a "del-header" action. It takes an header name as argument. It returns
1351 * ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
1352 */
1353static enum act_parse_ret parse_http_del_header(const char **args, int *orig_arg, struct proxy *px,
1354 struct act_rule *rule, char **err)
1355{
1356 int cur_arg;
1357
1358 rule->action = ACT_HTTP_DEL_HDR;
Christopher Faulet2eb53962020-01-14 14:47:34 +01001359 rule->release_ptr = release_http_action;
Christopher Faulet81e20172019-12-12 16:40:30 +01001360
1361 cur_arg = *orig_arg;
1362 if (!*args[cur_arg]) {
1363 memprintf(err, "expects exactly 1 arguments");
1364 return ACT_RET_PRS_ERR;
1365 }
1366
Christopher Faulet96bff762019-12-17 13:46:18 +01001367 rule->arg.http.str.ptr = strdup(args[cur_arg]);
1368 rule->arg.http.str.len = strlen(rule->arg.http.str.ptr);
Christopher Faulet81e20172019-12-12 16:40:30 +01001369
1370 px->conf.args.ctx = (rule->from == ACT_F_HTTP_REQ ? ARGC_HRQ : ARGC_HRS);
1371
1372 *orig_arg = cur_arg + 1;
1373 return ACT_RET_PRS_OK;
1374}
1375
Christopher Faulet2eb53962020-01-14 14:47:34 +01001376/* Release memory allocated by an http redirect action. */
1377static void release_http_redir(struct act_rule *rule)
1378{
1379 struct logformat_node *lf, *lfb;
1380 struct redirect_rule *redir;
1381
1382 redir = rule->arg.redir;
1383 LIST_DEL(&redir->list);
1384 if (redir->cond) {
1385 prune_acl_cond(redir->cond);
1386 free(redir->cond);
1387 }
1388 free(redir->rdr_str);
1389 free(redir->cookie_str);
1390 list_for_each_entry_safe(lf, lfb, &redir->rdr_fmt, list) {
1391 LIST_DEL(&lf->list);
1392 free(lf);
1393 }
1394 free(redir);
1395}
1396
Christopher Faulet81e20172019-12-12 16:40:30 +01001397/* Parse a "redirect" action. It returns ACT_RET_PRS_OK on success,
1398 * ACT_RET_PRS_ERR on error.
1399 */
1400static enum act_parse_ret parse_http_redirect(const char **args, int *orig_arg, struct proxy *px,
1401 struct act_rule *rule, char **err)
1402{
1403 struct redirect_rule *redir;
1404 int dir, cur_arg;
1405
1406 rule->action = ACT_HTTP_REDIR;
Christopher Faulet245cf792019-12-18 14:58:12 +01001407 rule->flags |= ACT_FLAG_FINAL;
Christopher Faulet2eb53962020-01-14 14:47:34 +01001408 rule->release_ptr = release_http_redir;
Christopher Faulet81e20172019-12-12 16:40:30 +01001409
1410 cur_arg = *orig_arg;
1411
1412 dir = (rule->from == ACT_F_HTTP_REQ ? 0 : 1);
1413 if ((redir = http_parse_redirect_rule(px->conf.args.file, px->conf.args.line, px, &args[cur_arg], err, 1, dir)) == NULL)
1414 return ACT_RET_PRS_ERR;
1415
1416 rule->arg.redir = redir;
1417 rule->cond = redir->cond;
1418 redir->cond = NULL;
1419
1420 /* skip all arguments */
1421 while (*args[cur_arg])
1422 cur_arg++;
1423
1424 *orig_arg = cur_arg;
1425 return ACT_RET_PRS_OK;
1426}
1427
Christopher Faulet046cf442019-12-17 15:45:23 +01001428/* This function executes a add-acl, del-acl, set-map or del-map actions. On
1429 * success, it returns ACT_RET_CONT. Otherwsize ACT_RET_ERR is returned.
1430 */
1431static enum act_return http_action_set_map(struct act_rule *rule, struct proxy *px,
1432 struct session *sess, struct stream *s, int flags)
1433{
1434 struct pat_ref *ref;
1435 struct buffer *key = NULL, *value = NULL;
1436 enum act_return ret = ACT_RET_CONT;
1437
1438 /* collect reference */
1439 ref = pat_ref_lookup(rule->arg.map.ref);
1440 if (!ref)
1441 goto leave;
1442
1443 /* allocate key */
1444 key = alloc_trash_chunk();
1445 if (!key)
1446 goto fail_alloc;
1447
1448 /* collect key */
1449 key->data = build_logline(s, key->area, key->size, &rule->arg.map.key);
1450 key->area[key->data] = '\0';
1451
1452 switch (rule->action) {
1453 case 0: // add-acl
1454 /* add entry only if it does not already exist */
1455 HA_SPIN_LOCK(PATREF_LOCK, &ref->lock);
1456 if (pat_ref_find_elt(ref, key->area) == NULL)
1457 pat_ref_add(ref, key->area, NULL, NULL);
1458 HA_SPIN_UNLOCK(PATREF_LOCK, &ref->lock);
1459 break;
1460
1461 case 1: // set-map
1462 /* allocate value */
1463 value = alloc_trash_chunk();
1464 if (!value)
1465 goto fail_alloc;
1466
1467 /* collect value */
1468 value->data = build_logline(s, value->area, value->size, &rule->arg.map.value);
1469 value->area[value->data] = '\0';
1470
1471 HA_SPIN_LOCK(PATREF_LOCK, &ref->lock);
1472 if (pat_ref_find_elt(ref, key->area) != NULL) {
1473 /* update entry if it exists */
1474 pat_ref_set(ref, key->area, value->area, NULL);
1475 }
1476 else {
1477 /* insert a new entry */
1478 pat_ref_add(ref, key->area, value->area, NULL);
1479 }
1480 HA_SPIN_UNLOCK(PATREF_LOCK, &ref->lock);
1481 break;
1482
1483 case 2: // del-acl
1484 case 3: // del-map
1485 /* returned code: 1=ok, 0=ko */
1486 HA_SPIN_LOCK(PATREF_LOCK, &ref->lock);
1487 pat_ref_delete(ref, key->area);
1488 HA_SPIN_UNLOCK(PATREF_LOCK, &ref->lock);
1489 break;
1490
1491 default:
1492 ret = ACT_RET_ERR;
1493 }
1494
1495
1496 leave:
1497 free_trash_chunk(key);
1498 free_trash_chunk(value);
1499 return ret;
1500
1501 fail_alloc:
1502 if (!(s->flags & SF_ERR_MASK))
1503 s->flags |= SF_ERR_RESOURCE;
1504 ret = ACT_RET_ERR;
1505 goto leave;
1506}
1507
Christopher Faulet2eb53962020-01-14 14:47:34 +01001508/* Release memory allocated by an http map/acl action. */
1509static void release_http_map(struct act_rule *rule)
1510{
1511 struct logformat_node *lf, *lfb;
1512
1513 free(rule->arg.map.ref);
1514 list_for_each_entry_safe(lf, lfb, &rule->arg.map.key, list) {
1515 LIST_DEL(&lf->list);
1516 release_sample_expr(lf->expr);
1517 free(lf->arg);
1518 free(lf);
1519 }
1520 if (rule->action == 1) {
1521 list_for_each_entry_safe(lf, lfb, &rule->arg.map.value, list) {
1522 LIST_DEL(&lf->list);
1523 release_sample_expr(lf->expr);
1524 free(lf->arg);
1525 free(lf);
1526 }
1527 }
1528}
1529
Christopher Faulet81e20172019-12-12 16:40:30 +01001530/* Parse a "add-acl", "del-acl", "set-map" or "del-map" actions. It takes one or
Christopher Faulet046cf442019-12-17 15:45:23 +01001531 * two log-format string as argument depending on the action. The action is
1532 * stored in <.action> as an int (0=add-acl, 1=set-map, 2=del-acl,
1533 * 3=del-map). It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
Christopher Faulet81e20172019-12-12 16:40:30 +01001534 */
1535static enum act_parse_ret parse_http_set_map(const char **args, int *orig_arg, struct proxy *px,
1536 struct act_rule *rule, char **err)
1537{
1538 int cap, cur_arg;
1539
Christopher Faulet046cf442019-12-17 15:45:23 +01001540 if (args[*orig_arg-1][0] == 'a') // add-acl
1541 rule->action = 0;
1542 else if (args[*orig_arg-1][0] == 's') // set-map
1543 rule->action = 1;
1544 else if (args[*orig_arg-1][4] == 'a') // del-acl
1545 rule->action = 2;
1546 else if (args[*orig_arg-1][4] == 'm') // del-map
1547 rule->action = 3;
1548 else {
1549 memprintf(err, "internal error: unhandled action '%s'", args[0]);
1550 return ACT_RET_PRS_ERR;
1551 }
1552 rule->action_ptr = http_action_set_map;
Christopher Faulet2eb53962020-01-14 14:47:34 +01001553 rule->release_ptr = release_http_map;
Christopher Faulet81e20172019-12-12 16:40:30 +01001554
1555 cur_arg = *orig_arg;
Christopher Faulet046cf442019-12-17 15:45:23 +01001556 if (rule->action == 1 && (!*args[cur_arg] || !*args[cur_arg+1])) {
1557 /* 2 args for set-map */
Christopher Faulet81e20172019-12-12 16:40:30 +01001558 memprintf(err, "expects exactly 2 arguments");
1559 return ACT_RET_PRS_ERR;
1560 }
1561 else if (!*args[cur_arg]) {
Christopher Faulet046cf442019-12-17 15:45:23 +01001562 /* only one arg for other actions */
Christopher Faulet81e20172019-12-12 16:40:30 +01001563 memprintf(err, "expects exactly 1 arguments");
1564 return ACT_RET_PRS_ERR;
1565 }
1566
1567 /*
1568 * '+ 8' for 'set-map(' (same for del-map)
1569 * '- 9' for 'set-map(' + trailing ')' (same for del-map)
1570 */
1571 rule->arg.map.ref = my_strndup(args[cur_arg-1] + 8, strlen(args[cur_arg-1]) - 9);
1572
1573 if (rule->from == ACT_F_HTTP_REQ) {
1574 px->conf.args.ctx = ARGC_HRQ;
1575 cap = (px->cap & PR_CAP_FE) ? SMP_VAL_FE_HRQ_HDR : SMP_VAL_BE_HRQ_HDR;
1576 }
1577 else{
1578 px->conf.args.ctx = ARGC_HRS;
1579 cap = (px->cap & PR_CAP_BE) ? SMP_VAL_BE_HRS_HDR : SMP_VAL_FE_HRS_HDR;
1580 }
1581
1582 /* key pattern */
1583 LIST_INIT(&rule->arg.map.key);
Christopher Faulet1337b322020-01-14 14:50:55 +01001584 if (!parse_logformat_string(args[cur_arg], px, &rule->arg.map.key, LOG_OPT_HTTP, cap, err)) {
1585 free(rule->arg.map.ref);
Christopher Faulet81e20172019-12-12 16:40:30 +01001586 return ACT_RET_PRS_ERR;
Christopher Faulet1337b322020-01-14 14:50:55 +01001587 }
Christopher Faulet81e20172019-12-12 16:40:30 +01001588
Christopher Faulet046cf442019-12-17 15:45:23 +01001589 if (rule->action == 1) {
Christopher Faulet81e20172019-12-12 16:40:30 +01001590 /* value pattern for set-map only */
1591 cur_arg++;
1592 LIST_INIT(&rule->arg.map.value);
Christopher Faulet1337b322020-01-14 14:50:55 +01001593 if (!parse_logformat_string(args[cur_arg], px, &rule->arg.map.value, LOG_OPT_HTTP, cap, err)) {
1594 free(rule->arg.map.ref);
Christopher Faulet81e20172019-12-12 16:40:30 +01001595 return ACT_RET_PRS_ERR;
Christopher Faulet1337b322020-01-14 14:50:55 +01001596 }
Christopher Faulet81e20172019-12-12 16:40:30 +01001597 }
1598
1599 free(px->conf.lfs_file);
1600 px->conf.lfs_file = strdup(px->conf.args.file);
1601 px->conf.lfs_line = px->conf.args.line;
1602
1603 *orig_arg = cur_arg + 1;
1604 return ACT_RET_PRS_OK;
1605}
1606
Christopher Fauletac98d812019-12-18 09:20:16 +01001607/* This function executes a track-sc* actions. On success, it returns
1608 * ACT_RET_CONT. Otherwsize ACT_RET_ERR is returned.
1609 */
1610static enum act_return http_action_track_sc(struct act_rule *rule, struct proxy *px,
1611 struct session *sess, struct stream *s, int flags)
1612{
1613 struct stktable *t;
1614 struct stksess *ts;
1615 struct stktable_key *key;
1616 void *ptr1, *ptr2, *ptr3, *ptr4;
1617 int opt;
1618
1619 ptr1 = ptr2 = ptr3 = ptr4 = NULL;
1620 opt = ((rule->from == ACT_F_HTTP_REQ) ? SMP_OPT_DIR_REQ : SMP_OPT_DIR_RES) | SMP_OPT_FINAL;
1621
1622 t = rule->arg.trk_ctr.table.t;
1623 key = stktable_fetch_key(t, s->be, sess, s, opt, rule->arg.trk_ctr.expr, NULL);
1624
1625 if (!key)
1626 goto end;
1627 ts = stktable_get_entry(t, key);
1628 if (!ts)
1629 goto end;
1630
1631 stream_track_stkctr(&s->stkctr[rule->action], t, ts);
1632
1633 /* let's count a new HTTP request as it's the first time we do it */
1634 ptr1 = stktable_data_ptr(t, ts, STKTABLE_DT_HTTP_REQ_CNT);
1635 ptr2 = stktable_data_ptr(t, ts, STKTABLE_DT_HTTP_REQ_RATE);
1636
1637 /* When the client triggers a 4xx from the server, it's most often due
1638 * to a missing object or permission. These events should be tracked
1639 * because if they happen often, it may indicate a brute force or a
1640 * vulnerability scan. Normally this is done when receiving the response
1641 * but here we're tracking after this ought to have been done so we have
1642 * to do it on purpose.
1643 */
1644 if (rule->from == ACT_F_HTTP_RES && (unsigned)(s->txn->status - 400) < 100) {
1645 ptr3 = stktable_data_ptr(t, ts, STKTABLE_DT_HTTP_ERR_CNT);
1646 ptr4 = stktable_data_ptr(t, ts, STKTABLE_DT_HTTP_ERR_RATE);
1647 }
1648
1649 if (ptr1 || ptr2 || ptr3 || ptr4) {
1650 HA_RWLOCK_WRLOCK(STK_SESS_LOCK, &ts->lock);
1651
1652 if (ptr1)
1653 stktable_data_cast(ptr1, http_req_cnt)++;
1654 if (ptr2)
1655 update_freq_ctr_period(&stktable_data_cast(ptr2, http_req_rate),
1656 t->data_arg[STKTABLE_DT_HTTP_REQ_RATE].u, 1);
1657 if (ptr3)
1658 stktable_data_cast(ptr3, http_err_cnt)++;
1659 if (ptr4)
1660 update_freq_ctr_period(&stktable_data_cast(ptr4, http_err_rate),
1661 t->data_arg[STKTABLE_DT_HTTP_ERR_RATE].u, 1);
1662
1663 HA_RWLOCK_WRUNLOCK(STK_SESS_LOCK, &ts->lock);
1664
1665 /* If data was modified, we need to touch to re-schedule sync */
1666 stktable_touch_local(t, ts, 0);
1667 }
1668
1669 stkctr_set_flags(&s->stkctr[rule->action], STKCTR_TRACK_CONTENT);
1670 if (sess->fe != s->be)
1671 stkctr_set_flags(&s->stkctr[rule->action], STKCTR_TRACK_BACKEND);
1672
1673 end:
1674 return ACT_RET_CONT;
1675}
Christopher Faulet81e20172019-12-12 16:40:30 +01001676
Christopher Faulet2eb53962020-01-14 14:47:34 +01001677static void release_http_track_sc(struct act_rule *rule)
1678{
1679 release_sample_expr(rule->arg.trk_ctr.expr);
1680}
1681
Christopher Faulet81e20172019-12-12 16:40:30 +01001682/* Parse a "track-sc*" actions. It returns ACT_RET_PRS_OK on success,
1683 * ACT_RET_PRS_ERR on error.
1684 */
1685static enum act_parse_ret parse_http_track_sc(const char **args, int *orig_arg, struct proxy *px,
1686 struct act_rule *rule, char **err)
1687{
1688 struct sample_expr *expr;
1689 unsigned int where;
1690 unsigned int tsc_num;
1691 const char *tsc_num_str;
1692 int cur_arg;
1693
1694 tsc_num_str = &args[*orig_arg-1][8];
1695 if (cfg_parse_track_sc_num(&tsc_num, tsc_num_str, tsc_num_str + strlen(tsc_num_str), err) == -1)
1696 return ACT_RET_PRS_ERR;
1697
1698 cur_arg = *orig_arg;
1699 expr = sample_parse_expr((char **)args, &cur_arg, px->conf.args.file, px->conf.args.line,
1700 err, &px->conf.args);
1701 if (!expr)
1702 return ACT_RET_PRS_ERR;
1703
1704 where = 0;
1705 if (px->cap & PR_CAP_FE)
1706 where |= (rule->from == ACT_F_HTTP_REQ ? SMP_VAL_FE_HRQ_HDR : SMP_VAL_FE_HRS_HDR);
1707 if (px->cap & PR_CAP_BE)
1708 where |= (rule->from == ACT_F_HTTP_REQ ? SMP_VAL_BE_HRQ_HDR : SMP_VAL_BE_HRS_HDR);
1709
1710 if (!(expr->fetch->val & where)) {
1711 memprintf(err, "fetch method '%s' extracts information from '%s', none of which is available here",
1712 args[cur_arg-1], sample_src_names(expr->fetch->use));
Christopher Faulet1337b322020-01-14 14:50:55 +01001713 release_sample_expr(expr);
Christopher Faulet81e20172019-12-12 16:40:30 +01001714 return ACT_RET_PRS_ERR;
1715 }
1716
1717 if (strcmp(args[cur_arg], "table") == 0) {
1718 cur_arg++;
1719 if (!*args[cur_arg]) {
1720 memprintf(err, "missing table name");
Christopher Faulet1337b322020-01-14 14:50:55 +01001721 release_sample_expr(expr);
Christopher Faulet81e20172019-12-12 16:40:30 +01001722 return ACT_RET_PRS_ERR;
1723 }
1724
1725 /* we copy the table name for now, it will be resolved later */
1726 rule->arg.trk_ctr.table.n = strdup(args[cur_arg]);
1727 cur_arg++;
1728 }
1729
Christopher Fauletac98d812019-12-18 09:20:16 +01001730 rule->action = tsc_num;
Christopher Faulet81e20172019-12-12 16:40:30 +01001731 rule->arg.trk_ctr.expr = expr;
Christopher Fauletac98d812019-12-18 09:20:16 +01001732 rule->action_ptr = http_action_track_sc;
Christopher Faulet2eb53962020-01-14 14:47:34 +01001733 rule->release_ptr = release_http_track_sc;
Christopher Faulet81e20172019-12-12 16:40:30 +01001734 rule->check_ptr = check_trk_action;
1735
1736 *orig_arg = cur_arg;
1737 return ACT_RET_PRS_OK;
1738}
1739
Christopher Faulet46f95542019-12-20 10:07:22 +01001740/* This function executes a strict-mode actions. On success, it always returns
1741 * ACT_RET_CONT
1742 */
1743static enum act_return http_action_strict_mode(struct act_rule *rule, struct proxy *px,
1744 struct session *sess, struct stream *s, int flags)
1745{
1746 struct http_msg *msg = ((rule->from == ACT_F_HTTP_REQ) ? &s->txn->req : &s->txn->rsp);
1747
1748 if (rule->action == 0) // strict-mode on
1749 msg->flags &= ~HTTP_MSGF_SOFT_RW;
1750 else // strict-mode off
1751 msg->flags |= HTTP_MSGF_SOFT_RW;
1752 return ACT_RET_CONT;
1753}
1754
1755/* Parse a "strict-mode" action. It returns ACT_RET_PRS_OK on success,
1756 * ACT_RET_PRS_ERR on error.
1757 */
1758static enum act_parse_ret parse_http_strict_mode(const char **args, int *orig_arg, struct proxy *px,
1759 struct act_rule *rule, char **err)
1760{
1761 int cur_arg;
1762
1763
1764 cur_arg = *orig_arg;
1765 if (!*args[cur_arg]) {
1766 memprintf(err, "expects exactly 1 arguments");
1767 return ACT_RET_PRS_ERR;
1768 }
1769
1770 if (strcasecmp(args[cur_arg], "on") == 0)
1771 rule->action = 0; // strict-mode on
1772 else if (strcasecmp(args[cur_arg], "off") == 0)
1773 rule->action = 1; // strict-mode off
1774 else {
1775 memprintf(err, "Unexpected value '%s'. Only 'on' and 'off' are supported", args[cur_arg]);
1776 return ACT_RET_PRS_ERR;
1777 }
1778 rule->action_ptr = http_action_strict_mode;
1779
1780 *orig_arg = cur_arg + 1;
1781 return ACT_RET_PRS_OK;
1782}
1783
Willy Tarreau79e57332018-10-02 16:01:16 +02001784/************************************************************************/
1785/* All supported http-request action keywords must be declared here. */
1786/************************************************************************/
1787
1788static struct action_kw_list http_req_actions = {
1789 .kw = {
Christopher Faulet81e20172019-12-12 16:40:30 +01001790 { "add-acl", parse_http_set_map, 1 },
1791 { "add-header", parse_http_set_header, 0 },
1792 { "allow", parse_http_allow, 0 },
1793 { "auth", parse_http_auth, 0 },
1794 { "capture", parse_http_req_capture, 0 },
1795 { "del-acl", parse_http_set_map, 1 },
1796 { "del-header", parse_http_del_header, 0 },
1797 { "del-map", parse_http_set_map, 1 },
Christopher Faulete0fca292020-01-13 21:49:03 +01001798 { "deny", parse_http_deny, 0 },
Christopher Faulet81e20172019-12-12 16:40:30 +01001799 { "disable-l7-retry", parse_http_req_disable_l7_retry, 0 },
1800 { "early-hint", parse_http_set_header, 0 },
1801 { "redirect", parse_http_redirect, 0 },
1802 { "reject", parse_http_action_reject, 0 },
1803 { "replace-header", parse_http_replace_header, 0 },
1804 { "replace-path", parse_replace_uri, 0 },
1805 { "replace-uri", parse_replace_uri, 0 },
1806 { "replace-value", parse_http_replace_header, 0 },
1807 { "set-header", parse_http_set_header, 0 },
1808 { "set-log-level", parse_http_set_log_level, 0 },
1809 { "set-map", parse_http_set_map, 1 },
1810 { "set-method", parse_set_req_line, 0 },
1811 { "set-mark", parse_http_set_mark, 0 },
1812 { "set-nice", parse_http_set_nice, 0 },
1813 { "set-path", parse_set_req_line, 0 },
1814 { "set-query", parse_set_req_line, 0 },
1815 { "set-tos", parse_http_set_tos, 0 },
1816 { "set-uri", parse_set_req_line, 0 },
Christopher Faulet46f95542019-12-20 10:07:22 +01001817 { "strict-mode", parse_http_strict_mode, 0 },
Christopher Faulete0fca292020-01-13 21:49:03 +01001818 { "tarpit", parse_http_deny, 0 },
Christopher Faulet81e20172019-12-12 16:40:30 +01001819 { "track-sc", parse_http_track_sc, 1 },
Willy Tarreau79e57332018-10-02 16:01:16 +02001820 { NULL, NULL }
1821 }
1822};
1823
Willy Tarreau0108d902018-11-25 19:14:37 +01001824INITCALL1(STG_REGISTER, http_req_keywords_register, &http_req_actions);
1825
Willy Tarreau79e57332018-10-02 16:01:16 +02001826static struct action_kw_list http_res_actions = {
1827 .kw = {
Christopher Faulet81e20172019-12-12 16:40:30 +01001828 { "add-acl", parse_http_set_map, 1 },
1829 { "add-header", parse_http_set_header, 0 },
1830 { "allow", parse_http_allow, 0 },
1831 { "capture", parse_http_res_capture, 0 },
1832 { "del-acl", parse_http_set_map, 1 },
1833 { "del-header", parse_http_del_header, 0 },
1834 { "del-map", parse_http_set_map, 1 },
Christopher Faulete0fca292020-01-13 21:49:03 +01001835 { "deny", parse_http_deny, 0 },
Christopher Faulet81e20172019-12-12 16:40:30 +01001836 { "redirect", parse_http_redirect, 0 },
1837 { "replace-header", parse_http_replace_header, 0 },
1838 { "replace-value", parse_http_replace_header, 0 },
1839 { "set-header", parse_http_set_header, 0 },
1840 { "set-log-level", parse_http_set_log_level, 0 },
1841 { "set-map", parse_http_set_map, 1 },
1842 { "set-mark", parse_http_set_mark, 0 },
1843 { "set-nice", parse_http_set_nice, 0 },
1844 { "set-status", parse_http_set_status, 0 },
1845 { "set-tos", parse_http_set_tos, 0 },
Christopher Faulet46f95542019-12-20 10:07:22 +01001846 { "strict-mode", parse_http_strict_mode, 0 },
Christopher Faulet81e20172019-12-12 16:40:30 +01001847 { "track-sc", parse_http_track_sc, 1 },
Willy Tarreau79e57332018-10-02 16:01:16 +02001848 { NULL, NULL }
1849 }
1850};
1851
Willy Tarreau0108d902018-11-25 19:14:37 +01001852INITCALL1(STG_REGISTER, http_res_keywords_register, &http_res_actions);
Willy Tarreau79e57332018-10-02 16:01:16 +02001853
1854/*
1855 * Local variables:
1856 * c-indent-level: 8
1857 * c-basic-offset: 8
1858 * End:
1859 */