blob: 9907425fc0484704b820a630c4de106646117955 [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
788/* Parse "deny" or "tarpit" actions for a request rule. It may take 2 optional arguments
789 * to define the status code. It returns ACT_RET_PRS_OK on success,
790 * ACT_RET_PRS_ERR on error.
791 */
792static enum act_parse_ret parse_http_req_deny(const char **args, int *orig_arg, struct proxy *px,
793 struct act_rule *rule, char **err)
794{
795 int code, hc, cur_arg;
796
797 cur_arg = *orig_arg;
798 if (!strcmp(args[cur_arg-1], "tarpit")) {
799 rule->action = ACT_HTTP_REQ_TARPIT;
Christopher Faulet96bff762019-12-17 13:46:18 +0100800 rule->arg.http.i = HTTP_ERR_500;
Christopher Faulet81e20172019-12-12 16:40:30 +0100801 }
802 else {
803 rule->action = ACT_ACTION_DENY;
Christopher Faulet96bff762019-12-17 13:46:18 +0100804 rule->arg.http.i = HTTP_ERR_403;
Christopher Faulet81e20172019-12-12 16:40:30 +0100805 }
Christopher Faulet245cf792019-12-18 14:58:12 +0100806 rule->flags |= ACT_FLAG_FINAL;
Christopher Faulet81e20172019-12-12 16:40:30 +0100807
808 if (strcmp(args[cur_arg], "deny_status") == 0) {
809 cur_arg++;
810 if (!*args[cur_arg]) {
811 memprintf(err, "missing status code.\n");
812 return ACT_RET_PRS_ERR;
813 }
814
815 code = atol(args[cur_arg]);
816 cur_arg++;
817 for (hc = 0; hc < HTTP_ERR_SIZE; hc++) {
818 if (http_err_codes[hc] == code) {
Christopher Faulet96bff762019-12-17 13:46:18 +0100819 rule->arg.http.i = hc;
Christopher Faulet81e20172019-12-12 16:40:30 +0100820 break;
821 }
822 }
823 if (hc >= HTTP_ERR_SIZE)
824 memprintf(err, "status code %d not handled, using default code %d",
Christopher Faulet96bff762019-12-17 13:46:18 +0100825 code, http_err_codes[rule->arg.http.i]);
Christopher Faulet81e20172019-12-12 16:40:30 +0100826 }
827
828 *orig_arg = cur_arg;
829 return ACT_RET_PRS_OK;
830}
831
832/* Parse a "deny" action for a response rule. It takes no argument. It returns
833 * ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
834 */
835static enum act_parse_ret parse_http_res_deny(const char **args, int *orig_arg, struct proxy *px,
836 struct act_rule *rule, char **err)
837{
838 rule->action = ACT_ACTION_DENY;
Christopher Faulet245cf792019-12-18 14:58:12 +0100839 rule->flags |= ACT_FLAG_FINAL;
Christopher Faulet81e20172019-12-12 16:40:30 +0100840 return ACT_RET_PRS_OK;
841}
842
843/* Parse a "auth" action. It may take 2 optional arguments to define a "realm"
844 * parameter. It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
845 */
846static enum act_parse_ret parse_http_auth(const char **args, int *orig_arg, struct proxy *px,
847 struct act_rule *rule, char **err)
848{
849 int cur_arg;
850
851 rule->action = ACT_HTTP_REQ_AUTH;
Christopher Faulet245cf792019-12-18 14:58:12 +0100852 rule->flags |= ACT_FLAG_FINAL;
Christopher Faulet2eb53962020-01-14 14:47:34 +0100853 rule->release_ptr = release_http_action;
Christopher Faulet81e20172019-12-12 16:40:30 +0100854
855 cur_arg = *orig_arg;
856 if (!strcmp(args[cur_arg], "realm")) {
857 cur_arg++;
858 if (!*args[cur_arg]) {
859 memprintf(err, "missing realm value.\n");
860 return ACT_RET_PRS_ERR;
861 }
Christopher Faulet96bff762019-12-17 13:46:18 +0100862 rule->arg.http.str.ptr = strdup(args[cur_arg]);
863 rule->arg.http.str.len = strlen(rule->arg.http.str.ptr);
Christopher Faulet81e20172019-12-12 16:40:30 +0100864 cur_arg++;
865 }
866
867 *orig_arg = cur_arg;
868 return ACT_RET_PRS_OK;
869}
870
871/* Parse a "set-nice" action. It takes the nice value as argument. It returns
872 * ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
873 */
874static enum act_parse_ret parse_http_set_nice(const char **args, int *orig_arg, struct proxy *px,
875 struct act_rule *rule, char **err)
876{
877 int cur_arg;
878
879 rule->action = ACT_HTTP_SET_NICE;
880
881 cur_arg = *orig_arg;
882 if (!*args[cur_arg]) {
883 memprintf(err, "expects exactly 1 argument (integer value)");
884 return ACT_RET_PRS_ERR;
885 }
Christopher Faulet96bff762019-12-17 13:46:18 +0100886 rule->arg.http.i = atoi(args[cur_arg]);
887 if (rule->arg.http.i < -1024)
888 rule->arg.http.i = -1024;
889 else if (rule->arg.http.i > 1024)
890 rule->arg.http.i = 1024;
Christopher Faulet81e20172019-12-12 16:40:30 +0100891
892 *orig_arg = cur_arg + 1;
893 return ACT_RET_PRS_OK;
894}
895
896/* Parse a "set-tos" action. It takes the TOS value as argument. It returns
897 * ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
898 */
899static enum act_parse_ret parse_http_set_tos(const char **args, int *orig_arg, struct proxy *px,
900 struct act_rule *rule, char **err)
901{
902#ifdef IP_TOS
903 char *endp;
904 int cur_arg;
905
906 rule->action = ACT_HTTP_SET_TOS;
907
908 cur_arg = *orig_arg;
909 if (!*args[cur_arg]) {
910 memprintf(err, "expects exactly 1 argument (integer/hex value)");
911 return ACT_RET_PRS_ERR;
912 }
Christopher Faulet96bff762019-12-17 13:46:18 +0100913 rule->arg.http.i = strtol(args[cur_arg], &endp, 0);
Christopher Faulet81e20172019-12-12 16:40:30 +0100914 if (endp && *endp != '\0') {
915 memprintf(err, "invalid character starting at '%s' (integer/hex value expected)", endp);
916 return ACT_RET_PRS_ERR;
917 }
918
919 *orig_arg = cur_arg + 1;
920 return ACT_RET_PRS_OK;
921#else
922 memprintf(err, "not supported on this platform (IP_TOS undefined)");
923 return ACT_RET_PRS_ERR;
924#endif
925}
926
927/* Parse a "set-mark" action. It takes the MARK value as argument. It returns
928 * ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
929 */
930static enum act_parse_ret parse_http_set_mark(const char **args, int *orig_arg, struct proxy *px,
931 struct act_rule *rule, char **err)
932{
933#ifdef SO_MARK
934 char *endp;
935 int cur_arg;
936
937 rule->action = ACT_HTTP_SET_MARK;
938
939 cur_arg = *orig_arg;
940 if (!*args[cur_arg]) {
941 memprintf(err, "expects exactly 1 argument (integer/hex value)");
942 return ACT_RET_PRS_ERR;
943 }
Christopher Faulet96bff762019-12-17 13:46:18 +0100944 rule->arg.http.i = strtoul(args[cur_arg], &endp, 0);
Christopher Faulet81e20172019-12-12 16:40:30 +0100945 if (endp && *endp != '\0') {
946 memprintf(err, "invalid character starting at '%s' (integer/hex value expected)", endp);
947 return ACT_RET_PRS_ERR;
948 }
949
950 *orig_arg = cur_arg + 1;
951 global.last_checks |= LSTCHK_NETADM;
952 return ACT_RET_PRS_OK;
953#else
954 memprintf(err, "not supported on this platform (SO_MARK undefined)");
955 return ACT_RET_PRS_ERR;
956#endif
957}
958
959/* Parse a "set-log-level" action. It takes the level value as argument. It
960 * returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
961 */
962static enum act_parse_ret parse_http_set_log_level(const char **args, int *orig_arg, struct proxy *px,
963 struct act_rule *rule, char **err)
964{
965 int cur_arg;
966
967 rule->action = ACT_HTTP_SET_LOGL;
968
969 cur_arg = *orig_arg;
970 if (!*args[cur_arg]) {
971 bad_log_level:
972 memprintf(err, "expects exactly 1 argument (log level name or 'silent')");
973 return ACT_RET_PRS_ERR;
974 }
975 if (strcmp(args[cur_arg], "silent") == 0)
Christopher Faulet96bff762019-12-17 13:46:18 +0100976 rule->arg.http.i = -1;
977 else if ((rule->arg.http.i = get_log_level(args[cur_arg]) + 1) == 0)
Christopher Faulet81e20172019-12-12 16:40:30 +0100978 goto bad_log_level;
979
980 *orig_arg = cur_arg + 1;
981 return ACT_RET_PRS_OK;
982}
983
Christopher Faulet91b3ec12020-01-17 22:30:06 +0100984/* This function executes a early-hint action. It adds an HTTP Early Hint HTTP
985 * 103 response header with <.arg.http.str> name and with a value built
986 * according to <.arg.http.fmt> log line format. If it is the first early-hint
987 * rule of a serie, the 103 response start-line is added first. At the end, if
988 * the next rule is not an early-hint rule or if it is the last rule, the EOH
989 * block is added to terminate the response. On success, it returns
990 * ACT_RET_CONT. If an error occurs while soft rewrites are enabled, the action
991 * is canceled, but the rule processing continue. Otherwsize ACT_RET_ERR is
992 * returned.
993 */
994static enum act_return http_action_early_hint(struct act_rule *rule, struct proxy *px,
995 struct session *sess, struct stream *s, int flags)
996{
997 struct act_rule *prev_rule, *next_rule;
998 struct channel *res = &s->res;
999 struct htx *htx = htx_from_buf(&res->buf);
1000 struct buffer *value = alloc_trash_chunk();
1001 enum act_return ret = ACT_RET_CONT;
1002
1003 if (!(s->txn->req.flags & HTTP_MSGF_VER_11))
1004 goto leave;
1005
1006 if (!value) {
1007 if (!(s->flags & SF_ERR_MASK))
1008 s->flags |= SF_ERR_RESOURCE;
1009 goto error;
1010 }
1011
1012 /* get previous and next rules */
1013 prev_rule = LIST_PREV(&rule->list, typeof(rule), list);
1014 next_rule = LIST_NEXT(&rule->list, typeof(rule), list);
1015
1016 /* if no previous rule or previous rule is not early-hint, start a new response. Otherwise,
1017 * continue to add link to a previously started response */
1018 if (&prev_rule->list == s->current_rule_list || prev_rule->action_ptr != http_action_early_hint) {
1019 struct htx_sl *sl;
1020 unsigned int flags = (HTX_SL_F_IS_RESP|HTX_SL_F_VER_11|
1021 HTX_SL_F_XFER_LEN|HTX_SL_F_BODYLESS);
1022
1023 sl = htx_add_stline(htx, HTX_BLK_RES_SL, flags,
1024 ist("HTTP/1.1"), ist("103"), ist("Early Hints"));
1025 if (!sl)
1026 goto error;
1027 sl->info.res.status = 103;
1028 }
1029
1030 /* Add the HTTP Early Hint HTTP 103 response heade */
1031 value->data = build_logline(s, b_tail(value), b_room(value), &rule->arg.http.fmt);
1032 if (!htx_add_header(htx, rule->arg.http.str, ist2(b_head(value), b_data(value))))
1033 goto error;
1034
1035 /* if it is the last rule or the next one is not an early-hint, terminate the current
1036 * response. */
1037 if (&next_rule->list == s->current_rule_list || next_rule->action_ptr != http_action_early_hint) {
1038 size_t data;
1039
1040 if (!htx_add_endof(htx, HTX_BLK_EOH)) {
1041 /* If an error occurred during an Early-hint rule,
1042 * remove the incomplete HTTP 103 response from the
1043 * buffer */
1044 goto error;
1045 }
1046
1047 data = htx->data - co_data(res);
1048 c_adv(res, data);
1049 res->total += data;
1050 }
1051
1052 leave:
1053 free_trash_chunk(value);
1054 return ret;
1055
1056 error:
1057 /* If an error occurred during an Early-hint rule, remove the incomplete
1058 * HTTP 103 response from the buffer */
1059 channel_htx_truncate(res, htx);
1060 ret = ACT_RET_ERR;
1061 goto leave;
1062}
1063
Christopher Fauletd1f27e32019-12-17 09:33:38 +01001064/* This function executes a set-header or add-header actions. It builds a string
1065 * in the trash from the specified format string. It finds the action to be
1066 * performed in <.action>, previously filled by function parse_set_header(). The
1067 * replacement action is excuted by the function http_action_set_header(). On
1068 * success, it returns ACT_RET_CONT. If an error occurs while soft rewrites are
1069 * enabled, the action is canceled, but the rule processing continue. Otherwsize
1070 * ACT_RET_ERR is returned.
1071 */
1072static enum act_return http_action_set_header(struct act_rule *rule, struct proxy *px,
1073 struct session *sess, struct stream *s, int flags)
1074{
1075 struct htx *htx = htxbuf((rule->from == ACT_F_HTTP_REQ) ? &s->req.buf : &s->res.buf);
1076 enum act_return ret = ACT_RET_CONT;
1077 struct buffer *replace;
1078 struct http_hdr_ctx ctx;
1079 struct ist n, v;
1080
1081 replace = alloc_trash_chunk();
1082 if (!replace)
1083 goto fail_alloc;
1084
1085 replace->data = build_logline(s, replace->area, replace->size, &rule->arg.http.fmt);
1086 n = rule->arg.http.str;
1087 v = ist2(replace->area, replace->data);
1088
1089 if (rule->action == 0) { // set-header
1090 /* remove all occurrences of the header */
1091 ctx.blk = NULL;
1092 while (http_find_header(htx, n, &ctx, 1))
1093 http_remove_header(htx, &ctx);
1094 }
1095
1096 /* Now add header */
1097 if (!http_add_header(htx, n, v))
1098 goto fail_rewrite;
1099
1100 leave:
1101 free_trash_chunk(replace);
1102 return ret;
1103
1104 fail_alloc:
1105 if (!(s->flags & SF_ERR_MASK))
1106 s->flags |= SF_ERR_RESOURCE;
1107 ret = ACT_RET_ERR;
1108 goto leave;
1109
1110 fail_rewrite:
1111 _HA_ATOMIC_ADD(&sess->fe->fe_counters.failed_rewrites, 1);
1112 if (s->flags & SF_BE_ASSIGNED)
1113 _HA_ATOMIC_ADD(&s->be->be_counters.failed_rewrites, 1);
1114 if (sess->listener->counters)
1115 _HA_ATOMIC_ADD(&sess->listener->counters->failed_rewrites, 1);
1116 if (objt_server(s->target))
1117 _HA_ATOMIC_ADD(&__objt_server(s->target)->counters.failed_rewrites, 1);
1118
1119 if (!(s->txn->req.flags & HTTP_MSGF_SOFT_RW))
1120 ret = ACT_RET_ERR;
1121 goto leave;
1122}
1123
Christopher Faulet81e20172019-12-12 16:40:30 +01001124/* Parse a "set-header", "add-header" or "early-hint" actions. It takes an
1125 * header name and a log-format string as arguments. It returns ACT_RET_PRS_OK
1126 * on success, ACT_RET_PRS_ERR on error.
1127 *
1128 * Note: same function is used for the request and the response. However
1129 * "early-hint" rules are only supported for request rules.
1130 */
1131static enum act_parse_ret parse_http_set_header(const char **args, int *orig_arg, struct proxy *px,
1132 struct act_rule *rule, char **err)
1133{
Christopher Faulet81e20172019-12-12 16:40:30 +01001134 int cap, cur_arg;
1135
Christopher Faulet91b3ec12020-01-17 22:30:06 +01001136 if (args[*orig_arg-1][0] == 'e') {
1137 rule->action = ACT_CUSTOM;
1138 rule->action_ptr = http_action_early_hint;
1139 }
Christopher Fauletd1f27e32019-12-17 09:33:38 +01001140 else {
1141 if (args[*orig_arg-1][0] == 's')
1142 rule->action = 0; // set-header
1143 else
1144 rule->action = 1; // add-header
1145 rule->action_ptr = http_action_set_header;
1146 }
Christopher Faulet2eb53962020-01-14 14:47:34 +01001147 rule->release_ptr = release_http_action;
Christopher Faulet81e20172019-12-12 16:40:30 +01001148
1149 cur_arg = *orig_arg;
1150 if (!*args[cur_arg] || !*args[cur_arg+1]) {
1151 memprintf(err, "expects exactly 2 arguments");
1152 return ACT_RET_PRS_ERR;
1153 }
1154
Christopher Faulet81e20172019-12-12 16:40:30 +01001155
Christopher Faulet96bff762019-12-17 13:46:18 +01001156 rule->arg.http.str.ptr = strdup(args[cur_arg]);
1157 rule->arg.http.str.len = strlen(rule->arg.http.str.ptr);
1158 LIST_INIT(&rule->arg.http.fmt);
Christopher Faulet81e20172019-12-12 16:40:30 +01001159
1160 if (rule->from == ACT_F_HTTP_REQ) {
1161 px->conf.args.ctx = ARGC_HRQ;
1162 cap = (px->cap & PR_CAP_FE) ? SMP_VAL_FE_HRQ_HDR : SMP_VAL_BE_HRQ_HDR;
1163 }
1164 else{
1165 px->conf.args.ctx = ARGC_HRS;
1166 cap = (px->cap & PR_CAP_BE) ? SMP_VAL_BE_HRS_HDR : SMP_VAL_FE_HRS_HDR;
1167 }
1168
1169 cur_arg++;
Christopher Faulet1337b322020-01-14 14:50:55 +01001170 if (!parse_logformat_string(args[cur_arg], px, &rule->arg.http.fmt, LOG_OPT_HTTP, cap, err)) {
1171 free(rule->arg.http.str.ptr);
Christopher Faulet81e20172019-12-12 16:40:30 +01001172 return ACT_RET_PRS_ERR;
Christopher Faulet1337b322020-01-14 14:50:55 +01001173 }
Christopher Faulet81e20172019-12-12 16:40:30 +01001174
1175 free(px->conf.lfs_file);
1176 px->conf.lfs_file = strdup(px->conf.args.file);
1177 px->conf.lfs_line = px->conf.args.line;
1178
1179 *orig_arg = cur_arg + 1;
1180 return ACT_RET_PRS_OK;
1181}
1182
Christopher Faulet92d34fe2019-12-17 09:20:34 +01001183/* This function executes a replace-header or replace-value actions. It
1184 * builds a string in the trash from the specified format string. It finds
1185 * the action to be performed in <.action>, previously filled by function
1186 * parse_replace_header(). The replacement action is excuted by the function
1187 * http_action_replace_header(). On success, it returns ACT_RET_CONT. If an error
1188 * occurs while soft rewrites are enabled, the action is canceled, but the rule
1189 * processing continue. Otherwsize ACT_RET_ERR is returned.
1190 */
1191static enum act_return http_action_replace_header(struct act_rule *rule, struct proxy *px,
1192 struct session *sess, struct stream *s, int flags)
1193{
1194 struct htx *htx = htxbuf((rule->from == ACT_F_HTTP_REQ) ? &s->req.buf : &s->res.buf);
1195 enum act_return ret = ACT_RET_CONT;
1196 struct buffer *replace;
1197 int r;
1198
1199 replace = alloc_trash_chunk();
1200 if (!replace)
1201 goto fail_alloc;
1202
1203 replace->data = build_logline(s, replace->area, replace->size, &rule->arg.http.fmt);
1204
1205 r = http_replace_hdrs(s, htx, rule->arg.http.str, replace->area, rule->arg.http.re, (rule->action == 0));
1206 if (r == -1)
1207 goto fail_rewrite;
1208
1209 leave:
1210 free_trash_chunk(replace);
1211 return ret;
1212
1213 fail_alloc:
1214 if (!(s->flags & SF_ERR_MASK))
1215 s->flags |= SF_ERR_RESOURCE;
1216 ret = ACT_RET_ERR;
1217 goto leave;
1218
1219 fail_rewrite:
1220 _HA_ATOMIC_ADD(&sess->fe->fe_counters.failed_rewrites, 1);
1221 if (s->flags & SF_BE_ASSIGNED)
1222 _HA_ATOMIC_ADD(&s->be->be_counters.failed_rewrites, 1);
1223 if (sess->listener->counters)
1224 _HA_ATOMIC_ADD(&sess->listener->counters->failed_rewrites, 1);
1225 if (objt_server(s->target))
1226 _HA_ATOMIC_ADD(&__objt_server(s->target)->counters.failed_rewrites, 1);
1227
1228 if (!(s->txn->req.flags & HTTP_MSGF_SOFT_RW))
1229 ret = ACT_RET_ERR;
1230 goto leave;
1231}
1232
Christopher Faulet81e20172019-12-12 16:40:30 +01001233/* Parse a "replace-header" or "replace-value" actions. It takes an header name,
1234 * a regex and replacement string as arguments. It returns ACT_RET_PRS_OK on
1235 * success, ACT_RET_PRS_ERR on error.
1236 */
1237static enum act_parse_ret parse_http_replace_header(const char **args, int *orig_arg, struct proxy *px,
1238 struct act_rule *rule, char **err)
1239{
1240 int cap, cur_arg;
1241
Christopher Faulet92d34fe2019-12-17 09:20:34 +01001242 if (args[*orig_arg-1][8] == 'h')
1243 rule->action = 0; // replace-header
1244 else
1245 rule->action = 1; // replace-value
1246 rule->action_ptr = http_action_replace_header;
Christopher Faulet2eb53962020-01-14 14:47:34 +01001247 rule->release_ptr = release_http_action;
Christopher Faulet81e20172019-12-12 16:40:30 +01001248
1249 cur_arg = *orig_arg;
1250 if (!*args[cur_arg] || !*args[cur_arg+1] || !*args[cur_arg+2]) {
1251 memprintf(err, "expects exactly 3 arguments");
1252 return ACT_RET_PRS_ERR;
1253 }
1254
Christopher Faulet96bff762019-12-17 13:46:18 +01001255 rule->arg.http.str.ptr = strdup(args[cur_arg]);
1256 rule->arg.http.str.len = strlen(rule->arg.http.str.ptr);
1257 LIST_INIT(&rule->arg.http.fmt);
Christopher Faulet81e20172019-12-12 16:40:30 +01001258
1259 cur_arg++;
Christopher Faulet1337b322020-01-14 14:50:55 +01001260 if (!(rule->arg.http.re = regex_comp(args[cur_arg], 1, 1, err))) {
1261 free(rule->arg.http.str.ptr);
Christopher Faulet81e20172019-12-12 16:40:30 +01001262 return ACT_RET_PRS_ERR;
Christopher Faulet1337b322020-01-14 14:50:55 +01001263 }
Christopher Faulet81e20172019-12-12 16:40:30 +01001264
1265 if (rule->from == ACT_F_HTTP_REQ) {
1266 px->conf.args.ctx = ARGC_HRQ;
1267 cap = (px->cap & PR_CAP_FE) ? SMP_VAL_FE_HRQ_HDR : SMP_VAL_BE_HRQ_HDR;
1268 }
1269 else{
1270 px->conf.args.ctx = ARGC_HRS;
1271 cap = (px->cap & PR_CAP_BE) ? SMP_VAL_BE_HRS_HDR : SMP_VAL_FE_HRS_HDR;
1272 }
1273
1274 cur_arg++;
Christopher Faulet1337b322020-01-14 14:50:55 +01001275 if (!parse_logformat_string(args[cur_arg], px, &rule->arg.http.fmt, LOG_OPT_HTTP, cap, err)) {
1276 free(rule->arg.http.str.ptr);
1277 regex_free(rule->arg.http.re);
Christopher Faulet81e20172019-12-12 16:40:30 +01001278 return ACT_RET_PRS_ERR;
Christopher Faulet1337b322020-01-14 14:50:55 +01001279 }
Christopher Faulet81e20172019-12-12 16:40:30 +01001280
1281 free(px->conf.lfs_file);
1282 px->conf.lfs_file = strdup(px->conf.args.file);
1283 px->conf.lfs_line = px->conf.args.line;
1284
1285 *orig_arg = cur_arg + 1;
1286 return ACT_RET_PRS_OK;
1287}
1288
1289/* Parse a "del-header" action. It takes an header name as argument. It returns
1290 * ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
1291 */
1292static enum act_parse_ret parse_http_del_header(const char **args, int *orig_arg, struct proxy *px,
1293 struct act_rule *rule, char **err)
1294{
1295 int cur_arg;
1296
1297 rule->action = ACT_HTTP_DEL_HDR;
Christopher Faulet2eb53962020-01-14 14:47:34 +01001298 rule->release_ptr = release_http_action;
Christopher Faulet81e20172019-12-12 16:40:30 +01001299
1300 cur_arg = *orig_arg;
1301 if (!*args[cur_arg]) {
1302 memprintf(err, "expects exactly 1 arguments");
1303 return ACT_RET_PRS_ERR;
1304 }
1305
Christopher Faulet96bff762019-12-17 13:46:18 +01001306 rule->arg.http.str.ptr = strdup(args[cur_arg]);
1307 rule->arg.http.str.len = strlen(rule->arg.http.str.ptr);
Christopher Faulet81e20172019-12-12 16:40:30 +01001308
1309 px->conf.args.ctx = (rule->from == ACT_F_HTTP_REQ ? ARGC_HRQ : ARGC_HRS);
1310
1311 *orig_arg = cur_arg + 1;
1312 return ACT_RET_PRS_OK;
1313}
1314
Christopher Faulet2eb53962020-01-14 14:47:34 +01001315/* Release memory allocated by an http redirect action. */
1316static void release_http_redir(struct act_rule *rule)
1317{
1318 struct logformat_node *lf, *lfb;
1319 struct redirect_rule *redir;
1320
1321 redir = rule->arg.redir;
1322 LIST_DEL(&redir->list);
1323 if (redir->cond) {
1324 prune_acl_cond(redir->cond);
1325 free(redir->cond);
1326 }
1327 free(redir->rdr_str);
1328 free(redir->cookie_str);
1329 list_for_each_entry_safe(lf, lfb, &redir->rdr_fmt, list) {
1330 LIST_DEL(&lf->list);
1331 free(lf);
1332 }
1333 free(redir);
1334}
1335
Christopher Faulet81e20172019-12-12 16:40:30 +01001336/* Parse a "redirect" action. It returns ACT_RET_PRS_OK on success,
1337 * ACT_RET_PRS_ERR on error.
1338 */
1339static enum act_parse_ret parse_http_redirect(const char **args, int *orig_arg, struct proxy *px,
1340 struct act_rule *rule, char **err)
1341{
1342 struct redirect_rule *redir;
1343 int dir, cur_arg;
1344
1345 rule->action = ACT_HTTP_REDIR;
Christopher Faulet245cf792019-12-18 14:58:12 +01001346 rule->flags |= ACT_FLAG_FINAL;
Christopher Faulet2eb53962020-01-14 14:47:34 +01001347 rule->release_ptr = release_http_redir;
Christopher Faulet81e20172019-12-12 16:40:30 +01001348
1349 cur_arg = *orig_arg;
1350
1351 dir = (rule->from == ACT_F_HTTP_REQ ? 0 : 1);
1352 if ((redir = http_parse_redirect_rule(px->conf.args.file, px->conf.args.line, px, &args[cur_arg], err, 1, dir)) == NULL)
1353 return ACT_RET_PRS_ERR;
1354
1355 rule->arg.redir = redir;
1356 rule->cond = redir->cond;
1357 redir->cond = NULL;
1358
1359 /* skip all arguments */
1360 while (*args[cur_arg])
1361 cur_arg++;
1362
1363 *orig_arg = cur_arg;
1364 return ACT_RET_PRS_OK;
1365}
1366
Christopher Faulet046cf442019-12-17 15:45:23 +01001367/* This function executes a add-acl, del-acl, set-map or del-map actions. On
1368 * success, it returns ACT_RET_CONT. Otherwsize ACT_RET_ERR is returned.
1369 */
1370static enum act_return http_action_set_map(struct act_rule *rule, struct proxy *px,
1371 struct session *sess, struct stream *s, int flags)
1372{
1373 struct pat_ref *ref;
1374 struct buffer *key = NULL, *value = NULL;
1375 enum act_return ret = ACT_RET_CONT;
1376
1377 /* collect reference */
1378 ref = pat_ref_lookup(rule->arg.map.ref);
1379 if (!ref)
1380 goto leave;
1381
1382 /* allocate key */
1383 key = alloc_trash_chunk();
1384 if (!key)
1385 goto fail_alloc;
1386
1387 /* collect key */
1388 key->data = build_logline(s, key->area, key->size, &rule->arg.map.key);
1389 key->area[key->data] = '\0';
1390
1391 switch (rule->action) {
1392 case 0: // add-acl
1393 /* add entry only if it does not already exist */
1394 HA_SPIN_LOCK(PATREF_LOCK, &ref->lock);
1395 if (pat_ref_find_elt(ref, key->area) == NULL)
1396 pat_ref_add(ref, key->area, NULL, NULL);
1397 HA_SPIN_UNLOCK(PATREF_LOCK, &ref->lock);
1398 break;
1399
1400 case 1: // set-map
1401 /* allocate value */
1402 value = alloc_trash_chunk();
1403 if (!value)
1404 goto fail_alloc;
1405
1406 /* collect value */
1407 value->data = build_logline(s, value->area, value->size, &rule->arg.map.value);
1408 value->area[value->data] = '\0';
1409
1410 HA_SPIN_LOCK(PATREF_LOCK, &ref->lock);
1411 if (pat_ref_find_elt(ref, key->area) != NULL) {
1412 /* update entry if it exists */
1413 pat_ref_set(ref, key->area, value->area, NULL);
1414 }
1415 else {
1416 /* insert a new entry */
1417 pat_ref_add(ref, key->area, value->area, NULL);
1418 }
1419 HA_SPIN_UNLOCK(PATREF_LOCK, &ref->lock);
1420 break;
1421
1422 case 2: // del-acl
1423 case 3: // del-map
1424 /* returned code: 1=ok, 0=ko */
1425 HA_SPIN_LOCK(PATREF_LOCK, &ref->lock);
1426 pat_ref_delete(ref, key->area);
1427 HA_SPIN_UNLOCK(PATREF_LOCK, &ref->lock);
1428 break;
1429
1430 default:
1431 ret = ACT_RET_ERR;
1432 }
1433
1434
1435 leave:
1436 free_trash_chunk(key);
1437 free_trash_chunk(value);
1438 return ret;
1439
1440 fail_alloc:
1441 if (!(s->flags & SF_ERR_MASK))
1442 s->flags |= SF_ERR_RESOURCE;
1443 ret = ACT_RET_ERR;
1444 goto leave;
1445}
1446
Christopher Faulet2eb53962020-01-14 14:47:34 +01001447/* Release memory allocated by an http map/acl action. */
1448static void release_http_map(struct act_rule *rule)
1449{
1450 struct logformat_node *lf, *lfb;
1451
1452 free(rule->arg.map.ref);
1453 list_for_each_entry_safe(lf, lfb, &rule->arg.map.key, list) {
1454 LIST_DEL(&lf->list);
1455 release_sample_expr(lf->expr);
1456 free(lf->arg);
1457 free(lf);
1458 }
1459 if (rule->action == 1) {
1460 list_for_each_entry_safe(lf, lfb, &rule->arg.map.value, list) {
1461 LIST_DEL(&lf->list);
1462 release_sample_expr(lf->expr);
1463 free(lf->arg);
1464 free(lf);
1465 }
1466 }
1467}
1468
Christopher Faulet81e20172019-12-12 16:40:30 +01001469/* Parse a "add-acl", "del-acl", "set-map" or "del-map" actions. It takes one or
Christopher Faulet046cf442019-12-17 15:45:23 +01001470 * two log-format string as argument depending on the action. The action is
1471 * stored in <.action> as an int (0=add-acl, 1=set-map, 2=del-acl,
1472 * 3=del-map). It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
Christopher Faulet81e20172019-12-12 16:40:30 +01001473 */
1474static enum act_parse_ret parse_http_set_map(const char **args, int *orig_arg, struct proxy *px,
1475 struct act_rule *rule, char **err)
1476{
1477 int cap, cur_arg;
1478
Christopher Faulet046cf442019-12-17 15:45:23 +01001479 if (args[*orig_arg-1][0] == 'a') // add-acl
1480 rule->action = 0;
1481 else if (args[*orig_arg-1][0] == 's') // set-map
1482 rule->action = 1;
1483 else if (args[*orig_arg-1][4] == 'a') // del-acl
1484 rule->action = 2;
1485 else if (args[*orig_arg-1][4] == 'm') // del-map
1486 rule->action = 3;
1487 else {
1488 memprintf(err, "internal error: unhandled action '%s'", args[0]);
1489 return ACT_RET_PRS_ERR;
1490 }
1491 rule->action_ptr = http_action_set_map;
Christopher Faulet2eb53962020-01-14 14:47:34 +01001492 rule->release_ptr = release_http_map;
Christopher Faulet81e20172019-12-12 16:40:30 +01001493
1494 cur_arg = *orig_arg;
Christopher Faulet046cf442019-12-17 15:45:23 +01001495 if (rule->action == 1 && (!*args[cur_arg] || !*args[cur_arg+1])) {
1496 /* 2 args for set-map */
Christopher Faulet81e20172019-12-12 16:40:30 +01001497 memprintf(err, "expects exactly 2 arguments");
1498 return ACT_RET_PRS_ERR;
1499 }
1500 else if (!*args[cur_arg]) {
Christopher Faulet046cf442019-12-17 15:45:23 +01001501 /* only one arg for other actions */
Christopher Faulet81e20172019-12-12 16:40:30 +01001502 memprintf(err, "expects exactly 1 arguments");
1503 return ACT_RET_PRS_ERR;
1504 }
1505
1506 /*
1507 * '+ 8' for 'set-map(' (same for del-map)
1508 * '- 9' for 'set-map(' + trailing ')' (same for del-map)
1509 */
1510 rule->arg.map.ref = my_strndup(args[cur_arg-1] + 8, strlen(args[cur_arg-1]) - 9);
1511
1512 if (rule->from == ACT_F_HTTP_REQ) {
1513 px->conf.args.ctx = ARGC_HRQ;
1514 cap = (px->cap & PR_CAP_FE) ? SMP_VAL_FE_HRQ_HDR : SMP_VAL_BE_HRQ_HDR;
1515 }
1516 else{
1517 px->conf.args.ctx = ARGC_HRS;
1518 cap = (px->cap & PR_CAP_BE) ? SMP_VAL_BE_HRS_HDR : SMP_VAL_FE_HRS_HDR;
1519 }
1520
1521 /* key pattern */
1522 LIST_INIT(&rule->arg.map.key);
Christopher Faulet1337b322020-01-14 14:50:55 +01001523 if (!parse_logformat_string(args[cur_arg], px, &rule->arg.map.key, LOG_OPT_HTTP, cap, err)) {
1524 free(rule->arg.map.ref);
Christopher Faulet81e20172019-12-12 16:40:30 +01001525 return ACT_RET_PRS_ERR;
Christopher Faulet1337b322020-01-14 14:50:55 +01001526 }
Christopher Faulet81e20172019-12-12 16:40:30 +01001527
Christopher Faulet046cf442019-12-17 15:45:23 +01001528 if (rule->action == 1) {
Christopher Faulet81e20172019-12-12 16:40:30 +01001529 /* value pattern for set-map only */
1530 cur_arg++;
1531 LIST_INIT(&rule->arg.map.value);
Christopher Faulet1337b322020-01-14 14:50:55 +01001532 if (!parse_logformat_string(args[cur_arg], px, &rule->arg.map.value, LOG_OPT_HTTP, cap, err)) {
1533 free(rule->arg.map.ref);
Christopher Faulet81e20172019-12-12 16:40:30 +01001534 return ACT_RET_PRS_ERR;
Christopher Faulet1337b322020-01-14 14:50:55 +01001535 }
Christopher Faulet81e20172019-12-12 16:40:30 +01001536 }
1537
1538 free(px->conf.lfs_file);
1539 px->conf.lfs_file = strdup(px->conf.args.file);
1540 px->conf.lfs_line = px->conf.args.line;
1541
1542 *orig_arg = cur_arg + 1;
1543 return ACT_RET_PRS_OK;
1544}
1545
Christopher Fauletac98d812019-12-18 09:20:16 +01001546/* This function executes a track-sc* actions. On success, it returns
1547 * ACT_RET_CONT. Otherwsize ACT_RET_ERR is returned.
1548 */
1549static enum act_return http_action_track_sc(struct act_rule *rule, struct proxy *px,
1550 struct session *sess, struct stream *s, int flags)
1551{
1552 struct stktable *t;
1553 struct stksess *ts;
1554 struct stktable_key *key;
1555 void *ptr1, *ptr2, *ptr3, *ptr4;
1556 int opt;
1557
1558 ptr1 = ptr2 = ptr3 = ptr4 = NULL;
1559 opt = ((rule->from == ACT_F_HTTP_REQ) ? SMP_OPT_DIR_REQ : SMP_OPT_DIR_RES) | SMP_OPT_FINAL;
1560
1561 t = rule->arg.trk_ctr.table.t;
1562 key = stktable_fetch_key(t, s->be, sess, s, opt, rule->arg.trk_ctr.expr, NULL);
1563
1564 if (!key)
1565 goto end;
1566 ts = stktable_get_entry(t, key);
1567 if (!ts)
1568 goto end;
1569
1570 stream_track_stkctr(&s->stkctr[rule->action], t, ts);
1571
1572 /* let's count a new HTTP request as it's the first time we do it */
1573 ptr1 = stktable_data_ptr(t, ts, STKTABLE_DT_HTTP_REQ_CNT);
1574 ptr2 = stktable_data_ptr(t, ts, STKTABLE_DT_HTTP_REQ_RATE);
1575
1576 /* When the client triggers a 4xx from the server, it's most often due
1577 * to a missing object or permission. These events should be tracked
1578 * because if they happen often, it may indicate a brute force or a
1579 * vulnerability scan. Normally this is done when receiving the response
1580 * but here we're tracking after this ought to have been done so we have
1581 * to do it on purpose.
1582 */
1583 if (rule->from == ACT_F_HTTP_RES && (unsigned)(s->txn->status - 400) < 100) {
1584 ptr3 = stktable_data_ptr(t, ts, STKTABLE_DT_HTTP_ERR_CNT);
1585 ptr4 = stktable_data_ptr(t, ts, STKTABLE_DT_HTTP_ERR_RATE);
1586 }
1587
1588 if (ptr1 || ptr2 || ptr3 || ptr4) {
1589 HA_RWLOCK_WRLOCK(STK_SESS_LOCK, &ts->lock);
1590
1591 if (ptr1)
1592 stktable_data_cast(ptr1, http_req_cnt)++;
1593 if (ptr2)
1594 update_freq_ctr_period(&stktable_data_cast(ptr2, http_req_rate),
1595 t->data_arg[STKTABLE_DT_HTTP_REQ_RATE].u, 1);
1596 if (ptr3)
1597 stktable_data_cast(ptr3, http_err_cnt)++;
1598 if (ptr4)
1599 update_freq_ctr_period(&stktable_data_cast(ptr4, http_err_rate),
1600 t->data_arg[STKTABLE_DT_HTTP_ERR_RATE].u, 1);
1601
1602 HA_RWLOCK_WRUNLOCK(STK_SESS_LOCK, &ts->lock);
1603
1604 /* If data was modified, we need to touch to re-schedule sync */
1605 stktable_touch_local(t, ts, 0);
1606 }
1607
1608 stkctr_set_flags(&s->stkctr[rule->action], STKCTR_TRACK_CONTENT);
1609 if (sess->fe != s->be)
1610 stkctr_set_flags(&s->stkctr[rule->action], STKCTR_TRACK_BACKEND);
1611
1612 end:
1613 return ACT_RET_CONT;
1614}
Christopher Faulet81e20172019-12-12 16:40:30 +01001615
Christopher Faulet2eb53962020-01-14 14:47:34 +01001616static void release_http_track_sc(struct act_rule *rule)
1617{
1618 release_sample_expr(rule->arg.trk_ctr.expr);
1619}
1620
Christopher Faulet81e20172019-12-12 16:40:30 +01001621/* Parse a "track-sc*" actions. It returns ACT_RET_PRS_OK on success,
1622 * ACT_RET_PRS_ERR on error.
1623 */
1624static enum act_parse_ret parse_http_track_sc(const char **args, int *orig_arg, struct proxy *px,
1625 struct act_rule *rule, char **err)
1626{
1627 struct sample_expr *expr;
1628 unsigned int where;
1629 unsigned int tsc_num;
1630 const char *tsc_num_str;
1631 int cur_arg;
1632
1633 tsc_num_str = &args[*orig_arg-1][8];
1634 if (cfg_parse_track_sc_num(&tsc_num, tsc_num_str, tsc_num_str + strlen(tsc_num_str), err) == -1)
1635 return ACT_RET_PRS_ERR;
1636
1637 cur_arg = *orig_arg;
1638 expr = sample_parse_expr((char **)args, &cur_arg, px->conf.args.file, px->conf.args.line,
1639 err, &px->conf.args);
1640 if (!expr)
1641 return ACT_RET_PRS_ERR;
1642
1643 where = 0;
1644 if (px->cap & PR_CAP_FE)
1645 where |= (rule->from == ACT_F_HTTP_REQ ? SMP_VAL_FE_HRQ_HDR : SMP_VAL_FE_HRS_HDR);
1646 if (px->cap & PR_CAP_BE)
1647 where |= (rule->from == ACT_F_HTTP_REQ ? SMP_VAL_BE_HRQ_HDR : SMP_VAL_BE_HRS_HDR);
1648
1649 if (!(expr->fetch->val & where)) {
1650 memprintf(err, "fetch method '%s' extracts information from '%s', none of which is available here",
1651 args[cur_arg-1], sample_src_names(expr->fetch->use));
Christopher Faulet1337b322020-01-14 14:50:55 +01001652 release_sample_expr(expr);
Christopher Faulet81e20172019-12-12 16:40:30 +01001653 return ACT_RET_PRS_ERR;
1654 }
1655
1656 if (strcmp(args[cur_arg], "table") == 0) {
1657 cur_arg++;
1658 if (!*args[cur_arg]) {
1659 memprintf(err, "missing table name");
Christopher Faulet1337b322020-01-14 14:50:55 +01001660 release_sample_expr(expr);
Christopher Faulet81e20172019-12-12 16:40:30 +01001661 return ACT_RET_PRS_ERR;
1662 }
1663
1664 /* we copy the table name for now, it will be resolved later */
1665 rule->arg.trk_ctr.table.n = strdup(args[cur_arg]);
1666 cur_arg++;
1667 }
1668
Christopher Fauletac98d812019-12-18 09:20:16 +01001669 rule->action = tsc_num;
Christopher Faulet81e20172019-12-12 16:40:30 +01001670 rule->arg.trk_ctr.expr = expr;
Christopher Fauletac98d812019-12-18 09:20:16 +01001671 rule->action_ptr = http_action_track_sc;
Christopher Faulet2eb53962020-01-14 14:47:34 +01001672 rule->release_ptr = release_http_track_sc;
Christopher Faulet81e20172019-12-12 16:40:30 +01001673 rule->check_ptr = check_trk_action;
1674
1675 *orig_arg = cur_arg;
1676 return ACT_RET_PRS_OK;
1677}
1678
Christopher Faulet46f95542019-12-20 10:07:22 +01001679/* This function executes a strict-mode actions. On success, it always returns
1680 * ACT_RET_CONT
1681 */
1682static enum act_return http_action_strict_mode(struct act_rule *rule, struct proxy *px,
1683 struct session *sess, struct stream *s, int flags)
1684{
1685 struct http_msg *msg = ((rule->from == ACT_F_HTTP_REQ) ? &s->txn->req : &s->txn->rsp);
1686
1687 if (rule->action == 0) // strict-mode on
1688 msg->flags &= ~HTTP_MSGF_SOFT_RW;
1689 else // strict-mode off
1690 msg->flags |= HTTP_MSGF_SOFT_RW;
1691 return ACT_RET_CONT;
1692}
1693
1694/* Parse a "strict-mode" action. It returns ACT_RET_PRS_OK on success,
1695 * ACT_RET_PRS_ERR on error.
1696 */
1697static enum act_parse_ret parse_http_strict_mode(const char **args, int *orig_arg, struct proxy *px,
1698 struct act_rule *rule, char **err)
1699{
1700 int cur_arg;
1701
1702
1703 cur_arg = *orig_arg;
1704 if (!*args[cur_arg]) {
1705 memprintf(err, "expects exactly 1 arguments");
1706 return ACT_RET_PRS_ERR;
1707 }
1708
1709 if (strcasecmp(args[cur_arg], "on") == 0)
1710 rule->action = 0; // strict-mode on
1711 else if (strcasecmp(args[cur_arg], "off") == 0)
1712 rule->action = 1; // strict-mode off
1713 else {
1714 memprintf(err, "Unexpected value '%s'. Only 'on' and 'off' are supported", args[cur_arg]);
1715 return ACT_RET_PRS_ERR;
1716 }
1717 rule->action_ptr = http_action_strict_mode;
1718
1719 *orig_arg = cur_arg + 1;
1720 return ACT_RET_PRS_OK;
1721}
1722
Willy Tarreau79e57332018-10-02 16:01:16 +02001723/************************************************************************/
1724/* All supported http-request action keywords must be declared here. */
1725/************************************************************************/
1726
1727static struct action_kw_list http_req_actions = {
1728 .kw = {
Christopher Faulet81e20172019-12-12 16:40:30 +01001729 { "add-acl", parse_http_set_map, 1 },
1730 { "add-header", parse_http_set_header, 0 },
1731 { "allow", parse_http_allow, 0 },
1732 { "auth", parse_http_auth, 0 },
1733 { "capture", parse_http_req_capture, 0 },
1734 { "del-acl", parse_http_set_map, 1 },
1735 { "del-header", parse_http_del_header, 0 },
1736 { "del-map", parse_http_set_map, 1 },
1737 { "deny", parse_http_req_deny, 0 },
1738 { "disable-l7-retry", parse_http_req_disable_l7_retry, 0 },
1739 { "early-hint", parse_http_set_header, 0 },
1740 { "redirect", parse_http_redirect, 0 },
1741 { "reject", parse_http_action_reject, 0 },
1742 { "replace-header", parse_http_replace_header, 0 },
1743 { "replace-path", parse_replace_uri, 0 },
1744 { "replace-uri", parse_replace_uri, 0 },
1745 { "replace-value", parse_http_replace_header, 0 },
1746 { "set-header", parse_http_set_header, 0 },
1747 { "set-log-level", parse_http_set_log_level, 0 },
1748 { "set-map", parse_http_set_map, 1 },
1749 { "set-method", parse_set_req_line, 0 },
1750 { "set-mark", parse_http_set_mark, 0 },
1751 { "set-nice", parse_http_set_nice, 0 },
1752 { "set-path", parse_set_req_line, 0 },
1753 { "set-query", parse_set_req_line, 0 },
1754 { "set-tos", parse_http_set_tos, 0 },
1755 { "set-uri", parse_set_req_line, 0 },
Christopher Faulet46f95542019-12-20 10:07:22 +01001756 { "strict-mode", parse_http_strict_mode, 0 },
Christopher Faulet81e20172019-12-12 16:40:30 +01001757 { "tarpit", parse_http_req_deny, 0 },
1758 { "track-sc", parse_http_track_sc, 1 },
Willy Tarreau79e57332018-10-02 16:01:16 +02001759 { NULL, NULL }
1760 }
1761};
1762
Willy Tarreau0108d902018-11-25 19:14:37 +01001763INITCALL1(STG_REGISTER, http_req_keywords_register, &http_req_actions);
1764
Willy Tarreau79e57332018-10-02 16:01:16 +02001765static struct action_kw_list http_res_actions = {
1766 .kw = {
Christopher Faulet81e20172019-12-12 16:40:30 +01001767 { "add-acl", parse_http_set_map, 1 },
1768 { "add-header", parse_http_set_header, 0 },
1769 { "allow", parse_http_allow, 0 },
1770 { "capture", parse_http_res_capture, 0 },
1771 { "del-acl", parse_http_set_map, 1 },
1772 { "del-header", parse_http_del_header, 0 },
1773 { "del-map", parse_http_set_map, 1 },
1774 { "deny", parse_http_res_deny, 0 },
1775 { "redirect", parse_http_redirect, 0 },
1776 { "replace-header", parse_http_replace_header, 0 },
1777 { "replace-value", parse_http_replace_header, 0 },
1778 { "set-header", parse_http_set_header, 0 },
1779 { "set-log-level", parse_http_set_log_level, 0 },
1780 { "set-map", parse_http_set_map, 1 },
1781 { "set-mark", parse_http_set_mark, 0 },
1782 { "set-nice", parse_http_set_nice, 0 },
1783 { "set-status", parse_http_set_status, 0 },
1784 { "set-tos", parse_http_set_tos, 0 },
Christopher Faulet46f95542019-12-20 10:07:22 +01001785 { "strict-mode", parse_http_strict_mode, 0 },
Christopher Faulet81e20172019-12-12 16:40:30 +01001786 { "track-sc", parse_http_track_sc, 1 },
Willy Tarreau79e57332018-10-02 16:01:16 +02001787 { NULL, NULL }
1788 }
1789};
1790
Willy Tarreau0108d902018-11-25 19:14:37 +01001791INITCALL1(STG_REGISTER, http_res_keywords_register, &http_res_actions);
Willy Tarreau79e57332018-10-02 16:01:16 +02001792
1793/*
1794 * Local variables:
1795 * c-indent-level: 8
1796 * c-basic-offset: 8
1797 * End:
1798 */