blob: a7f9e119dc16f8d1c91eac463cddf9982abb7db5 [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
Baptiste Assmann19a69b32020-01-16 14:34:22 +0100497 /* capture slots can only be declared in frontends, so we can't check their
498 * existence in backends at configuration parsing step
499 */
500 if (px->cap & PR_CAP_FE && rule->arg.capid.idx >= px->nb_req_cap) {
Willy Tarreau79e57332018-10-02 16:01:16 +0200501 memprintf(err, "unable to find capture id '%d' referenced by http-request capture rule",
502 rule->arg.capid.idx);
503 return 0;
504 }
505
506 return 1;
507}
508
Christopher Faulet2eb53962020-01-14 14:47:34 +0100509/* Release memory allocate by an http capture action */
510static void release_http_capture(struct act_rule *rule)
511{
512 if (rule->action_ptr == http_action_req_capture)
513 release_sample_expr(rule->arg.cap.expr);
514 else
515 release_sample_expr(rule->arg.capid.expr);
516}
517
Willy Tarreau79e57332018-10-02 16:01:16 +0200518/* parse an "http-request capture" action. It takes a single argument which is
519 * a sample fetch expression. It stores the expression into arg->act.p[0] and
520 * the allocated hdr_cap struct or the preallocated "id" into arg->act.p[1].
521 * It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
522 */
523static enum act_parse_ret parse_http_req_capture(const char **args, int *orig_arg, struct proxy *px,
524 struct act_rule *rule, char **err)
525{
526 struct sample_expr *expr;
527 struct cap_hdr *hdr;
528 int cur_arg;
529 int len = 0;
530
531 for (cur_arg = *orig_arg; cur_arg < *orig_arg + 3 && *args[cur_arg]; cur_arg++)
532 if (strcmp(args[cur_arg], "if") == 0 ||
533 strcmp(args[cur_arg], "unless") == 0)
534 break;
535
536 if (cur_arg < *orig_arg + 3) {
537 memprintf(err, "expects <expression> [ 'len' <length> | id <idx> ]");
538 return ACT_RET_PRS_ERR;
539 }
540
541 cur_arg = *orig_arg;
542 expr = sample_parse_expr((char **)args, &cur_arg, px->conf.args.file, px->conf.args.line, err, &px->conf.args);
543 if (!expr)
544 return ACT_RET_PRS_ERR;
545
546 if (!(expr->fetch->val & SMP_VAL_FE_HRQ_HDR)) {
547 memprintf(err,
548 "fetch method '%s' extracts information from '%s', none of which is available here",
549 args[cur_arg-1], sample_src_names(expr->fetch->use));
Christopher Faulet1337b322020-01-14 14:50:55 +0100550 release_sample_expr(expr);
Willy Tarreau79e57332018-10-02 16:01:16 +0200551 return ACT_RET_PRS_ERR;
552 }
553
554 if (!args[cur_arg] || !*args[cur_arg]) {
555 memprintf(err, "expects 'len or 'id'");
Christopher Faulet1337b322020-01-14 14:50:55 +0100556 release_sample_expr(expr);
Willy Tarreau79e57332018-10-02 16:01:16 +0200557 return ACT_RET_PRS_ERR;
558 }
559
560 if (strcmp(args[cur_arg], "len") == 0) {
561 cur_arg++;
562
563 if (!(px->cap & PR_CAP_FE)) {
564 memprintf(err, "proxy '%s' has no frontend capability", px->id);
Christopher Faulet1337b322020-01-14 14:50:55 +0100565 release_sample_expr(expr);
Willy Tarreau79e57332018-10-02 16:01:16 +0200566 return ACT_RET_PRS_ERR;
567 }
568
569 px->conf.args.ctx = ARGC_CAP;
570
571 if (!args[cur_arg]) {
572 memprintf(err, "missing length value");
Christopher Faulet1337b322020-01-14 14:50:55 +0100573 release_sample_expr(expr);
Willy Tarreau79e57332018-10-02 16:01:16 +0200574 return ACT_RET_PRS_ERR;
575 }
576 /* we copy the table name for now, it will be resolved later */
577 len = atoi(args[cur_arg]);
578 if (len <= 0) {
579 memprintf(err, "length must be > 0");
Christopher Faulet1337b322020-01-14 14:50:55 +0100580 release_sample_expr(expr);
Willy Tarreau79e57332018-10-02 16:01:16 +0200581 return ACT_RET_PRS_ERR;
582 }
583 cur_arg++;
584
Willy Tarreau79e57332018-10-02 16:01:16 +0200585 hdr = calloc(1, sizeof(*hdr));
586 hdr->next = px->req_cap;
587 hdr->name = NULL; /* not a header capture */
588 hdr->namelen = 0;
589 hdr->len = len;
590 hdr->pool = create_pool("caphdr", hdr->len + 1, MEM_F_SHARED);
591 hdr->index = px->nb_req_cap++;
592
593 px->req_cap = hdr;
594 px->to_log |= LW_REQHDR;
595
596 rule->action = ACT_CUSTOM;
597 rule->action_ptr = http_action_req_capture;
Christopher Faulet2eb53962020-01-14 14:47:34 +0100598 rule->release_ptr = release_http_capture;
Willy Tarreau79e57332018-10-02 16:01:16 +0200599 rule->arg.cap.expr = expr;
600 rule->arg.cap.hdr = hdr;
601 }
602
603 else if (strcmp(args[cur_arg], "id") == 0) {
604 int id;
605 char *error;
606
607 cur_arg++;
608
609 if (!args[cur_arg]) {
610 memprintf(err, "missing id value");
Christopher Faulet1337b322020-01-14 14:50:55 +0100611 release_sample_expr(expr);
Willy Tarreau79e57332018-10-02 16:01:16 +0200612 return ACT_RET_PRS_ERR;
613 }
614
615 id = strtol(args[cur_arg], &error, 10);
616 if (*error != '\0') {
617 memprintf(err, "cannot parse id '%s'", args[cur_arg]);
Christopher Faulet1337b322020-01-14 14:50:55 +0100618 release_sample_expr(expr);
Willy Tarreau79e57332018-10-02 16:01:16 +0200619 return ACT_RET_PRS_ERR;
620 }
621 cur_arg++;
622
623 px->conf.args.ctx = ARGC_CAP;
624
625 rule->action = ACT_CUSTOM;
626 rule->action_ptr = http_action_req_capture_by_id;
627 rule->check_ptr = check_http_req_capture;
Christopher Faulet2eb53962020-01-14 14:47:34 +0100628 rule->release_ptr = release_http_capture;
Willy Tarreau79e57332018-10-02 16:01:16 +0200629 rule->arg.capid.expr = expr;
630 rule->arg.capid.idx = id;
631 }
632
633 else {
634 memprintf(err, "expects 'len' or 'id', found '%s'", args[cur_arg]);
Christopher Faulet1337b322020-01-14 14:50:55 +0100635 release_sample_expr(expr);
Willy Tarreau79e57332018-10-02 16:01:16 +0200636 return ACT_RET_PRS_ERR;
637 }
638
639 *orig_arg = cur_arg;
640 return ACT_RET_PRS_OK;
641}
642
643/* This function executes the "capture" action and store the result in a
644 * capture slot if exists. It executes a fetch expression, turns the result
645 * into a string and puts it in a capture slot. It always returns 1. If an
646 * error occurs the action is cancelled, but the rule processing continues.
647 */
648static enum act_return http_action_res_capture_by_id(struct act_rule *rule, struct proxy *px,
649 struct session *sess, struct stream *s, int flags)
650{
651 struct sample *key;
652 struct cap_hdr *h;
653 char **cap = s->res_cap;
654 struct proxy *fe = strm_fe(s);
655 int len;
656 int i;
657
658 /* Look for the original configuration. */
659 for (h = fe->rsp_cap, i = fe->nb_rsp_cap - 1;
660 h != NULL && i != rule->arg.capid.idx ;
661 i--, h = h->next);
662 if (!h)
663 return ACT_RET_CONT;
664
665 key = sample_fetch_as_type(s->be, sess, s, SMP_OPT_DIR_RES|SMP_OPT_FINAL, rule->arg.capid.expr, SMP_T_STR);
666 if (!key)
667 return ACT_RET_CONT;
668
669 if (cap[h->index] == NULL)
670 cap[h->index] = pool_alloc(h->pool);
671
672 if (cap[h->index] == NULL) /* no more capture memory */
673 return ACT_RET_CONT;
674
675 len = key->data.u.str.data;
676 if (len > h->len)
677 len = h->len;
678
679 memcpy(cap[h->index], key->data.u.str.area, len);
680 cap[h->index][len] = 0;
681 return ACT_RET_CONT;
682}
683
684/* Check an "http-response capture" action.
685 *
686 * The function returns 1 in success case, otherwise, it returns 0 and err is
687 * filled.
688 */
689static int check_http_res_capture(struct act_rule *rule, struct proxy *px, char **err)
690{
691 if (rule->action_ptr != http_action_res_capture_by_id)
692 return 1;
693
694 if (rule->arg.capid.idx >= px->nb_rsp_cap) {
695 memprintf(err, "unable to find capture id '%d' referenced by http-response capture rule",
696 rule->arg.capid.idx);
697 return 0;
698 }
699
700 return 1;
701}
702
703/* parse an "http-response capture" action. It takes a single argument which is
704 * a sample fetch expression. It stores the expression into arg->act.p[0] and
705 * the allocated hdr_cap struct od the preallocated id into arg->act.p[1].
706 * It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
707 */
708static enum act_parse_ret parse_http_res_capture(const char **args, int *orig_arg, struct proxy *px,
709 struct act_rule *rule, char **err)
710{
711 struct sample_expr *expr;
712 int cur_arg;
713 int id;
714 char *error;
715
716 for (cur_arg = *orig_arg; cur_arg < *orig_arg + 3 && *args[cur_arg]; cur_arg++)
717 if (strcmp(args[cur_arg], "if") == 0 ||
718 strcmp(args[cur_arg], "unless") == 0)
719 break;
720
721 if (cur_arg < *orig_arg + 3) {
722 memprintf(err, "expects <expression> id <idx>");
723 return ACT_RET_PRS_ERR;
724 }
725
726 cur_arg = *orig_arg;
727 expr = sample_parse_expr((char **)args, &cur_arg, px->conf.args.file, px->conf.args.line, err, &px->conf.args);
728 if (!expr)
729 return ACT_RET_PRS_ERR;
730
731 if (!(expr->fetch->val & SMP_VAL_FE_HRS_HDR)) {
732 memprintf(err,
733 "fetch method '%s' extracts information from '%s', none of which is available here",
734 args[cur_arg-1], sample_src_names(expr->fetch->use));
Christopher Faulet1337b322020-01-14 14:50:55 +0100735 release_sample_expr(expr);
Willy Tarreau79e57332018-10-02 16:01:16 +0200736 return ACT_RET_PRS_ERR;
737 }
738
739 if (!args[cur_arg] || !*args[cur_arg]) {
740 memprintf(err, "expects 'id'");
Christopher Faulet1337b322020-01-14 14:50:55 +0100741 release_sample_expr(expr);
Willy Tarreau79e57332018-10-02 16:01:16 +0200742 return ACT_RET_PRS_ERR;
743 }
744
745 if (strcmp(args[cur_arg], "id") != 0) {
746 memprintf(err, "expects 'id', found '%s'", args[cur_arg]);
Christopher Faulet1337b322020-01-14 14:50:55 +0100747 release_sample_expr(expr);
Willy Tarreau79e57332018-10-02 16:01:16 +0200748 return ACT_RET_PRS_ERR;
749 }
750
751 cur_arg++;
752
753 if (!args[cur_arg]) {
754 memprintf(err, "missing id value");
Christopher Faulet1337b322020-01-14 14:50:55 +0100755 release_sample_expr(expr);
Willy Tarreau79e57332018-10-02 16:01:16 +0200756 return ACT_RET_PRS_ERR;
757 }
758
759 id = strtol(args[cur_arg], &error, 10);
760 if (*error != '\0') {
761 memprintf(err, "cannot parse id '%s'", args[cur_arg]);
Christopher Faulet1337b322020-01-14 14:50:55 +0100762 release_sample_expr(expr);
Willy Tarreau79e57332018-10-02 16:01:16 +0200763 return ACT_RET_PRS_ERR;
764 }
765 cur_arg++;
766
767 px->conf.args.ctx = ARGC_CAP;
768
769 rule->action = ACT_CUSTOM;
770 rule->action_ptr = http_action_res_capture_by_id;
771 rule->check_ptr = check_http_res_capture;
Christopher Faulet2eb53962020-01-14 14:47:34 +0100772 rule->release_ptr = release_http_capture;
Willy Tarreau79e57332018-10-02 16:01:16 +0200773 rule->arg.capid.expr = expr;
774 rule->arg.capid.idx = id;
775
776 *orig_arg = cur_arg;
777 return ACT_RET_PRS_OK;
778}
779
Christopher Faulet81e20172019-12-12 16:40:30 +0100780/* Parse a "allow" action for a request or a response rule. It takes no argument. It
781 * returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
782 */
783static enum act_parse_ret parse_http_allow(const char **args, int *orig_arg, struct proxy *px,
784 struct act_rule *rule, char **err)
785{
786 rule->action = ACT_ACTION_ALLOW;
Christopher Faulet245cf792019-12-18 14:58:12 +0100787 rule->flags |= ACT_FLAG_FINAL;
Christopher Faulet81e20172019-12-12 16:40:30 +0100788 return ACT_RET_PRS_OK;
789}
790
Christopher Faulet554c0eb2020-01-14 12:00:28 +0100791/* Check an "http-request deny" action when an http-errors section is referenced.
792 *
793 * The function returns 1 in success case, otherwise, it returns 0 and err is
794 * filled.
795 */
796static int check_http_deny_action(struct act_rule *rule, struct proxy *px, char **err)
797{
798 struct http_errors *http_errs;
799 int status = (intptr_t)(rule->arg.act.p[0]);
800 int ret = 1;
801
802 list_for_each_entry(http_errs, &http_errors_list, list) {
803 if (strcmp(http_errs->id, (char *)rule->arg.act.p[1]) == 0) {
804 free(rule->arg.act.p[1]);
805 rule->arg.http_deny.status = status;
806 rule->arg.http_deny.errmsg = http_errs->errmsg[http_get_status_idx(status)];
807 if (!rule->arg.http_deny.errmsg)
808 ha_warning("Proxy '%s': status '%d' referenced by http deny rule "
809 "not declared in http-errors section '%s'.\n",
810 px->id, status, http_errs->id);
811 break;
812 }
813 }
814
815 if (&http_errs->list == &http_errors_list) {
816 memprintf(err, "unknown http-errors section '%s' referenced by http deny rule",
817 (char *)rule->arg.act.p[1]);
818 free(rule->arg.act.p[1]);
819 ret = 0;
820 }
821
822 return ret;
823}
824
Christopher Faulete0fca292020-01-13 21:49:03 +0100825/* Parse "deny" or "tarpit" actions for a request rule or "deny" action for a
Christopher Faulet554c0eb2020-01-14 12:00:28 +0100826 * response rule. It may take optional arguments to define the status code, the
827 * error file or the http-errors section to use. It returns ACT_RET_PRS_OK on
828 * success, ACT_RET_PRS_ERR on error.
Christopher Faulet81e20172019-12-12 16:40:30 +0100829 */
Christopher Faulete0fca292020-01-13 21:49:03 +0100830static enum act_parse_ret parse_http_deny(const char **args, int *orig_arg, struct proxy *px,
831 struct act_rule *rule, char **err)
Christopher Faulet81e20172019-12-12 16:40:30 +0100832{
Christopher Faulet554c0eb2020-01-14 12:00:28 +0100833 int default_status, status, hc, cur_arg;
834
Christopher Faulet81e20172019-12-12 16:40:30 +0100835
836 cur_arg = *orig_arg;
Christopher Faulete0fca292020-01-13 21:49:03 +0100837 if (rule->from == ACT_F_HTTP_REQ) {
838 if (!strcmp(args[cur_arg-1], "tarpit")) {
839 rule->action = ACT_HTTP_REQ_TARPIT;
Christopher Faulet554c0eb2020-01-14 12:00:28 +0100840 default_status = status = 500;
Christopher Faulet81e20172019-12-12 16:40:30 +0100841 }
Christopher Faulete0fca292020-01-13 21:49:03 +0100842 else {
843 rule->action = ACT_ACTION_DENY;
Christopher Faulet554c0eb2020-01-14 12:00:28 +0100844 default_status = status = 403;
Christopher Faulet81e20172019-12-12 16:40:30 +0100845 }
Christopher Faulet81e20172019-12-12 16:40:30 +0100846 }
Christopher Faulete0fca292020-01-13 21:49:03 +0100847 else {
Christopher Faulet554c0eb2020-01-14 12:00:28 +0100848 rule->action = ACT_ACTION_DENY;
849 default_status = status = 502;
Christopher Faulete0fca292020-01-13 21:49:03 +0100850 }
Christopher Faulet245cf792019-12-18 14:58:12 +0100851 rule->flags |= ACT_FLAG_FINAL;
Christopher Faulet040c8cd2020-01-13 16:43:45 +0100852
853 if (strcmp(args[cur_arg], "deny_status") == 0) {
854 cur_arg++;
855 if (!*args[cur_arg]) {
Christopher Faulet554c0eb2020-01-14 12:00:28 +0100856 memprintf(err, "'%s' expects <status_code> as argument", args[cur_arg-1]);
Christopher Faulet040c8cd2020-01-13 16:43:45 +0100857 return ACT_RET_PRS_ERR;
858 }
859
Christopher Faulet554c0eb2020-01-14 12:00:28 +0100860 status = atol(args[cur_arg]);
Christopher Faulet040c8cd2020-01-13 16:43:45 +0100861 cur_arg++;
862 for (hc = 0; hc < HTTP_ERR_SIZE; hc++) {
Christopher Faulet554c0eb2020-01-14 12:00:28 +0100863 if (http_err_codes[hc] == status)
Christopher Faulet040c8cd2020-01-13 16:43:45 +0100864 break;
Christopher Faulet040c8cd2020-01-13 16:43:45 +0100865 }
Christopher Faulet554c0eb2020-01-14 12:00:28 +0100866 if (hc >= HTTP_ERR_SIZE) {
867 memprintf(err, "status code '%d' not handled, using default code '%d'",
868 status, default_status);
869 status = default_status;
870 hc = http_get_status_idx(status);
871 }
Christopher Faulet040c8cd2020-01-13 16:43:45 +0100872 }
873
Christopher Faulet554c0eb2020-01-14 12:00:28 +0100874 if (strcmp(args[cur_arg], "errorfile") == 0) {
875 cur_arg++;
876 if (!*args[cur_arg]) {
877 memprintf(err, "'%s' expects <file> as argument", args[cur_arg-1]);
878 return ACT_RET_PRS_ERR;
879 }
880
881 rule->arg.http_deny.errmsg = http_load_errorfile(args[cur_arg], err);
882 if (!rule->arg.http_deny.errmsg)
883 return ACT_RET_PRS_ERR;
884 cur_arg++;
885 }
886 else if (strcmp(args[cur_arg], "errorfiles") == 0) {
887 cur_arg++;
888 if (!*args[cur_arg]) {
889 memprintf(err, "'%s' expects <http_errors_name> as argument", args[cur_arg-1]);
890 return ACT_RET_PRS_ERR;
891 }
892 /* Must be resolved during the config validity check */
893 rule->arg.act.p[0] = (void *)((intptr_t)status);
894 rule->arg.act.p[1] = strdup(args[cur_arg]);
895 rule->check_ptr = check_http_deny_action;
896 cur_arg++;
897 goto out;
898 }
899
900 rule->arg.http_deny.status = status;
901
902 out:
Christopher Faulet040c8cd2020-01-13 16:43:45 +0100903 *orig_arg = cur_arg;
Christopher Faulet81e20172019-12-12 16:40:30 +0100904 return ACT_RET_PRS_OK;
905}
906
907/* Parse a "auth" action. It may take 2 optional arguments to define a "realm"
908 * parameter. It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
909 */
910static enum act_parse_ret parse_http_auth(const char **args, int *orig_arg, struct proxy *px,
911 struct act_rule *rule, char **err)
912{
913 int cur_arg;
914
915 rule->action = ACT_HTTP_REQ_AUTH;
Christopher Faulet245cf792019-12-18 14:58:12 +0100916 rule->flags |= ACT_FLAG_FINAL;
Christopher Faulet2eb53962020-01-14 14:47:34 +0100917 rule->release_ptr = release_http_action;
Christopher Faulet81e20172019-12-12 16:40:30 +0100918
919 cur_arg = *orig_arg;
920 if (!strcmp(args[cur_arg], "realm")) {
921 cur_arg++;
922 if (!*args[cur_arg]) {
923 memprintf(err, "missing realm value.\n");
924 return ACT_RET_PRS_ERR;
925 }
Christopher Faulet96bff762019-12-17 13:46:18 +0100926 rule->arg.http.str.ptr = strdup(args[cur_arg]);
927 rule->arg.http.str.len = strlen(rule->arg.http.str.ptr);
Christopher Faulet81e20172019-12-12 16:40:30 +0100928 cur_arg++;
929 }
930
931 *orig_arg = cur_arg;
932 return ACT_RET_PRS_OK;
933}
934
935/* Parse a "set-nice" action. It takes the nice value as argument. It returns
936 * ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
937 */
938static enum act_parse_ret parse_http_set_nice(const char **args, int *orig_arg, struct proxy *px,
939 struct act_rule *rule, char **err)
940{
941 int cur_arg;
942
943 rule->action = ACT_HTTP_SET_NICE;
944
945 cur_arg = *orig_arg;
946 if (!*args[cur_arg]) {
947 memprintf(err, "expects exactly 1 argument (integer value)");
948 return ACT_RET_PRS_ERR;
949 }
Christopher Faulet96bff762019-12-17 13:46:18 +0100950 rule->arg.http.i = atoi(args[cur_arg]);
951 if (rule->arg.http.i < -1024)
952 rule->arg.http.i = -1024;
953 else if (rule->arg.http.i > 1024)
954 rule->arg.http.i = 1024;
Christopher Faulet81e20172019-12-12 16:40:30 +0100955
956 *orig_arg = cur_arg + 1;
957 return ACT_RET_PRS_OK;
958}
959
960/* Parse a "set-tos" action. It takes the TOS value as argument. It returns
961 * ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
962 */
963static enum act_parse_ret parse_http_set_tos(const char **args, int *orig_arg, struct proxy *px,
964 struct act_rule *rule, char **err)
965{
966#ifdef IP_TOS
967 char *endp;
968 int cur_arg;
969
970 rule->action = ACT_HTTP_SET_TOS;
971
972 cur_arg = *orig_arg;
973 if (!*args[cur_arg]) {
974 memprintf(err, "expects exactly 1 argument (integer/hex value)");
975 return ACT_RET_PRS_ERR;
976 }
Christopher Faulet96bff762019-12-17 13:46:18 +0100977 rule->arg.http.i = strtol(args[cur_arg], &endp, 0);
Christopher Faulet81e20172019-12-12 16:40:30 +0100978 if (endp && *endp != '\0') {
979 memprintf(err, "invalid character starting at '%s' (integer/hex value expected)", endp);
980 return ACT_RET_PRS_ERR;
981 }
982
983 *orig_arg = cur_arg + 1;
984 return ACT_RET_PRS_OK;
985#else
986 memprintf(err, "not supported on this platform (IP_TOS undefined)");
987 return ACT_RET_PRS_ERR;
988#endif
989}
990
991/* Parse a "set-mark" action. It takes the MARK value as argument. It returns
992 * ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
993 */
994static enum act_parse_ret parse_http_set_mark(const char **args, int *orig_arg, struct proxy *px,
995 struct act_rule *rule, char **err)
996{
997#ifdef SO_MARK
998 char *endp;
999 int cur_arg;
1000
1001 rule->action = ACT_HTTP_SET_MARK;
1002
1003 cur_arg = *orig_arg;
1004 if (!*args[cur_arg]) {
1005 memprintf(err, "expects exactly 1 argument (integer/hex value)");
1006 return ACT_RET_PRS_ERR;
1007 }
Christopher Faulet96bff762019-12-17 13:46:18 +01001008 rule->arg.http.i = strtoul(args[cur_arg], &endp, 0);
Christopher Faulet81e20172019-12-12 16:40:30 +01001009 if (endp && *endp != '\0') {
1010 memprintf(err, "invalid character starting at '%s' (integer/hex value expected)", endp);
1011 return ACT_RET_PRS_ERR;
1012 }
1013
1014 *orig_arg = cur_arg + 1;
1015 global.last_checks |= LSTCHK_NETADM;
1016 return ACT_RET_PRS_OK;
1017#else
1018 memprintf(err, "not supported on this platform (SO_MARK undefined)");
1019 return ACT_RET_PRS_ERR;
1020#endif
1021}
1022
1023/* Parse a "set-log-level" action. It takes the level value as argument. It
1024 * returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
1025 */
1026static enum act_parse_ret parse_http_set_log_level(const char **args, int *orig_arg, struct proxy *px,
1027 struct act_rule *rule, char **err)
1028{
1029 int cur_arg;
1030
1031 rule->action = ACT_HTTP_SET_LOGL;
1032
1033 cur_arg = *orig_arg;
1034 if (!*args[cur_arg]) {
1035 bad_log_level:
1036 memprintf(err, "expects exactly 1 argument (log level name or 'silent')");
1037 return ACT_RET_PRS_ERR;
1038 }
1039 if (strcmp(args[cur_arg], "silent") == 0)
Christopher Faulet96bff762019-12-17 13:46:18 +01001040 rule->arg.http.i = -1;
1041 else if ((rule->arg.http.i = get_log_level(args[cur_arg]) + 1) == 0)
Christopher Faulet81e20172019-12-12 16:40:30 +01001042 goto bad_log_level;
1043
1044 *orig_arg = cur_arg + 1;
1045 return ACT_RET_PRS_OK;
1046}
1047
Christopher Faulet91b3ec12020-01-17 22:30:06 +01001048/* This function executes a early-hint action. It adds an HTTP Early Hint HTTP
1049 * 103 response header with <.arg.http.str> name and with a value built
1050 * according to <.arg.http.fmt> log line format. If it is the first early-hint
1051 * rule of a serie, the 103 response start-line is added first. At the end, if
1052 * the next rule is not an early-hint rule or if it is the last rule, the EOH
1053 * block is added to terminate the response. On success, it returns
1054 * ACT_RET_CONT. If an error occurs while soft rewrites are enabled, the action
1055 * is canceled, but the rule processing continue. Otherwsize ACT_RET_ERR is
1056 * returned.
1057 */
1058static enum act_return http_action_early_hint(struct act_rule *rule, struct proxy *px,
1059 struct session *sess, struct stream *s, int flags)
1060{
1061 struct act_rule *prev_rule, *next_rule;
1062 struct channel *res = &s->res;
1063 struct htx *htx = htx_from_buf(&res->buf);
1064 struct buffer *value = alloc_trash_chunk();
1065 enum act_return ret = ACT_RET_CONT;
1066
1067 if (!(s->txn->req.flags & HTTP_MSGF_VER_11))
1068 goto leave;
1069
1070 if (!value) {
1071 if (!(s->flags & SF_ERR_MASK))
1072 s->flags |= SF_ERR_RESOURCE;
1073 goto error;
1074 }
1075
1076 /* get previous and next rules */
1077 prev_rule = LIST_PREV(&rule->list, typeof(rule), list);
1078 next_rule = LIST_NEXT(&rule->list, typeof(rule), list);
1079
1080 /* if no previous rule or previous rule is not early-hint, start a new response. Otherwise,
1081 * continue to add link to a previously started response */
1082 if (&prev_rule->list == s->current_rule_list || prev_rule->action_ptr != http_action_early_hint) {
1083 struct htx_sl *sl;
1084 unsigned int flags = (HTX_SL_F_IS_RESP|HTX_SL_F_VER_11|
1085 HTX_SL_F_XFER_LEN|HTX_SL_F_BODYLESS);
1086
1087 sl = htx_add_stline(htx, HTX_BLK_RES_SL, flags,
1088 ist("HTTP/1.1"), ist("103"), ist("Early Hints"));
1089 if (!sl)
1090 goto error;
1091 sl->info.res.status = 103;
1092 }
1093
1094 /* Add the HTTP Early Hint HTTP 103 response heade */
1095 value->data = build_logline(s, b_tail(value), b_room(value), &rule->arg.http.fmt);
1096 if (!htx_add_header(htx, rule->arg.http.str, ist2(b_head(value), b_data(value))))
1097 goto error;
1098
1099 /* if it is the last rule or the next one is not an early-hint, terminate the current
1100 * response. */
1101 if (&next_rule->list == s->current_rule_list || next_rule->action_ptr != http_action_early_hint) {
1102 size_t data;
1103
1104 if (!htx_add_endof(htx, HTX_BLK_EOH)) {
1105 /* If an error occurred during an Early-hint rule,
1106 * remove the incomplete HTTP 103 response from the
1107 * buffer */
1108 goto error;
1109 }
1110
1111 data = htx->data - co_data(res);
1112 c_adv(res, data);
1113 res->total += data;
1114 }
1115
1116 leave:
1117 free_trash_chunk(value);
1118 return ret;
1119
1120 error:
1121 /* If an error occurred during an Early-hint rule, remove the incomplete
1122 * HTTP 103 response from the buffer */
1123 channel_htx_truncate(res, htx);
1124 ret = ACT_RET_ERR;
1125 goto leave;
1126}
1127
Christopher Fauletd1f27e32019-12-17 09:33:38 +01001128/* This function executes a set-header or add-header actions. It builds a string
1129 * in the trash from the specified format string. It finds the action to be
1130 * performed in <.action>, previously filled by function parse_set_header(). The
1131 * replacement action is excuted by the function http_action_set_header(). On
1132 * success, it returns ACT_RET_CONT. If an error occurs while soft rewrites are
1133 * enabled, the action is canceled, but the rule processing continue. Otherwsize
1134 * ACT_RET_ERR is returned.
1135 */
1136static enum act_return http_action_set_header(struct act_rule *rule, struct proxy *px,
1137 struct session *sess, struct stream *s, int flags)
1138{
1139 struct htx *htx = htxbuf((rule->from == ACT_F_HTTP_REQ) ? &s->req.buf : &s->res.buf);
1140 enum act_return ret = ACT_RET_CONT;
1141 struct buffer *replace;
1142 struct http_hdr_ctx ctx;
1143 struct ist n, v;
1144
1145 replace = alloc_trash_chunk();
1146 if (!replace)
1147 goto fail_alloc;
1148
1149 replace->data = build_logline(s, replace->area, replace->size, &rule->arg.http.fmt);
1150 n = rule->arg.http.str;
1151 v = ist2(replace->area, replace->data);
1152
1153 if (rule->action == 0) { // set-header
1154 /* remove all occurrences of the header */
1155 ctx.blk = NULL;
1156 while (http_find_header(htx, n, &ctx, 1))
1157 http_remove_header(htx, &ctx);
1158 }
1159
1160 /* Now add header */
1161 if (!http_add_header(htx, n, v))
1162 goto fail_rewrite;
1163
1164 leave:
1165 free_trash_chunk(replace);
1166 return ret;
1167
1168 fail_alloc:
1169 if (!(s->flags & SF_ERR_MASK))
1170 s->flags |= SF_ERR_RESOURCE;
1171 ret = ACT_RET_ERR;
1172 goto leave;
1173
1174 fail_rewrite:
1175 _HA_ATOMIC_ADD(&sess->fe->fe_counters.failed_rewrites, 1);
1176 if (s->flags & SF_BE_ASSIGNED)
1177 _HA_ATOMIC_ADD(&s->be->be_counters.failed_rewrites, 1);
1178 if (sess->listener->counters)
1179 _HA_ATOMIC_ADD(&sess->listener->counters->failed_rewrites, 1);
1180 if (objt_server(s->target))
1181 _HA_ATOMIC_ADD(&__objt_server(s->target)->counters.failed_rewrites, 1);
1182
1183 if (!(s->txn->req.flags & HTTP_MSGF_SOFT_RW))
1184 ret = ACT_RET_ERR;
1185 goto leave;
1186}
1187
Christopher Faulet81e20172019-12-12 16:40:30 +01001188/* Parse a "set-header", "add-header" or "early-hint" actions. It takes an
1189 * header name and a log-format string as arguments. It returns ACT_RET_PRS_OK
1190 * on success, ACT_RET_PRS_ERR on error.
1191 *
1192 * Note: same function is used for the request and the response. However
1193 * "early-hint" rules are only supported for request rules.
1194 */
1195static enum act_parse_ret parse_http_set_header(const char **args, int *orig_arg, struct proxy *px,
1196 struct act_rule *rule, char **err)
1197{
Christopher Faulet81e20172019-12-12 16:40:30 +01001198 int cap, cur_arg;
1199
Christopher Faulet91b3ec12020-01-17 22:30:06 +01001200 if (args[*orig_arg-1][0] == 'e') {
1201 rule->action = ACT_CUSTOM;
1202 rule->action_ptr = http_action_early_hint;
1203 }
Christopher Fauletd1f27e32019-12-17 09:33:38 +01001204 else {
1205 if (args[*orig_arg-1][0] == 's')
1206 rule->action = 0; // set-header
1207 else
1208 rule->action = 1; // add-header
1209 rule->action_ptr = http_action_set_header;
1210 }
Christopher Faulet2eb53962020-01-14 14:47:34 +01001211 rule->release_ptr = release_http_action;
Christopher Faulet81e20172019-12-12 16:40:30 +01001212
1213 cur_arg = *orig_arg;
1214 if (!*args[cur_arg] || !*args[cur_arg+1]) {
1215 memprintf(err, "expects exactly 2 arguments");
1216 return ACT_RET_PRS_ERR;
1217 }
1218
Christopher Faulet81e20172019-12-12 16:40:30 +01001219
Christopher Faulet96bff762019-12-17 13:46:18 +01001220 rule->arg.http.str.ptr = strdup(args[cur_arg]);
1221 rule->arg.http.str.len = strlen(rule->arg.http.str.ptr);
1222 LIST_INIT(&rule->arg.http.fmt);
Christopher Faulet81e20172019-12-12 16:40:30 +01001223
1224 if (rule->from == ACT_F_HTTP_REQ) {
1225 px->conf.args.ctx = ARGC_HRQ;
1226 cap = (px->cap & PR_CAP_FE) ? SMP_VAL_FE_HRQ_HDR : SMP_VAL_BE_HRQ_HDR;
1227 }
1228 else{
1229 px->conf.args.ctx = ARGC_HRS;
1230 cap = (px->cap & PR_CAP_BE) ? SMP_VAL_BE_HRS_HDR : SMP_VAL_FE_HRS_HDR;
1231 }
1232
1233 cur_arg++;
Christopher Faulet1337b322020-01-14 14:50:55 +01001234 if (!parse_logformat_string(args[cur_arg], px, &rule->arg.http.fmt, LOG_OPT_HTTP, cap, err)) {
1235 free(rule->arg.http.str.ptr);
Christopher Faulet81e20172019-12-12 16:40:30 +01001236 return ACT_RET_PRS_ERR;
Christopher Faulet1337b322020-01-14 14:50:55 +01001237 }
Christopher Faulet81e20172019-12-12 16:40:30 +01001238
1239 free(px->conf.lfs_file);
1240 px->conf.lfs_file = strdup(px->conf.args.file);
1241 px->conf.lfs_line = px->conf.args.line;
1242
1243 *orig_arg = cur_arg + 1;
1244 return ACT_RET_PRS_OK;
1245}
1246
Christopher Faulet92d34fe2019-12-17 09:20:34 +01001247/* This function executes a replace-header or replace-value actions. It
1248 * builds a string in the trash from the specified format string. It finds
1249 * the action to be performed in <.action>, previously filled by function
1250 * parse_replace_header(). The replacement action is excuted by the function
1251 * http_action_replace_header(). On success, it returns ACT_RET_CONT. If an error
1252 * occurs while soft rewrites are enabled, the action is canceled, but the rule
1253 * processing continue. Otherwsize ACT_RET_ERR is returned.
1254 */
1255static enum act_return http_action_replace_header(struct act_rule *rule, struct proxy *px,
1256 struct session *sess, struct stream *s, int flags)
1257{
1258 struct htx *htx = htxbuf((rule->from == ACT_F_HTTP_REQ) ? &s->req.buf : &s->res.buf);
1259 enum act_return ret = ACT_RET_CONT;
1260 struct buffer *replace;
1261 int r;
1262
1263 replace = alloc_trash_chunk();
1264 if (!replace)
1265 goto fail_alloc;
1266
1267 replace->data = build_logline(s, replace->area, replace->size, &rule->arg.http.fmt);
1268
1269 r = http_replace_hdrs(s, htx, rule->arg.http.str, replace->area, rule->arg.http.re, (rule->action == 0));
1270 if (r == -1)
1271 goto fail_rewrite;
1272
1273 leave:
1274 free_trash_chunk(replace);
1275 return ret;
1276
1277 fail_alloc:
1278 if (!(s->flags & SF_ERR_MASK))
1279 s->flags |= SF_ERR_RESOURCE;
1280 ret = ACT_RET_ERR;
1281 goto leave;
1282
1283 fail_rewrite:
1284 _HA_ATOMIC_ADD(&sess->fe->fe_counters.failed_rewrites, 1);
1285 if (s->flags & SF_BE_ASSIGNED)
1286 _HA_ATOMIC_ADD(&s->be->be_counters.failed_rewrites, 1);
1287 if (sess->listener->counters)
1288 _HA_ATOMIC_ADD(&sess->listener->counters->failed_rewrites, 1);
1289 if (objt_server(s->target))
1290 _HA_ATOMIC_ADD(&__objt_server(s->target)->counters.failed_rewrites, 1);
1291
1292 if (!(s->txn->req.flags & HTTP_MSGF_SOFT_RW))
1293 ret = ACT_RET_ERR;
1294 goto leave;
1295}
1296
Christopher Faulet81e20172019-12-12 16:40:30 +01001297/* Parse a "replace-header" or "replace-value" actions. It takes an header name,
1298 * a regex and replacement string as arguments. It returns ACT_RET_PRS_OK on
1299 * success, ACT_RET_PRS_ERR on error.
1300 */
1301static enum act_parse_ret parse_http_replace_header(const char **args, int *orig_arg, struct proxy *px,
1302 struct act_rule *rule, char **err)
1303{
1304 int cap, cur_arg;
1305
Christopher Faulet92d34fe2019-12-17 09:20:34 +01001306 if (args[*orig_arg-1][8] == 'h')
1307 rule->action = 0; // replace-header
1308 else
1309 rule->action = 1; // replace-value
1310 rule->action_ptr = http_action_replace_header;
Christopher Faulet2eb53962020-01-14 14:47:34 +01001311 rule->release_ptr = release_http_action;
Christopher Faulet81e20172019-12-12 16:40:30 +01001312
1313 cur_arg = *orig_arg;
1314 if (!*args[cur_arg] || !*args[cur_arg+1] || !*args[cur_arg+2]) {
1315 memprintf(err, "expects exactly 3 arguments");
1316 return ACT_RET_PRS_ERR;
1317 }
1318
Christopher Faulet96bff762019-12-17 13:46:18 +01001319 rule->arg.http.str.ptr = strdup(args[cur_arg]);
1320 rule->arg.http.str.len = strlen(rule->arg.http.str.ptr);
1321 LIST_INIT(&rule->arg.http.fmt);
Christopher Faulet81e20172019-12-12 16:40:30 +01001322
1323 cur_arg++;
Christopher Faulet1337b322020-01-14 14:50:55 +01001324 if (!(rule->arg.http.re = regex_comp(args[cur_arg], 1, 1, err))) {
1325 free(rule->arg.http.str.ptr);
Christopher Faulet81e20172019-12-12 16:40:30 +01001326 return ACT_RET_PRS_ERR;
Christopher Faulet1337b322020-01-14 14:50:55 +01001327 }
Christopher Faulet81e20172019-12-12 16:40:30 +01001328
1329 if (rule->from == ACT_F_HTTP_REQ) {
1330 px->conf.args.ctx = ARGC_HRQ;
1331 cap = (px->cap & PR_CAP_FE) ? SMP_VAL_FE_HRQ_HDR : SMP_VAL_BE_HRQ_HDR;
1332 }
1333 else{
1334 px->conf.args.ctx = ARGC_HRS;
1335 cap = (px->cap & PR_CAP_BE) ? SMP_VAL_BE_HRS_HDR : SMP_VAL_FE_HRS_HDR;
1336 }
1337
1338 cur_arg++;
Christopher Faulet1337b322020-01-14 14:50:55 +01001339 if (!parse_logformat_string(args[cur_arg], px, &rule->arg.http.fmt, LOG_OPT_HTTP, cap, err)) {
1340 free(rule->arg.http.str.ptr);
1341 regex_free(rule->arg.http.re);
Christopher Faulet81e20172019-12-12 16:40:30 +01001342 return ACT_RET_PRS_ERR;
Christopher Faulet1337b322020-01-14 14:50:55 +01001343 }
Christopher Faulet81e20172019-12-12 16:40:30 +01001344
1345 free(px->conf.lfs_file);
1346 px->conf.lfs_file = strdup(px->conf.args.file);
1347 px->conf.lfs_line = px->conf.args.line;
1348
1349 *orig_arg = cur_arg + 1;
1350 return ACT_RET_PRS_OK;
1351}
1352
1353/* Parse a "del-header" action. It takes an header name as argument. It returns
1354 * ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
1355 */
1356static enum act_parse_ret parse_http_del_header(const char **args, int *orig_arg, struct proxy *px,
1357 struct act_rule *rule, char **err)
1358{
1359 int cur_arg;
1360
1361 rule->action = ACT_HTTP_DEL_HDR;
Christopher Faulet2eb53962020-01-14 14:47:34 +01001362 rule->release_ptr = release_http_action;
Christopher Faulet81e20172019-12-12 16:40:30 +01001363
1364 cur_arg = *orig_arg;
1365 if (!*args[cur_arg]) {
1366 memprintf(err, "expects exactly 1 arguments");
1367 return ACT_RET_PRS_ERR;
1368 }
1369
Christopher Faulet96bff762019-12-17 13:46:18 +01001370 rule->arg.http.str.ptr = strdup(args[cur_arg]);
1371 rule->arg.http.str.len = strlen(rule->arg.http.str.ptr);
Christopher Faulet81e20172019-12-12 16:40:30 +01001372
1373 px->conf.args.ctx = (rule->from == ACT_F_HTTP_REQ ? ARGC_HRQ : ARGC_HRS);
1374
1375 *orig_arg = cur_arg + 1;
1376 return ACT_RET_PRS_OK;
1377}
1378
Christopher Faulet2eb53962020-01-14 14:47:34 +01001379/* Release memory allocated by an http redirect action. */
1380static void release_http_redir(struct act_rule *rule)
1381{
1382 struct logformat_node *lf, *lfb;
1383 struct redirect_rule *redir;
1384
1385 redir = rule->arg.redir;
1386 LIST_DEL(&redir->list);
1387 if (redir->cond) {
1388 prune_acl_cond(redir->cond);
1389 free(redir->cond);
1390 }
1391 free(redir->rdr_str);
1392 free(redir->cookie_str);
1393 list_for_each_entry_safe(lf, lfb, &redir->rdr_fmt, list) {
1394 LIST_DEL(&lf->list);
1395 free(lf);
1396 }
1397 free(redir);
1398}
1399
Christopher Faulet81e20172019-12-12 16:40:30 +01001400/* Parse a "redirect" action. It returns ACT_RET_PRS_OK on success,
1401 * ACT_RET_PRS_ERR on error.
1402 */
1403static enum act_parse_ret parse_http_redirect(const char **args, int *orig_arg, struct proxy *px,
1404 struct act_rule *rule, char **err)
1405{
1406 struct redirect_rule *redir;
1407 int dir, cur_arg;
1408
1409 rule->action = ACT_HTTP_REDIR;
Christopher Faulet245cf792019-12-18 14:58:12 +01001410 rule->flags |= ACT_FLAG_FINAL;
Christopher Faulet2eb53962020-01-14 14:47:34 +01001411 rule->release_ptr = release_http_redir;
Christopher Faulet81e20172019-12-12 16:40:30 +01001412
1413 cur_arg = *orig_arg;
1414
1415 dir = (rule->from == ACT_F_HTTP_REQ ? 0 : 1);
1416 if ((redir = http_parse_redirect_rule(px->conf.args.file, px->conf.args.line, px, &args[cur_arg], err, 1, dir)) == NULL)
1417 return ACT_RET_PRS_ERR;
1418
1419 rule->arg.redir = redir;
1420 rule->cond = redir->cond;
1421 redir->cond = NULL;
1422
1423 /* skip all arguments */
1424 while (*args[cur_arg])
1425 cur_arg++;
1426
1427 *orig_arg = cur_arg;
1428 return ACT_RET_PRS_OK;
1429}
1430
Christopher Faulet046cf442019-12-17 15:45:23 +01001431/* This function executes a add-acl, del-acl, set-map or del-map actions. On
1432 * success, it returns ACT_RET_CONT. Otherwsize ACT_RET_ERR is returned.
1433 */
1434static enum act_return http_action_set_map(struct act_rule *rule, struct proxy *px,
1435 struct session *sess, struct stream *s, int flags)
1436{
1437 struct pat_ref *ref;
1438 struct buffer *key = NULL, *value = NULL;
1439 enum act_return ret = ACT_RET_CONT;
1440
1441 /* collect reference */
1442 ref = pat_ref_lookup(rule->arg.map.ref);
1443 if (!ref)
1444 goto leave;
1445
1446 /* allocate key */
1447 key = alloc_trash_chunk();
1448 if (!key)
1449 goto fail_alloc;
1450
1451 /* collect key */
1452 key->data = build_logline(s, key->area, key->size, &rule->arg.map.key);
1453 key->area[key->data] = '\0';
1454
1455 switch (rule->action) {
1456 case 0: // add-acl
1457 /* add entry only if it does not already exist */
1458 HA_SPIN_LOCK(PATREF_LOCK, &ref->lock);
1459 if (pat_ref_find_elt(ref, key->area) == NULL)
1460 pat_ref_add(ref, key->area, NULL, NULL);
1461 HA_SPIN_UNLOCK(PATREF_LOCK, &ref->lock);
1462 break;
1463
1464 case 1: // set-map
1465 /* allocate value */
1466 value = alloc_trash_chunk();
1467 if (!value)
1468 goto fail_alloc;
1469
1470 /* collect value */
1471 value->data = build_logline(s, value->area, value->size, &rule->arg.map.value);
1472 value->area[value->data] = '\0';
1473
1474 HA_SPIN_LOCK(PATREF_LOCK, &ref->lock);
1475 if (pat_ref_find_elt(ref, key->area) != NULL) {
1476 /* update entry if it exists */
1477 pat_ref_set(ref, key->area, value->area, NULL);
1478 }
1479 else {
1480 /* insert a new entry */
1481 pat_ref_add(ref, key->area, value->area, NULL);
1482 }
1483 HA_SPIN_UNLOCK(PATREF_LOCK, &ref->lock);
1484 break;
1485
1486 case 2: // del-acl
1487 case 3: // del-map
1488 /* returned code: 1=ok, 0=ko */
1489 HA_SPIN_LOCK(PATREF_LOCK, &ref->lock);
1490 pat_ref_delete(ref, key->area);
1491 HA_SPIN_UNLOCK(PATREF_LOCK, &ref->lock);
1492 break;
1493
1494 default:
1495 ret = ACT_RET_ERR;
1496 }
1497
1498
1499 leave:
1500 free_trash_chunk(key);
1501 free_trash_chunk(value);
1502 return ret;
1503
1504 fail_alloc:
1505 if (!(s->flags & SF_ERR_MASK))
1506 s->flags |= SF_ERR_RESOURCE;
1507 ret = ACT_RET_ERR;
1508 goto leave;
1509}
1510
Christopher Faulet2eb53962020-01-14 14:47:34 +01001511/* Release memory allocated by an http map/acl action. */
1512static void release_http_map(struct act_rule *rule)
1513{
1514 struct logformat_node *lf, *lfb;
1515
1516 free(rule->arg.map.ref);
1517 list_for_each_entry_safe(lf, lfb, &rule->arg.map.key, list) {
1518 LIST_DEL(&lf->list);
1519 release_sample_expr(lf->expr);
1520 free(lf->arg);
1521 free(lf);
1522 }
1523 if (rule->action == 1) {
1524 list_for_each_entry_safe(lf, lfb, &rule->arg.map.value, list) {
1525 LIST_DEL(&lf->list);
1526 release_sample_expr(lf->expr);
1527 free(lf->arg);
1528 free(lf);
1529 }
1530 }
1531}
1532
Christopher Faulet81e20172019-12-12 16:40:30 +01001533/* Parse a "add-acl", "del-acl", "set-map" or "del-map" actions. It takes one or
Christopher Faulet046cf442019-12-17 15:45:23 +01001534 * two log-format string as argument depending on the action. The action is
1535 * stored in <.action> as an int (0=add-acl, 1=set-map, 2=del-acl,
1536 * 3=del-map). It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
Christopher Faulet81e20172019-12-12 16:40:30 +01001537 */
1538static enum act_parse_ret parse_http_set_map(const char **args, int *orig_arg, struct proxy *px,
1539 struct act_rule *rule, char **err)
1540{
1541 int cap, cur_arg;
1542
Christopher Faulet046cf442019-12-17 15:45:23 +01001543 if (args[*orig_arg-1][0] == 'a') // add-acl
1544 rule->action = 0;
1545 else if (args[*orig_arg-1][0] == 's') // set-map
1546 rule->action = 1;
1547 else if (args[*orig_arg-1][4] == 'a') // del-acl
1548 rule->action = 2;
1549 else if (args[*orig_arg-1][4] == 'm') // del-map
1550 rule->action = 3;
1551 else {
1552 memprintf(err, "internal error: unhandled action '%s'", args[0]);
1553 return ACT_RET_PRS_ERR;
1554 }
1555 rule->action_ptr = http_action_set_map;
Christopher Faulet2eb53962020-01-14 14:47:34 +01001556 rule->release_ptr = release_http_map;
Christopher Faulet81e20172019-12-12 16:40:30 +01001557
1558 cur_arg = *orig_arg;
Christopher Faulet046cf442019-12-17 15:45:23 +01001559 if (rule->action == 1 && (!*args[cur_arg] || !*args[cur_arg+1])) {
1560 /* 2 args for set-map */
Christopher Faulet81e20172019-12-12 16:40:30 +01001561 memprintf(err, "expects exactly 2 arguments");
1562 return ACT_RET_PRS_ERR;
1563 }
1564 else if (!*args[cur_arg]) {
Christopher Faulet046cf442019-12-17 15:45:23 +01001565 /* only one arg for other actions */
Christopher Faulet81e20172019-12-12 16:40:30 +01001566 memprintf(err, "expects exactly 1 arguments");
1567 return ACT_RET_PRS_ERR;
1568 }
1569
1570 /*
1571 * '+ 8' for 'set-map(' (same for del-map)
1572 * '- 9' for 'set-map(' + trailing ')' (same for del-map)
1573 */
1574 rule->arg.map.ref = my_strndup(args[cur_arg-1] + 8, strlen(args[cur_arg-1]) - 9);
1575
1576 if (rule->from == ACT_F_HTTP_REQ) {
1577 px->conf.args.ctx = ARGC_HRQ;
1578 cap = (px->cap & PR_CAP_FE) ? SMP_VAL_FE_HRQ_HDR : SMP_VAL_BE_HRQ_HDR;
1579 }
1580 else{
1581 px->conf.args.ctx = ARGC_HRS;
1582 cap = (px->cap & PR_CAP_BE) ? SMP_VAL_BE_HRS_HDR : SMP_VAL_FE_HRS_HDR;
1583 }
1584
1585 /* key pattern */
1586 LIST_INIT(&rule->arg.map.key);
Christopher Faulet1337b322020-01-14 14:50:55 +01001587 if (!parse_logformat_string(args[cur_arg], px, &rule->arg.map.key, LOG_OPT_HTTP, cap, err)) {
1588 free(rule->arg.map.ref);
Christopher Faulet81e20172019-12-12 16:40:30 +01001589 return ACT_RET_PRS_ERR;
Christopher Faulet1337b322020-01-14 14:50:55 +01001590 }
Christopher Faulet81e20172019-12-12 16:40:30 +01001591
Christopher Faulet046cf442019-12-17 15:45:23 +01001592 if (rule->action == 1) {
Christopher Faulet81e20172019-12-12 16:40:30 +01001593 /* value pattern for set-map only */
1594 cur_arg++;
1595 LIST_INIT(&rule->arg.map.value);
Christopher Faulet1337b322020-01-14 14:50:55 +01001596 if (!parse_logformat_string(args[cur_arg], px, &rule->arg.map.value, LOG_OPT_HTTP, cap, err)) {
1597 free(rule->arg.map.ref);
Christopher Faulet81e20172019-12-12 16:40:30 +01001598 return ACT_RET_PRS_ERR;
Christopher Faulet1337b322020-01-14 14:50:55 +01001599 }
Christopher Faulet81e20172019-12-12 16:40:30 +01001600 }
1601
1602 free(px->conf.lfs_file);
1603 px->conf.lfs_file = strdup(px->conf.args.file);
1604 px->conf.lfs_line = px->conf.args.line;
1605
1606 *orig_arg = cur_arg + 1;
1607 return ACT_RET_PRS_OK;
1608}
1609
Christopher Fauletac98d812019-12-18 09:20:16 +01001610/* This function executes a track-sc* actions. On success, it returns
1611 * ACT_RET_CONT. Otherwsize ACT_RET_ERR is returned.
1612 */
1613static enum act_return http_action_track_sc(struct act_rule *rule, struct proxy *px,
1614 struct session *sess, struct stream *s, int flags)
1615{
1616 struct stktable *t;
1617 struct stksess *ts;
1618 struct stktable_key *key;
1619 void *ptr1, *ptr2, *ptr3, *ptr4;
1620 int opt;
1621
1622 ptr1 = ptr2 = ptr3 = ptr4 = NULL;
1623 opt = ((rule->from == ACT_F_HTTP_REQ) ? SMP_OPT_DIR_REQ : SMP_OPT_DIR_RES) | SMP_OPT_FINAL;
1624
1625 t = rule->arg.trk_ctr.table.t;
1626 key = stktable_fetch_key(t, s->be, sess, s, opt, rule->arg.trk_ctr.expr, NULL);
1627
1628 if (!key)
1629 goto end;
1630 ts = stktable_get_entry(t, key);
1631 if (!ts)
1632 goto end;
1633
1634 stream_track_stkctr(&s->stkctr[rule->action], t, ts);
1635
1636 /* let's count a new HTTP request as it's the first time we do it */
1637 ptr1 = stktable_data_ptr(t, ts, STKTABLE_DT_HTTP_REQ_CNT);
1638 ptr2 = stktable_data_ptr(t, ts, STKTABLE_DT_HTTP_REQ_RATE);
1639
1640 /* When the client triggers a 4xx from the server, it's most often due
1641 * to a missing object or permission. These events should be tracked
1642 * because if they happen often, it may indicate a brute force or a
1643 * vulnerability scan. Normally this is done when receiving the response
1644 * but here we're tracking after this ought to have been done so we have
1645 * to do it on purpose.
1646 */
1647 if (rule->from == ACT_F_HTTP_RES && (unsigned)(s->txn->status - 400) < 100) {
1648 ptr3 = stktable_data_ptr(t, ts, STKTABLE_DT_HTTP_ERR_CNT);
1649 ptr4 = stktable_data_ptr(t, ts, STKTABLE_DT_HTTP_ERR_RATE);
1650 }
1651
1652 if (ptr1 || ptr2 || ptr3 || ptr4) {
1653 HA_RWLOCK_WRLOCK(STK_SESS_LOCK, &ts->lock);
1654
1655 if (ptr1)
1656 stktable_data_cast(ptr1, http_req_cnt)++;
1657 if (ptr2)
1658 update_freq_ctr_period(&stktable_data_cast(ptr2, http_req_rate),
1659 t->data_arg[STKTABLE_DT_HTTP_REQ_RATE].u, 1);
1660 if (ptr3)
1661 stktable_data_cast(ptr3, http_err_cnt)++;
1662 if (ptr4)
1663 update_freq_ctr_period(&stktable_data_cast(ptr4, http_err_rate),
1664 t->data_arg[STKTABLE_DT_HTTP_ERR_RATE].u, 1);
1665
1666 HA_RWLOCK_WRUNLOCK(STK_SESS_LOCK, &ts->lock);
1667
1668 /* If data was modified, we need to touch to re-schedule sync */
1669 stktable_touch_local(t, ts, 0);
1670 }
1671
1672 stkctr_set_flags(&s->stkctr[rule->action], STKCTR_TRACK_CONTENT);
1673 if (sess->fe != s->be)
1674 stkctr_set_flags(&s->stkctr[rule->action], STKCTR_TRACK_BACKEND);
1675
1676 end:
1677 return ACT_RET_CONT;
1678}
Christopher Faulet81e20172019-12-12 16:40:30 +01001679
Christopher Faulet2eb53962020-01-14 14:47:34 +01001680static void release_http_track_sc(struct act_rule *rule)
1681{
1682 release_sample_expr(rule->arg.trk_ctr.expr);
1683}
1684
Christopher Faulet81e20172019-12-12 16:40:30 +01001685/* Parse a "track-sc*" actions. It returns ACT_RET_PRS_OK on success,
1686 * ACT_RET_PRS_ERR on error.
1687 */
1688static enum act_parse_ret parse_http_track_sc(const char **args, int *orig_arg, struct proxy *px,
1689 struct act_rule *rule, char **err)
1690{
1691 struct sample_expr *expr;
1692 unsigned int where;
1693 unsigned int tsc_num;
1694 const char *tsc_num_str;
1695 int cur_arg;
1696
1697 tsc_num_str = &args[*orig_arg-1][8];
1698 if (cfg_parse_track_sc_num(&tsc_num, tsc_num_str, tsc_num_str + strlen(tsc_num_str), err) == -1)
1699 return ACT_RET_PRS_ERR;
1700
1701 cur_arg = *orig_arg;
1702 expr = sample_parse_expr((char **)args, &cur_arg, px->conf.args.file, px->conf.args.line,
1703 err, &px->conf.args);
1704 if (!expr)
1705 return ACT_RET_PRS_ERR;
1706
1707 where = 0;
1708 if (px->cap & PR_CAP_FE)
1709 where |= (rule->from == ACT_F_HTTP_REQ ? SMP_VAL_FE_HRQ_HDR : SMP_VAL_FE_HRS_HDR);
1710 if (px->cap & PR_CAP_BE)
1711 where |= (rule->from == ACT_F_HTTP_REQ ? SMP_VAL_BE_HRQ_HDR : SMP_VAL_BE_HRS_HDR);
1712
1713 if (!(expr->fetch->val & where)) {
1714 memprintf(err, "fetch method '%s' extracts information from '%s', none of which is available here",
1715 args[cur_arg-1], sample_src_names(expr->fetch->use));
Christopher Faulet1337b322020-01-14 14:50:55 +01001716 release_sample_expr(expr);
Christopher Faulet81e20172019-12-12 16:40:30 +01001717 return ACT_RET_PRS_ERR;
1718 }
1719
1720 if (strcmp(args[cur_arg], "table") == 0) {
1721 cur_arg++;
1722 if (!*args[cur_arg]) {
1723 memprintf(err, "missing table name");
Christopher Faulet1337b322020-01-14 14:50:55 +01001724 release_sample_expr(expr);
Christopher Faulet81e20172019-12-12 16:40:30 +01001725 return ACT_RET_PRS_ERR;
1726 }
1727
1728 /* we copy the table name for now, it will be resolved later */
1729 rule->arg.trk_ctr.table.n = strdup(args[cur_arg]);
1730 cur_arg++;
1731 }
1732
Christopher Fauletac98d812019-12-18 09:20:16 +01001733 rule->action = tsc_num;
Christopher Faulet81e20172019-12-12 16:40:30 +01001734 rule->arg.trk_ctr.expr = expr;
Christopher Fauletac98d812019-12-18 09:20:16 +01001735 rule->action_ptr = http_action_track_sc;
Christopher Faulet2eb53962020-01-14 14:47:34 +01001736 rule->release_ptr = release_http_track_sc;
Christopher Faulet81e20172019-12-12 16:40:30 +01001737 rule->check_ptr = check_trk_action;
1738
1739 *orig_arg = cur_arg;
1740 return ACT_RET_PRS_OK;
1741}
1742
Christopher Faulet46f95542019-12-20 10:07:22 +01001743/* This function executes a strict-mode actions. On success, it always returns
1744 * ACT_RET_CONT
1745 */
1746static enum act_return http_action_strict_mode(struct act_rule *rule, struct proxy *px,
1747 struct session *sess, struct stream *s, int flags)
1748{
1749 struct http_msg *msg = ((rule->from == ACT_F_HTTP_REQ) ? &s->txn->req : &s->txn->rsp);
1750
1751 if (rule->action == 0) // strict-mode on
1752 msg->flags &= ~HTTP_MSGF_SOFT_RW;
1753 else // strict-mode off
1754 msg->flags |= HTTP_MSGF_SOFT_RW;
1755 return ACT_RET_CONT;
1756}
1757
1758/* Parse a "strict-mode" action. It returns ACT_RET_PRS_OK on success,
1759 * ACT_RET_PRS_ERR on error.
1760 */
1761static enum act_parse_ret parse_http_strict_mode(const char **args, int *orig_arg, struct proxy *px,
1762 struct act_rule *rule, char **err)
1763{
1764 int cur_arg;
1765
1766
1767 cur_arg = *orig_arg;
1768 if (!*args[cur_arg]) {
1769 memprintf(err, "expects exactly 1 arguments");
1770 return ACT_RET_PRS_ERR;
1771 }
1772
1773 if (strcasecmp(args[cur_arg], "on") == 0)
1774 rule->action = 0; // strict-mode on
1775 else if (strcasecmp(args[cur_arg], "off") == 0)
1776 rule->action = 1; // strict-mode off
1777 else {
1778 memprintf(err, "Unexpected value '%s'. Only 'on' and 'off' are supported", args[cur_arg]);
1779 return ACT_RET_PRS_ERR;
1780 }
1781 rule->action_ptr = http_action_strict_mode;
1782
1783 *orig_arg = cur_arg + 1;
1784 return ACT_RET_PRS_OK;
1785}
1786
Willy Tarreau79e57332018-10-02 16:01:16 +02001787/************************************************************************/
1788/* All supported http-request action keywords must be declared here. */
1789/************************************************************************/
1790
1791static struct action_kw_list http_req_actions = {
1792 .kw = {
Christopher Faulet81e20172019-12-12 16:40:30 +01001793 { "add-acl", parse_http_set_map, 1 },
1794 { "add-header", parse_http_set_header, 0 },
1795 { "allow", parse_http_allow, 0 },
1796 { "auth", parse_http_auth, 0 },
1797 { "capture", parse_http_req_capture, 0 },
1798 { "del-acl", parse_http_set_map, 1 },
1799 { "del-header", parse_http_del_header, 0 },
1800 { "del-map", parse_http_set_map, 1 },
Christopher Faulete0fca292020-01-13 21:49:03 +01001801 { "deny", parse_http_deny, 0 },
Christopher Faulet81e20172019-12-12 16:40:30 +01001802 { "disable-l7-retry", parse_http_req_disable_l7_retry, 0 },
1803 { "early-hint", parse_http_set_header, 0 },
1804 { "redirect", parse_http_redirect, 0 },
1805 { "reject", parse_http_action_reject, 0 },
1806 { "replace-header", parse_http_replace_header, 0 },
1807 { "replace-path", parse_replace_uri, 0 },
1808 { "replace-uri", parse_replace_uri, 0 },
1809 { "replace-value", parse_http_replace_header, 0 },
1810 { "set-header", parse_http_set_header, 0 },
1811 { "set-log-level", parse_http_set_log_level, 0 },
1812 { "set-map", parse_http_set_map, 1 },
1813 { "set-method", parse_set_req_line, 0 },
1814 { "set-mark", parse_http_set_mark, 0 },
1815 { "set-nice", parse_http_set_nice, 0 },
1816 { "set-path", parse_set_req_line, 0 },
1817 { "set-query", parse_set_req_line, 0 },
1818 { "set-tos", parse_http_set_tos, 0 },
1819 { "set-uri", parse_set_req_line, 0 },
Christopher Faulet46f95542019-12-20 10:07:22 +01001820 { "strict-mode", parse_http_strict_mode, 0 },
Christopher Faulete0fca292020-01-13 21:49:03 +01001821 { "tarpit", parse_http_deny, 0 },
Christopher Faulet81e20172019-12-12 16:40:30 +01001822 { "track-sc", parse_http_track_sc, 1 },
Willy Tarreau79e57332018-10-02 16:01:16 +02001823 { NULL, NULL }
1824 }
1825};
1826
Willy Tarreau0108d902018-11-25 19:14:37 +01001827INITCALL1(STG_REGISTER, http_req_keywords_register, &http_req_actions);
1828
Willy Tarreau79e57332018-10-02 16:01:16 +02001829static struct action_kw_list http_res_actions = {
1830 .kw = {
Christopher Faulet81e20172019-12-12 16:40:30 +01001831 { "add-acl", parse_http_set_map, 1 },
1832 { "add-header", parse_http_set_header, 0 },
1833 { "allow", parse_http_allow, 0 },
1834 { "capture", parse_http_res_capture, 0 },
1835 { "del-acl", parse_http_set_map, 1 },
1836 { "del-header", parse_http_del_header, 0 },
1837 { "del-map", parse_http_set_map, 1 },
Christopher Faulete0fca292020-01-13 21:49:03 +01001838 { "deny", parse_http_deny, 0 },
Christopher Faulet81e20172019-12-12 16:40:30 +01001839 { "redirect", parse_http_redirect, 0 },
1840 { "replace-header", parse_http_replace_header, 0 },
1841 { "replace-value", parse_http_replace_header, 0 },
1842 { "set-header", parse_http_set_header, 0 },
1843 { "set-log-level", parse_http_set_log_level, 0 },
1844 { "set-map", parse_http_set_map, 1 },
1845 { "set-mark", parse_http_set_mark, 0 },
1846 { "set-nice", parse_http_set_nice, 0 },
1847 { "set-status", parse_http_set_status, 0 },
1848 { "set-tos", parse_http_set_tos, 0 },
Christopher Faulet46f95542019-12-20 10:07:22 +01001849 { "strict-mode", parse_http_strict_mode, 0 },
Christopher Faulet81e20172019-12-12 16:40:30 +01001850 { "track-sc", parse_http_track_sc, 1 },
Willy Tarreau79e57332018-10-02 16:01:16 +02001851 { NULL, NULL }
1852 }
1853};
1854
Willy Tarreau0108d902018-11-25 19:14:37 +01001855INITCALL1(STG_REGISTER, http_res_keywords_register, &http_res_actions);
Willy Tarreau79e57332018-10-02 16:01:16 +02001856
1857/*
1858 * Local variables:
1859 * c-indent-level: 8
1860 * c-basic-offset: 8
1861 * End:
1862 */