blob: 163213099e378be12f260017c04eed344b15e486 [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
Christopher Fauletc20b3712020-01-27 15:51:56 +0100338 LIST_INIT(&rule->arg.http.fmt);
Willy Tarreau79e57332018-10-02 16:01:16 +0200339 return ACT_RET_PRS_OK;
340}
341
342/* This function executes the "reject" HTTP action. It clears the request and
343 * response buffer without sending any response. It can be useful as an HTTP
344 * alternative to the silent-drop action to defend against DoS attacks, and may
345 * also be used with HTTP/2 to close a connection instead of just a stream.
346 * The txn status is unchanged, indicating no response was sent. The termination
Christopher Faulet8f1aa772019-07-04 11:27:15 +0200347 * flags will indicate "PR". It always returns ACT_RET_DONE.
Willy Tarreau79e57332018-10-02 16:01:16 +0200348 */
349static enum act_return http_action_reject(struct act_rule *rule, struct proxy *px,
350 struct session *sess, struct stream *s, int flags)
351{
Willy Tarreau0f9cd7b2019-01-31 19:02:43 +0100352 si_must_kill_conn(chn_prod(&s->req));
Willy Tarreau79e57332018-10-02 16:01:16 +0200353 channel_abort(&s->req);
354 channel_abort(&s->res);
355 s->req.analysers = 0;
356 s->res.analysers = 0;
357
Olivier Houcharda798bf52019-03-08 18:52:00 +0100358 _HA_ATOMIC_ADD(&s->be->be_counters.denied_req, 1);
359 _HA_ATOMIC_ADD(&sess->fe->fe_counters.denied_req, 1);
Willy Tarreau79e57332018-10-02 16:01:16 +0200360 if (sess->listener && sess->listener->counters)
Olivier Houcharda798bf52019-03-08 18:52:00 +0100361 _HA_ATOMIC_ADD(&sess->listener->counters->denied_req, 1);
Willy Tarreau79e57332018-10-02 16:01:16 +0200362
363 if (!(s->flags & SF_ERR_MASK))
364 s->flags |= SF_ERR_PRXCOND;
365 if (!(s->flags & SF_FINST_MASK))
366 s->flags |= SF_FINST_R;
367
Christopher Faulet8f1aa772019-07-04 11:27:15 +0200368 return ACT_RET_DONE;
Willy Tarreau79e57332018-10-02 16:01:16 +0200369}
370
371/* parse the "reject" action:
372 * This action takes no argument and returns ACT_RET_PRS_OK on success,
373 * ACT_RET_PRS_ERR on error.
374 */
375static enum act_parse_ret parse_http_action_reject(const char **args, int *orig_arg, struct proxy *px,
376 struct act_rule *rule, char **err)
377{
378 rule->action = ACT_CUSTOM;
379 rule->action_ptr = http_action_reject;
380 return ACT_RET_PRS_OK;
381}
382
Olivier Houchard602bf7d2019-05-10 13:59:15 +0200383/* This function executes the "disable-l7-retry" HTTP action.
384 * It disables L7 retries (all retry except for a connection failure). This
385 * can be useful for example to avoid retrying on POST requests.
386 * It just removes the L7 retry flag on the stream_interface, and always
387 * return ACT_RET_CONT;
388 */
389static enum act_return http_req_disable_l7_retry(struct act_rule *rule, struct proxy *px,
390 struct session *sess, struct stream *s, int flags)
391{
392 struct stream_interface *si = &s->si[1];
393
394 /* In theory, the SI_FL_L7_RETRY flags isn't set at this point, but
395 * let's be future-proof and remove it anyway.
396 */
397 si->flags &= ~SI_FL_L7_RETRY;
398 si->flags |= SI_FL_D_L7_RETRY;
399 return ACT_RET_CONT;
400}
401
402/* parse the "disable-l7-retry" action:
403 * This action takes no argument and returns ACT_RET_PRS_OK on success,
404 * ACT_RET_PRS_ERR on error.
405 */
406static enum act_parse_ret parse_http_req_disable_l7_retry(const char **args,
407 int *orig_args, struct proxy *px,
408 struct act_rule *rule, char **err)
409{
410 rule->action = ACT_CUSTOM;
411 rule->action_ptr = http_req_disable_l7_retry;
412 return ACT_RET_PRS_OK;
413}
414
Willy Tarreau79e57332018-10-02 16:01:16 +0200415/* This function executes the "capture" action. It executes a fetch expression,
416 * turns the result into a string and puts it in a capture slot. It always
417 * returns 1. If an error occurs the action is cancelled, but the rule
418 * processing continues.
419 */
420static enum act_return http_action_req_capture(struct act_rule *rule, struct proxy *px,
421 struct session *sess, struct stream *s, int flags)
422{
423 struct sample *key;
424 struct cap_hdr *h = rule->arg.cap.hdr;
425 char **cap = s->req_cap;
426 int len;
427
428 key = sample_fetch_as_type(s->be, sess, s, SMP_OPT_DIR_REQ|SMP_OPT_FINAL, rule->arg.cap.expr, SMP_T_STR);
429 if (!key)
430 return ACT_RET_CONT;
431
432 if (cap[h->index] == NULL)
433 cap[h->index] = pool_alloc(h->pool);
434
435 if (cap[h->index] == NULL) /* no more capture memory */
436 return ACT_RET_CONT;
437
438 len = key->data.u.str.data;
439 if (len > h->len)
440 len = h->len;
441
442 memcpy(cap[h->index], key->data.u.str.area, len);
443 cap[h->index][len] = 0;
444 return ACT_RET_CONT;
445}
446
447/* This function executes the "capture" action and store the result in a
448 * capture slot if exists. It executes a fetch expression, turns the result
449 * into a string and puts it in a capture slot. It always returns 1. If an
450 * error occurs the action is cancelled, but the rule processing continues.
451 */
452static enum act_return http_action_req_capture_by_id(struct act_rule *rule, struct proxy *px,
453 struct session *sess, struct stream *s, int flags)
454{
455 struct sample *key;
456 struct cap_hdr *h;
457 char **cap = s->req_cap;
458 struct proxy *fe = strm_fe(s);
459 int len;
460 int i;
461
462 /* Look for the original configuration. */
463 for (h = fe->req_cap, i = fe->nb_req_cap - 1;
464 h != NULL && i != rule->arg.capid.idx ;
465 i--, h = h->next);
466 if (!h)
467 return ACT_RET_CONT;
468
469 key = sample_fetch_as_type(s->be, sess, s, SMP_OPT_DIR_REQ|SMP_OPT_FINAL, rule->arg.capid.expr, SMP_T_STR);
470 if (!key)
471 return ACT_RET_CONT;
472
473 if (cap[h->index] == NULL)
474 cap[h->index] = pool_alloc(h->pool);
475
476 if (cap[h->index] == NULL) /* no more capture memory */
477 return ACT_RET_CONT;
478
479 len = key->data.u.str.data;
480 if (len > h->len)
481 len = h->len;
482
483 memcpy(cap[h->index], key->data.u.str.area, len);
484 cap[h->index][len] = 0;
485 return ACT_RET_CONT;
486}
487
488/* Check an "http-request capture" action.
489 *
490 * The function returns 1 in success case, otherwise, it returns 0 and err is
491 * filled.
492 */
493static int check_http_req_capture(struct act_rule *rule, struct proxy *px, char **err)
494{
495 if (rule->action_ptr != http_action_req_capture_by_id)
496 return 1;
497
Baptiste Assmann19a69b32020-01-16 14:34:22 +0100498 /* capture slots can only be declared in frontends, so we can't check their
499 * existence in backends at configuration parsing step
500 */
501 if (px->cap & PR_CAP_FE && rule->arg.capid.idx >= px->nb_req_cap) {
Willy Tarreau79e57332018-10-02 16:01:16 +0200502 memprintf(err, "unable to find capture id '%d' referenced by http-request capture rule",
503 rule->arg.capid.idx);
504 return 0;
505 }
506
507 return 1;
508}
509
Christopher Faulet2eb53962020-01-14 14:47:34 +0100510/* Release memory allocate by an http capture action */
511static void release_http_capture(struct act_rule *rule)
512{
513 if (rule->action_ptr == http_action_req_capture)
514 release_sample_expr(rule->arg.cap.expr);
515 else
516 release_sample_expr(rule->arg.capid.expr);
517}
518
Willy Tarreau79e57332018-10-02 16:01:16 +0200519/* parse an "http-request capture" action. It takes a single argument which is
520 * a sample fetch expression. It stores the expression into arg->act.p[0] and
521 * the allocated hdr_cap struct or the preallocated "id" into arg->act.p[1].
522 * It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
523 */
524static enum act_parse_ret parse_http_req_capture(const char **args, int *orig_arg, struct proxy *px,
525 struct act_rule *rule, char **err)
526{
527 struct sample_expr *expr;
528 struct cap_hdr *hdr;
529 int cur_arg;
530 int len = 0;
531
532 for (cur_arg = *orig_arg; cur_arg < *orig_arg + 3 && *args[cur_arg]; cur_arg++)
533 if (strcmp(args[cur_arg], "if") == 0 ||
534 strcmp(args[cur_arg], "unless") == 0)
535 break;
536
537 if (cur_arg < *orig_arg + 3) {
538 memprintf(err, "expects <expression> [ 'len' <length> | id <idx> ]");
539 return ACT_RET_PRS_ERR;
540 }
541
542 cur_arg = *orig_arg;
543 expr = sample_parse_expr((char **)args, &cur_arg, px->conf.args.file, px->conf.args.line, err, &px->conf.args);
544 if (!expr)
545 return ACT_RET_PRS_ERR;
546
547 if (!(expr->fetch->val & SMP_VAL_FE_HRQ_HDR)) {
548 memprintf(err,
549 "fetch method '%s' extracts information from '%s', none of which is available here",
550 args[cur_arg-1], sample_src_names(expr->fetch->use));
Christopher Faulet1337b322020-01-14 14:50:55 +0100551 release_sample_expr(expr);
Willy Tarreau79e57332018-10-02 16:01:16 +0200552 return ACT_RET_PRS_ERR;
553 }
554
555 if (!args[cur_arg] || !*args[cur_arg]) {
556 memprintf(err, "expects 'len or 'id'");
Christopher Faulet1337b322020-01-14 14:50:55 +0100557 release_sample_expr(expr);
Willy Tarreau79e57332018-10-02 16:01:16 +0200558 return ACT_RET_PRS_ERR;
559 }
560
561 if (strcmp(args[cur_arg], "len") == 0) {
562 cur_arg++;
563
564 if (!(px->cap & PR_CAP_FE)) {
565 memprintf(err, "proxy '%s' has no frontend capability", px->id);
Christopher Faulet1337b322020-01-14 14:50:55 +0100566 release_sample_expr(expr);
Willy Tarreau79e57332018-10-02 16:01:16 +0200567 return ACT_RET_PRS_ERR;
568 }
569
570 px->conf.args.ctx = ARGC_CAP;
571
572 if (!args[cur_arg]) {
573 memprintf(err, "missing length value");
Christopher Faulet1337b322020-01-14 14:50:55 +0100574 release_sample_expr(expr);
Willy Tarreau79e57332018-10-02 16:01:16 +0200575 return ACT_RET_PRS_ERR;
576 }
577 /* we copy the table name for now, it will be resolved later */
578 len = atoi(args[cur_arg]);
579 if (len <= 0) {
580 memprintf(err, "length must be > 0");
Christopher Faulet1337b322020-01-14 14:50:55 +0100581 release_sample_expr(expr);
Willy Tarreau79e57332018-10-02 16:01:16 +0200582 return ACT_RET_PRS_ERR;
583 }
584 cur_arg++;
585
Willy Tarreau79e57332018-10-02 16:01:16 +0200586 hdr = calloc(1, sizeof(*hdr));
587 hdr->next = px->req_cap;
588 hdr->name = NULL; /* not a header capture */
589 hdr->namelen = 0;
590 hdr->len = len;
591 hdr->pool = create_pool("caphdr", hdr->len + 1, MEM_F_SHARED);
592 hdr->index = px->nb_req_cap++;
593
594 px->req_cap = hdr;
595 px->to_log |= LW_REQHDR;
596
597 rule->action = ACT_CUSTOM;
598 rule->action_ptr = http_action_req_capture;
Christopher Faulet2eb53962020-01-14 14:47:34 +0100599 rule->release_ptr = release_http_capture;
Willy Tarreau79e57332018-10-02 16:01:16 +0200600 rule->arg.cap.expr = expr;
601 rule->arg.cap.hdr = hdr;
602 }
603
604 else if (strcmp(args[cur_arg], "id") == 0) {
605 int id;
606 char *error;
607
608 cur_arg++;
609
610 if (!args[cur_arg]) {
611 memprintf(err, "missing id value");
Christopher Faulet1337b322020-01-14 14:50:55 +0100612 release_sample_expr(expr);
Willy Tarreau79e57332018-10-02 16:01:16 +0200613 return ACT_RET_PRS_ERR;
614 }
615
616 id = strtol(args[cur_arg], &error, 10);
617 if (*error != '\0') {
618 memprintf(err, "cannot parse id '%s'", args[cur_arg]);
Christopher Faulet1337b322020-01-14 14:50:55 +0100619 release_sample_expr(expr);
Willy Tarreau79e57332018-10-02 16:01:16 +0200620 return ACT_RET_PRS_ERR;
621 }
622 cur_arg++;
623
624 px->conf.args.ctx = ARGC_CAP;
625
626 rule->action = ACT_CUSTOM;
627 rule->action_ptr = http_action_req_capture_by_id;
628 rule->check_ptr = check_http_req_capture;
Christopher Faulet2eb53962020-01-14 14:47:34 +0100629 rule->release_ptr = release_http_capture;
Willy Tarreau79e57332018-10-02 16:01:16 +0200630 rule->arg.capid.expr = expr;
631 rule->arg.capid.idx = id;
632 }
633
634 else {
635 memprintf(err, "expects 'len' or 'id', found '%s'", args[cur_arg]);
Christopher Faulet1337b322020-01-14 14:50:55 +0100636 release_sample_expr(expr);
Willy Tarreau79e57332018-10-02 16:01:16 +0200637 return ACT_RET_PRS_ERR;
638 }
639
640 *orig_arg = cur_arg;
641 return ACT_RET_PRS_OK;
642}
643
644/* This function executes the "capture" action and store the result in a
645 * capture slot if exists. It executes a fetch expression, turns the result
646 * into a string and puts it in a capture slot. It always returns 1. If an
647 * error occurs the action is cancelled, but the rule processing continues.
648 */
649static enum act_return http_action_res_capture_by_id(struct act_rule *rule, struct proxy *px,
650 struct session *sess, struct stream *s, int flags)
651{
652 struct sample *key;
653 struct cap_hdr *h;
654 char **cap = s->res_cap;
655 struct proxy *fe = strm_fe(s);
656 int len;
657 int i;
658
659 /* Look for the original configuration. */
660 for (h = fe->rsp_cap, i = fe->nb_rsp_cap - 1;
661 h != NULL && i != rule->arg.capid.idx ;
662 i--, h = h->next);
663 if (!h)
664 return ACT_RET_CONT;
665
666 key = sample_fetch_as_type(s->be, sess, s, SMP_OPT_DIR_RES|SMP_OPT_FINAL, rule->arg.capid.expr, SMP_T_STR);
667 if (!key)
668 return ACT_RET_CONT;
669
670 if (cap[h->index] == NULL)
671 cap[h->index] = pool_alloc(h->pool);
672
673 if (cap[h->index] == NULL) /* no more capture memory */
674 return ACT_RET_CONT;
675
676 len = key->data.u.str.data;
677 if (len > h->len)
678 len = h->len;
679
680 memcpy(cap[h->index], key->data.u.str.area, len);
681 cap[h->index][len] = 0;
682 return ACT_RET_CONT;
683}
684
685/* Check an "http-response capture" action.
686 *
687 * The function returns 1 in success case, otherwise, it returns 0 and err is
688 * filled.
689 */
690static int check_http_res_capture(struct act_rule *rule, struct proxy *px, char **err)
691{
692 if (rule->action_ptr != http_action_res_capture_by_id)
693 return 1;
694
695 if (rule->arg.capid.idx >= px->nb_rsp_cap) {
696 memprintf(err, "unable to find capture id '%d' referenced by http-response capture rule",
697 rule->arg.capid.idx);
698 return 0;
699 }
700
701 return 1;
702}
703
704/* parse an "http-response capture" action. It takes a single argument which is
705 * a sample fetch expression. It stores the expression into arg->act.p[0] and
706 * the allocated hdr_cap struct od the preallocated id into arg->act.p[1].
707 * It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
708 */
709static enum act_parse_ret parse_http_res_capture(const char **args, int *orig_arg, struct proxy *px,
710 struct act_rule *rule, char **err)
711{
712 struct sample_expr *expr;
713 int cur_arg;
714 int id;
715 char *error;
716
717 for (cur_arg = *orig_arg; cur_arg < *orig_arg + 3 && *args[cur_arg]; cur_arg++)
718 if (strcmp(args[cur_arg], "if") == 0 ||
719 strcmp(args[cur_arg], "unless") == 0)
720 break;
721
722 if (cur_arg < *orig_arg + 3) {
723 memprintf(err, "expects <expression> id <idx>");
724 return ACT_RET_PRS_ERR;
725 }
726
727 cur_arg = *orig_arg;
728 expr = sample_parse_expr((char **)args, &cur_arg, px->conf.args.file, px->conf.args.line, err, &px->conf.args);
729 if (!expr)
730 return ACT_RET_PRS_ERR;
731
732 if (!(expr->fetch->val & SMP_VAL_FE_HRS_HDR)) {
733 memprintf(err,
734 "fetch method '%s' extracts information from '%s', none of which is available here",
735 args[cur_arg-1], sample_src_names(expr->fetch->use));
Christopher Faulet1337b322020-01-14 14:50:55 +0100736 release_sample_expr(expr);
Willy Tarreau79e57332018-10-02 16:01:16 +0200737 return ACT_RET_PRS_ERR;
738 }
739
740 if (!args[cur_arg] || !*args[cur_arg]) {
741 memprintf(err, "expects 'id'");
Christopher Faulet1337b322020-01-14 14:50:55 +0100742 release_sample_expr(expr);
Willy Tarreau79e57332018-10-02 16:01:16 +0200743 return ACT_RET_PRS_ERR;
744 }
745
746 if (strcmp(args[cur_arg], "id") != 0) {
747 memprintf(err, "expects 'id', found '%s'", args[cur_arg]);
Christopher Faulet1337b322020-01-14 14:50:55 +0100748 release_sample_expr(expr);
Willy Tarreau79e57332018-10-02 16:01:16 +0200749 return ACT_RET_PRS_ERR;
750 }
751
752 cur_arg++;
753
754 if (!args[cur_arg]) {
755 memprintf(err, "missing id value");
Christopher Faulet1337b322020-01-14 14:50:55 +0100756 release_sample_expr(expr);
Willy Tarreau79e57332018-10-02 16:01:16 +0200757 return ACT_RET_PRS_ERR;
758 }
759
760 id = strtol(args[cur_arg], &error, 10);
761 if (*error != '\0') {
762 memprintf(err, "cannot parse id '%s'", args[cur_arg]);
Christopher Faulet1337b322020-01-14 14:50:55 +0100763 release_sample_expr(expr);
Willy Tarreau79e57332018-10-02 16:01:16 +0200764 return ACT_RET_PRS_ERR;
765 }
766 cur_arg++;
767
768 px->conf.args.ctx = ARGC_CAP;
769
770 rule->action = ACT_CUSTOM;
771 rule->action_ptr = http_action_res_capture_by_id;
772 rule->check_ptr = check_http_res_capture;
Christopher Faulet2eb53962020-01-14 14:47:34 +0100773 rule->release_ptr = release_http_capture;
Willy Tarreau79e57332018-10-02 16:01:16 +0200774 rule->arg.capid.expr = expr;
775 rule->arg.capid.idx = id;
776
777 *orig_arg = cur_arg;
778 return ACT_RET_PRS_OK;
779}
780
Christopher Faulet81e20172019-12-12 16:40:30 +0100781/* Parse a "allow" action for a request or a response rule. It takes no argument. It
782 * returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
783 */
784static enum act_parse_ret parse_http_allow(const char **args, int *orig_arg, struct proxy *px,
785 struct act_rule *rule, char **err)
786{
787 rule->action = ACT_ACTION_ALLOW;
Christopher Faulet245cf792019-12-18 14:58:12 +0100788 rule->flags |= ACT_FLAG_FINAL;
Christopher Faulet81e20172019-12-12 16:40:30 +0100789 return ACT_RET_PRS_OK;
790}
791
Christopher Faulet554c0eb2020-01-14 12:00:28 +0100792/* Check an "http-request deny" action when an http-errors section is referenced.
793 *
794 * The function returns 1 in success case, otherwise, it returns 0 and err is
795 * filled.
796 */
797static int check_http_deny_action(struct act_rule *rule, struct proxy *px, char **err)
798{
799 struct http_errors *http_errs;
800 int status = (intptr_t)(rule->arg.act.p[0]);
801 int ret = 1;
802
803 list_for_each_entry(http_errs, &http_errors_list, list) {
804 if (strcmp(http_errs->id, (char *)rule->arg.act.p[1]) == 0) {
805 free(rule->arg.act.p[1]);
806 rule->arg.http_deny.status = status;
807 rule->arg.http_deny.errmsg = http_errs->errmsg[http_get_status_idx(status)];
808 if (!rule->arg.http_deny.errmsg)
809 ha_warning("Proxy '%s': status '%d' referenced by http deny rule "
810 "not declared in http-errors section '%s'.\n",
811 px->id, status, http_errs->id);
812 break;
813 }
814 }
815
816 if (&http_errs->list == &http_errors_list) {
817 memprintf(err, "unknown http-errors section '%s' referenced by http deny rule",
818 (char *)rule->arg.act.p[1]);
819 free(rule->arg.act.p[1]);
820 ret = 0;
821 }
822
823 return ret;
824}
825
Christopher Faulete0fca292020-01-13 21:49:03 +0100826/* Parse "deny" or "tarpit" actions for a request rule or "deny" action for a
Christopher Faulet554c0eb2020-01-14 12:00:28 +0100827 * response rule. It may take optional arguments to define the status code, the
828 * error file or the http-errors section to use. It returns ACT_RET_PRS_OK on
829 * success, ACT_RET_PRS_ERR on error.
Christopher Faulet81e20172019-12-12 16:40:30 +0100830 */
Christopher Faulete0fca292020-01-13 21:49:03 +0100831static enum act_parse_ret parse_http_deny(const char **args, int *orig_arg, struct proxy *px,
832 struct act_rule *rule, char **err)
Christopher Faulet81e20172019-12-12 16:40:30 +0100833{
Christopher Faulet554c0eb2020-01-14 12:00:28 +0100834 int default_status, status, hc, cur_arg;
835
Christopher Faulet81e20172019-12-12 16:40:30 +0100836
837 cur_arg = *orig_arg;
Christopher Faulete0fca292020-01-13 21:49:03 +0100838 if (rule->from == ACT_F_HTTP_REQ) {
839 if (!strcmp(args[cur_arg-1], "tarpit")) {
840 rule->action = ACT_HTTP_REQ_TARPIT;
Christopher Faulet554c0eb2020-01-14 12:00:28 +0100841 default_status = status = 500;
Christopher Faulet81e20172019-12-12 16:40:30 +0100842 }
Christopher Faulete0fca292020-01-13 21:49:03 +0100843 else {
844 rule->action = ACT_ACTION_DENY;
Christopher Faulet554c0eb2020-01-14 12:00:28 +0100845 default_status = status = 403;
Christopher Faulet81e20172019-12-12 16:40:30 +0100846 }
Christopher Faulet81e20172019-12-12 16:40:30 +0100847 }
Christopher Faulete0fca292020-01-13 21:49:03 +0100848 else {
Christopher Faulet554c0eb2020-01-14 12:00:28 +0100849 rule->action = ACT_ACTION_DENY;
850 default_status = status = 502;
Christopher Faulete0fca292020-01-13 21:49:03 +0100851 }
Christopher Faulet245cf792019-12-18 14:58:12 +0100852 rule->flags |= ACT_FLAG_FINAL;
Christopher Faulet040c8cd2020-01-13 16:43:45 +0100853
854 if (strcmp(args[cur_arg], "deny_status") == 0) {
855 cur_arg++;
856 if (!*args[cur_arg]) {
Christopher Faulet554c0eb2020-01-14 12:00:28 +0100857 memprintf(err, "'%s' expects <status_code> as argument", args[cur_arg-1]);
Christopher Faulet040c8cd2020-01-13 16:43:45 +0100858 return ACT_RET_PRS_ERR;
859 }
860
Christopher Faulet554c0eb2020-01-14 12:00:28 +0100861 status = atol(args[cur_arg]);
Christopher Faulet040c8cd2020-01-13 16:43:45 +0100862 cur_arg++;
863 for (hc = 0; hc < HTTP_ERR_SIZE; hc++) {
Christopher Faulet554c0eb2020-01-14 12:00:28 +0100864 if (http_err_codes[hc] == status)
Christopher Faulet040c8cd2020-01-13 16:43:45 +0100865 break;
Christopher Faulet040c8cd2020-01-13 16:43:45 +0100866 }
Christopher Faulet554c0eb2020-01-14 12:00:28 +0100867 if (hc >= HTTP_ERR_SIZE) {
868 memprintf(err, "status code '%d' not handled, using default code '%d'",
869 status, default_status);
870 status = default_status;
871 hc = http_get_status_idx(status);
872 }
Christopher Faulet040c8cd2020-01-13 16:43:45 +0100873 }
874
Christopher Faulet554c0eb2020-01-14 12:00:28 +0100875 if (strcmp(args[cur_arg], "errorfile") == 0) {
876 cur_arg++;
877 if (!*args[cur_arg]) {
878 memprintf(err, "'%s' expects <file> as argument", args[cur_arg-1]);
879 return ACT_RET_PRS_ERR;
880 }
881
882 rule->arg.http_deny.errmsg = http_load_errorfile(args[cur_arg], err);
883 if (!rule->arg.http_deny.errmsg)
884 return ACT_RET_PRS_ERR;
885 cur_arg++;
886 }
887 else if (strcmp(args[cur_arg], "errorfiles") == 0) {
888 cur_arg++;
889 if (!*args[cur_arg]) {
890 memprintf(err, "'%s' expects <http_errors_name> as argument", args[cur_arg-1]);
891 return ACT_RET_PRS_ERR;
892 }
893 /* Must be resolved during the config validity check */
894 rule->arg.act.p[0] = (void *)((intptr_t)status);
895 rule->arg.act.p[1] = strdup(args[cur_arg]);
896 rule->check_ptr = check_http_deny_action;
897 cur_arg++;
898 goto out;
899 }
900
901 rule->arg.http_deny.status = status;
902
903 out:
Christopher Faulet040c8cd2020-01-13 16:43:45 +0100904 *orig_arg = cur_arg;
Christopher Faulet81e20172019-12-12 16:40:30 +0100905 return ACT_RET_PRS_OK;
906}
907
908/* Parse a "auth" action. It may take 2 optional arguments to define a "realm"
909 * parameter. It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
910 */
911static enum act_parse_ret parse_http_auth(const char **args, int *orig_arg, struct proxy *px,
912 struct act_rule *rule, char **err)
913{
914 int cur_arg;
915
916 rule->action = ACT_HTTP_REQ_AUTH;
Christopher Faulet245cf792019-12-18 14:58:12 +0100917 rule->flags |= ACT_FLAG_FINAL;
Christopher Faulet2eb53962020-01-14 14:47:34 +0100918 rule->release_ptr = release_http_action;
Christopher Faulet81e20172019-12-12 16:40:30 +0100919
920 cur_arg = *orig_arg;
921 if (!strcmp(args[cur_arg], "realm")) {
922 cur_arg++;
923 if (!*args[cur_arg]) {
924 memprintf(err, "missing realm value.\n");
925 return ACT_RET_PRS_ERR;
926 }
Christopher Faulet96bff762019-12-17 13:46:18 +0100927 rule->arg.http.str.ptr = strdup(args[cur_arg]);
928 rule->arg.http.str.len = strlen(rule->arg.http.str.ptr);
Christopher Faulet81e20172019-12-12 16:40:30 +0100929 cur_arg++;
930 }
931
Christopher Fauletc20b3712020-01-27 15:51:56 +0100932 LIST_INIT(&rule->arg.http.fmt);
Christopher Faulet81e20172019-12-12 16:40:30 +0100933 *orig_arg = cur_arg;
934 return ACT_RET_PRS_OK;
935}
936
937/* Parse a "set-nice" action. It takes the nice value as argument. It returns
938 * ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
939 */
940static enum act_parse_ret parse_http_set_nice(const char **args, int *orig_arg, struct proxy *px,
941 struct act_rule *rule, char **err)
942{
943 int cur_arg;
944
945 rule->action = ACT_HTTP_SET_NICE;
946
947 cur_arg = *orig_arg;
948 if (!*args[cur_arg]) {
949 memprintf(err, "expects exactly 1 argument (integer value)");
950 return ACT_RET_PRS_ERR;
951 }
Christopher Faulet96bff762019-12-17 13:46:18 +0100952 rule->arg.http.i = atoi(args[cur_arg]);
953 if (rule->arg.http.i < -1024)
954 rule->arg.http.i = -1024;
955 else if (rule->arg.http.i > 1024)
956 rule->arg.http.i = 1024;
Christopher Faulet81e20172019-12-12 16:40:30 +0100957
Christopher Fauletc20b3712020-01-27 15:51:56 +0100958 LIST_INIT(&rule->arg.http.fmt);
Christopher Faulet81e20172019-12-12 16:40:30 +0100959 *orig_arg = cur_arg + 1;
960 return ACT_RET_PRS_OK;
961}
962
963/* Parse a "set-tos" action. It takes the TOS value as argument. It returns
964 * ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
965 */
966static enum act_parse_ret parse_http_set_tos(const char **args, int *orig_arg, struct proxy *px,
967 struct act_rule *rule, char **err)
968{
969#ifdef IP_TOS
970 char *endp;
971 int cur_arg;
972
973 rule->action = ACT_HTTP_SET_TOS;
974
975 cur_arg = *orig_arg;
976 if (!*args[cur_arg]) {
977 memprintf(err, "expects exactly 1 argument (integer/hex value)");
978 return ACT_RET_PRS_ERR;
979 }
Christopher Faulet96bff762019-12-17 13:46:18 +0100980 rule->arg.http.i = strtol(args[cur_arg], &endp, 0);
Christopher Faulet81e20172019-12-12 16:40:30 +0100981 if (endp && *endp != '\0') {
982 memprintf(err, "invalid character starting at '%s' (integer/hex value expected)", endp);
983 return ACT_RET_PRS_ERR;
984 }
985
Christopher Fauletc20b3712020-01-27 15:51:56 +0100986 LIST_INIT(&rule->arg.http.fmt);
Christopher Faulet81e20172019-12-12 16:40:30 +0100987 *orig_arg = cur_arg + 1;
988 return ACT_RET_PRS_OK;
989#else
990 memprintf(err, "not supported on this platform (IP_TOS undefined)");
991 return ACT_RET_PRS_ERR;
992#endif
993}
994
995/* Parse a "set-mark" action. It takes the MARK value as argument. It returns
996 * ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
997 */
998static enum act_parse_ret parse_http_set_mark(const char **args, int *orig_arg, struct proxy *px,
999 struct act_rule *rule, char **err)
1000{
1001#ifdef SO_MARK
1002 char *endp;
1003 int cur_arg;
1004
1005 rule->action = ACT_HTTP_SET_MARK;
1006
1007 cur_arg = *orig_arg;
1008 if (!*args[cur_arg]) {
1009 memprintf(err, "expects exactly 1 argument (integer/hex value)");
1010 return ACT_RET_PRS_ERR;
1011 }
Christopher Faulet96bff762019-12-17 13:46:18 +01001012 rule->arg.http.i = strtoul(args[cur_arg], &endp, 0);
Christopher Faulet81e20172019-12-12 16:40:30 +01001013 if (endp && *endp != '\0') {
1014 memprintf(err, "invalid character starting at '%s' (integer/hex value expected)", endp);
1015 return ACT_RET_PRS_ERR;
1016 }
1017
Christopher Fauletc20b3712020-01-27 15:51:56 +01001018 LIST_INIT(&rule->arg.http.fmt);
Christopher Faulet81e20172019-12-12 16:40:30 +01001019 *orig_arg = cur_arg + 1;
1020 global.last_checks |= LSTCHK_NETADM;
1021 return ACT_RET_PRS_OK;
1022#else
1023 memprintf(err, "not supported on this platform (SO_MARK undefined)");
1024 return ACT_RET_PRS_ERR;
1025#endif
1026}
1027
1028/* Parse a "set-log-level" action. It takes the level value as argument. It
1029 * returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
1030 */
1031static enum act_parse_ret parse_http_set_log_level(const char **args, int *orig_arg, struct proxy *px,
1032 struct act_rule *rule, char **err)
1033{
1034 int cur_arg;
1035
1036 rule->action = ACT_HTTP_SET_LOGL;
1037
1038 cur_arg = *orig_arg;
1039 if (!*args[cur_arg]) {
1040 bad_log_level:
1041 memprintf(err, "expects exactly 1 argument (log level name or 'silent')");
1042 return ACT_RET_PRS_ERR;
1043 }
1044 if (strcmp(args[cur_arg], "silent") == 0)
Christopher Faulet96bff762019-12-17 13:46:18 +01001045 rule->arg.http.i = -1;
1046 else if ((rule->arg.http.i = get_log_level(args[cur_arg]) + 1) == 0)
Christopher Faulet81e20172019-12-12 16:40:30 +01001047 goto bad_log_level;
1048
Christopher Fauletc20b3712020-01-27 15:51:56 +01001049 LIST_INIT(&rule->arg.http.fmt);
Christopher Faulet81e20172019-12-12 16:40:30 +01001050 *orig_arg = cur_arg + 1;
1051 return ACT_RET_PRS_OK;
1052}
1053
Christopher Faulet91b3ec12020-01-17 22:30:06 +01001054/* This function executes a early-hint action. It adds an HTTP Early Hint HTTP
1055 * 103 response header with <.arg.http.str> name and with a value built
1056 * according to <.arg.http.fmt> log line format. If it is the first early-hint
1057 * rule of a serie, the 103 response start-line is added first. At the end, if
1058 * the next rule is not an early-hint rule or if it is the last rule, the EOH
1059 * block is added to terminate the response. On success, it returns
1060 * ACT_RET_CONT. If an error occurs while soft rewrites are enabled, the action
1061 * is canceled, but the rule processing continue. Otherwsize ACT_RET_ERR is
1062 * returned.
1063 */
1064static enum act_return http_action_early_hint(struct act_rule *rule, struct proxy *px,
1065 struct session *sess, struct stream *s, int flags)
1066{
1067 struct act_rule *prev_rule, *next_rule;
1068 struct channel *res = &s->res;
1069 struct htx *htx = htx_from_buf(&res->buf);
1070 struct buffer *value = alloc_trash_chunk();
1071 enum act_return ret = ACT_RET_CONT;
1072
1073 if (!(s->txn->req.flags & HTTP_MSGF_VER_11))
1074 goto leave;
1075
1076 if (!value) {
1077 if (!(s->flags & SF_ERR_MASK))
1078 s->flags |= SF_ERR_RESOURCE;
1079 goto error;
1080 }
1081
1082 /* get previous and next rules */
1083 prev_rule = LIST_PREV(&rule->list, typeof(rule), list);
1084 next_rule = LIST_NEXT(&rule->list, typeof(rule), list);
1085
1086 /* if no previous rule or previous rule is not early-hint, start a new response. Otherwise,
1087 * continue to add link to a previously started response */
1088 if (&prev_rule->list == s->current_rule_list || prev_rule->action_ptr != http_action_early_hint) {
1089 struct htx_sl *sl;
1090 unsigned int flags = (HTX_SL_F_IS_RESP|HTX_SL_F_VER_11|
1091 HTX_SL_F_XFER_LEN|HTX_SL_F_BODYLESS);
1092
1093 sl = htx_add_stline(htx, HTX_BLK_RES_SL, flags,
1094 ist("HTTP/1.1"), ist("103"), ist("Early Hints"));
1095 if (!sl)
1096 goto error;
1097 sl->info.res.status = 103;
1098 }
1099
1100 /* Add the HTTP Early Hint HTTP 103 response heade */
1101 value->data = build_logline(s, b_tail(value), b_room(value), &rule->arg.http.fmt);
1102 if (!htx_add_header(htx, rule->arg.http.str, ist2(b_head(value), b_data(value))))
1103 goto error;
1104
1105 /* if it is the last rule or the next one is not an early-hint, terminate the current
1106 * response. */
1107 if (&next_rule->list == s->current_rule_list || next_rule->action_ptr != http_action_early_hint) {
1108 size_t data;
1109
1110 if (!htx_add_endof(htx, HTX_BLK_EOH)) {
1111 /* If an error occurred during an Early-hint rule,
1112 * remove the incomplete HTTP 103 response from the
1113 * buffer */
1114 goto error;
1115 }
1116
1117 data = htx->data - co_data(res);
1118 c_adv(res, data);
1119 res->total += data;
1120 }
1121
1122 leave:
1123 free_trash_chunk(value);
1124 return ret;
1125
1126 error:
1127 /* If an error occurred during an Early-hint rule, remove the incomplete
1128 * HTTP 103 response from the buffer */
1129 channel_htx_truncate(res, htx);
1130 ret = ACT_RET_ERR;
1131 goto leave;
1132}
1133
Christopher Fauletd1f27e32019-12-17 09:33:38 +01001134/* This function executes a set-header or add-header actions. It builds a string
1135 * in the trash from the specified format string. It finds the action to be
1136 * performed in <.action>, previously filled by function parse_set_header(). The
1137 * replacement action is excuted by the function http_action_set_header(). On
1138 * success, it returns ACT_RET_CONT. If an error occurs while soft rewrites are
1139 * enabled, the action is canceled, but the rule processing continue. Otherwsize
1140 * ACT_RET_ERR is returned.
1141 */
1142static enum act_return http_action_set_header(struct act_rule *rule, struct proxy *px,
1143 struct session *sess, struct stream *s, int flags)
1144{
1145 struct htx *htx = htxbuf((rule->from == ACT_F_HTTP_REQ) ? &s->req.buf : &s->res.buf);
1146 enum act_return ret = ACT_RET_CONT;
1147 struct buffer *replace;
1148 struct http_hdr_ctx ctx;
1149 struct ist n, v;
1150
1151 replace = alloc_trash_chunk();
1152 if (!replace)
1153 goto fail_alloc;
1154
1155 replace->data = build_logline(s, replace->area, replace->size, &rule->arg.http.fmt);
1156 n = rule->arg.http.str;
1157 v = ist2(replace->area, replace->data);
1158
1159 if (rule->action == 0) { // set-header
1160 /* remove all occurrences of the header */
1161 ctx.blk = NULL;
1162 while (http_find_header(htx, n, &ctx, 1))
1163 http_remove_header(htx, &ctx);
1164 }
1165
1166 /* Now add header */
1167 if (!http_add_header(htx, n, v))
1168 goto fail_rewrite;
1169
1170 leave:
1171 free_trash_chunk(replace);
1172 return ret;
1173
1174 fail_alloc:
1175 if (!(s->flags & SF_ERR_MASK))
1176 s->flags |= SF_ERR_RESOURCE;
1177 ret = ACT_RET_ERR;
1178 goto leave;
1179
1180 fail_rewrite:
1181 _HA_ATOMIC_ADD(&sess->fe->fe_counters.failed_rewrites, 1);
1182 if (s->flags & SF_BE_ASSIGNED)
1183 _HA_ATOMIC_ADD(&s->be->be_counters.failed_rewrites, 1);
1184 if (sess->listener->counters)
1185 _HA_ATOMIC_ADD(&sess->listener->counters->failed_rewrites, 1);
1186 if (objt_server(s->target))
1187 _HA_ATOMIC_ADD(&__objt_server(s->target)->counters.failed_rewrites, 1);
1188
1189 if (!(s->txn->req.flags & HTTP_MSGF_SOFT_RW))
1190 ret = ACT_RET_ERR;
1191 goto leave;
1192}
1193
Christopher Faulet81e20172019-12-12 16:40:30 +01001194/* Parse a "set-header", "add-header" or "early-hint" actions. It takes an
1195 * header name and a log-format string as arguments. It returns ACT_RET_PRS_OK
1196 * on success, ACT_RET_PRS_ERR on error.
1197 *
1198 * Note: same function is used for the request and the response. However
1199 * "early-hint" rules are only supported for request rules.
1200 */
1201static enum act_parse_ret parse_http_set_header(const char **args, int *orig_arg, struct proxy *px,
1202 struct act_rule *rule, char **err)
1203{
Christopher Faulet81e20172019-12-12 16:40:30 +01001204 int cap, cur_arg;
1205
Christopher Faulet91b3ec12020-01-17 22:30:06 +01001206 if (args[*orig_arg-1][0] == 'e') {
1207 rule->action = ACT_CUSTOM;
1208 rule->action_ptr = http_action_early_hint;
1209 }
Christopher Fauletd1f27e32019-12-17 09:33:38 +01001210 else {
1211 if (args[*orig_arg-1][0] == 's')
1212 rule->action = 0; // set-header
1213 else
1214 rule->action = 1; // add-header
1215 rule->action_ptr = http_action_set_header;
1216 }
Christopher Faulet2eb53962020-01-14 14:47:34 +01001217 rule->release_ptr = release_http_action;
Christopher Faulet81e20172019-12-12 16:40:30 +01001218
1219 cur_arg = *orig_arg;
1220 if (!*args[cur_arg] || !*args[cur_arg+1]) {
1221 memprintf(err, "expects exactly 2 arguments");
1222 return ACT_RET_PRS_ERR;
1223 }
1224
Christopher Faulet81e20172019-12-12 16:40:30 +01001225
Christopher Faulet96bff762019-12-17 13:46:18 +01001226 rule->arg.http.str.ptr = strdup(args[cur_arg]);
1227 rule->arg.http.str.len = strlen(rule->arg.http.str.ptr);
1228 LIST_INIT(&rule->arg.http.fmt);
Christopher Faulet81e20172019-12-12 16:40:30 +01001229
1230 if (rule->from == ACT_F_HTTP_REQ) {
1231 px->conf.args.ctx = ARGC_HRQ;
1232 cap = (px->cap & PR_CAP_FE) ? SMP_VAL_FE_HRQ_HDR : SMP_VAL_BE_HRQ_HDR;
1233 }
1234 else{
1235 px->conf.args.ctx = ARGC_HRS;
1236 cap = (px->cap & PR_CAP_BE) ? SMP_VAL_BE_HRS_HDR : SMP_VAL_FE_HRS_HDR;
1237 }
1238
1239 cur_arg++;
Christopher Faulet1337b322020-01-14 14:50:55 +01001240 if (!parse_logformat_string(args[cur_arg], px, &rule->arg.http.fmt, LOG_OPT_HTTP, cap, err)) {
1241 free(rule->arg.http.str.ptr);
Christopher Faulet81e20172019-12-12 16:40:30 +01001242 return ACT_RET_PRS_ERR;
Christopher Faulet1337b322020-01-14 14:50:55 +01001243 }
Christopher Faulet81e20172019-12-12 16:40:30 +01001244
1245 free(px->conf.lfs_file);
1246 px->conf.lfs_file = strdup(px->conf.args.file);
1247 px->conf.lfs_line = px->conf.args.line;
1248
1249 *orig_arg = cur_arg + 1;
1250 return ACT_RET_PRS_OK;
1251}
1252
Christopher Faulet92d34fe2019-12-17 09:20:34 +01001253/* This function executes a replace-header or replace-value actions. It
1254 * builds a string in the trash from the specified format string. It finds
1255 * the action to be performed in <.action>, previously filled by function
1256 * parse_replace_header(). The replacement action is excuted by the function
1257 * http_action_replace_header(). On success, it returns ACT_RET_CONT. If an error
1258 * occurs while soft rewrites are enabled, the action is canceled, but the rule
1259 * processing continue. Otherwsize ACT_RET_ERR is returned.
1260 */
1261static enum act_return http_action_replace_header(struct act_rule *rule, struct proxy *px,
1262 struct session *sess, struct stream *s, int flags)
1263{
1264 struct htx *htx = htxbuf((rule->from == ACT_F_HTTP_REQ) ? &s->req.buf : &s->res.buf);
1265 enum act_return ret = ACT_RET_CONT;
1266 struct buffer *replace;
1267 int r;
1268
1269 replace = alloc_trash_chunk();
1270 if (!replace)
1271 goto fail_alloc;
1272
1273 replace->data = build_logline(s, replace->area, replace->size, &rule->arg.http.fmt);
1274
1275 r = http_replace_hdrs(s, htx, rule->arg.http.str, replace->area, rule->arg.http.re, (rule->action == 0));
1276 if (r == -1)
1277 goto fail_rewrite;
1278
1279 leave:
1280 free_trash_chunk(replace);
1281 return ret;
1282
1283 fail_alloc:
1284 if (!(s->flags & SF_ERR_MASK))
1285 s->flags |= SF_ERR_RESOURCE;
1286 ret = ACT_RET_ERR;
1287 goto leave;
1288
1289 fail_rewrite:
1290 _HA_ATOMIC_ADD(&sess->fe->fe_counters.failed_rewrites, 1);
1291 if (s->flags & SF_BE_ASSIGNED)
1292 _HA_ATOMIC_ADD(&s->be->be_counters.failed_rewrites, 1);
1293 if (sess->listener->counters)
1294 _HA_ATOMIC_ADD(&sess->listener->counters->failed_rewrites, 1);
1295 if (objt_server(s->target))
1296 _HA_ATOMIC_ADD(&__objt_server(s->target)->counters.failed_rewrites, 1);
1297
1298 if (!(s->txn->req.flags & HTTP_MSGF_SOFT_RW))
1299 ret = ACT_RET_ERR;
1300 goto leave;
1301}
1302
Christopher Faulet81e20172019-12-12 16:40:30 +01001303/* Parse a "replace-header" or "replace-value" actions. It takes an header name,
1304 * a regex and replacement string as arguments. It returns ACT_RET_PRS_OK on
1305 * success, ACT_RET_PRS_ERR on error.
1306 */
1307static enum act_parse_ret parse_http_replace_header(const char **args, int *orig_arg, struct proxy *px,
1308 struct act_rule *rule, char **err)
1309{
1310 int cap, cur_arg;
1311
Christopher Faulet92d34fe2019-12-17 09:20:34 +01001312 if (args[*orig_arg-1][8] == 'h')
1313 rule->action = 0; // replace-header
1314 else
1315 rule->action = 1; // replace-value
1316 rule->action_ptr = http_action_replace_header;
Christopher Faulet2eb53962020-01-14 14:47:34 +01001317 rule->release_ptr = release_http_action;
Christopher Faulet81e20172019-12-12 16:40:30 +01001318
1319 cur_arg = *orig_arg;
1320 if (!*args[cur_arg] || !*args[cur_arg+1] || !*args[cur_arg+2]) {
1321 memprintf(err, "expects exactly 3 arguments");
1322 return ACT_RET_PRS_ERR;
1323 }
1324
Christopher Faulet96bff762019-12-17 13:46:18 +01001325 rule->arg.http.str.ptr = strdup(args[cur_arg]);
1326 rule->arg.http.str.len = strlen(rule->arg.http.str.ptr);
1327 LIST_INIT(&rule->arg.http.fmt);
Christopher Faulet81e20172019-12-12 16:40:30 +01001328
1329 cur_arg++;
Christopher Faulet1337b322020-01-14 14:50:55 +01001330 if (!(rule->arg.http.re = regex_comp(args[cur_arg], 1, 1, err))) {
1331 free(rule->arg.http.str.ptr);
Christopher Faulet81e20172019-12-12 16:40:30 +01001332 return ACT_RET_PRS_ERR;
Christopher Faulet1337b322020-01-14 14:50:55 +01001333 }
Christopher Faulet81e20172019-12-12 16:40:30 +01001334
1335 if (rule->from == ACT_F_HTTP_REQ) {
1336 px->conf.args.ctx = ARGC_HRQ;
1337 cap = (px->cap & PR_CAP_FE) ? SMP_VAL_FE_HRQ_HDR : SMP_VAL_BE_HRQ_HDR;
1338 }
1339 else{
1340 px->conf.args.ctx = ARGC_HRS;
1341 cap = (px->cap & PR_CAP_BE) ? SMP_VAL_BE_HRS_HDR : SMP_VAL_FE_HRS_HDR;
1342 }
1343
1344 cur_arg++;
Christopher Faulet1337b322020-01-14 14:50:55 +01001345 if (!parse_logformat_string(args[cur_arg], px, &rule->arg.http.fmt, LOG_OPT_HTTP, cap, err)) {
1346 free(rule->arg.http.str.ptr);
1347 regex_free(rule->arg.http.re);
Christopher Faulet81e20172019-12-12 16:40:30 +01001348 return ACT_RET_PRS_ERR;
Christopher Faulet1337b322020-01-14 14:50:55 +01001349 }
Christopher Faulet81e20172019-12-12 16:40:30 +01001350
1351 free(px->conf.lfs_file);
1352 px->conf.lfs_file = strdup(px->conf.args.file);
1353 px->conf.lfs_line = px->conf.args.line;
1354
1355 *orig_arg = cur_arg + 1;
1356 return ACT_RET_PRS_OK;
1357}
1358
1359/* Parse a "del-header" action. It takes an header name as argument. It returns
1360 * ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
1361 */
1362static enum act_parse_ret parse_http_del_header(const char **args, int *orig_arg, struct proxy *px,
1363 struct act_rule *rule, char **err)
1364{
1365 int cur_arg;
1366
1367 rule->action = ACT_HTTP_DEL_HDR;
Christopher Faulet2eb53962020-01-14 14:47:34 +01001368 rule->release_ptr = release_http_action;
Christopher Faulet81e20172019-12-12 16:40:30 +01001369
1370 cur_arg = *orig_arg;
1371 if (!*args[cur_arg]) {
1372 memprintf(err, "expects exactly 1 arguments");
1373 return ACT_RET_PRS_ERR;
1374 }
1375
Christopher Faulet96bff762019-12-17 13:46:18 +01001376 rule->arg.http.str.ptr = strdup(args[cur_arg]);
1377 rule->arg.http.str.len = strlen(rule->arg.http.str.ptr);
Christopher Faulet81e20172019-12-12 16:40:30 +01001378
1379 px->conf.args.ctx = (rule->from == ACT_F_HTTP_REQ ? ARGC_HRQ : ARGC_HRS);
1380
Christopher Fauletc20b3712020-01-27 15:51:56 +01001381 LIST_INIT(&rule->arg.http.fmt);
Christopher Faulet81e20172019-12-12 16:40:30 +01001382 *orig_arg = cur_arg + 1;
1383 return ACT_RET_PRS_OK;
1384}
1385
Christopher Faulet2eb53962020-01-14 14:47:34 +01001386/* Release memory allocated by an http redirect action. */
1387static void release_http_redir(struct act_rule *rule)
1388{
1389 struct logformat_node *lf, *lfb;
1390 struct redirect_rule *redir;
1391
1392 redir = rule->arg.redir;
1393 LIST_DEL(&redir->list);
1394 if (redir->cond) {
1395 prune_acl_cond(redir->cond);
1396 free(redir->cond);
1397 }
1398 free(redir->rdr_str);
1399 free(redir->cookie_str);
1400 list_for_each_entry_safe(lf, lfb, &redir->rdr_fmt, list) {
1401 LIST_DEL(&lf->list);
1402 free(lf);
1403 }
1404 free(redir);
1405}
1406
Christopher Faulet81e20172019-12-12 16:40:30 +01001407/* Parse a "redirect" action. It returns ACT_RET_PRS_OK on success,
1408 * ACT_RET_PRS_ERR on error.
1409 */
1410static enum act_parse_ret parse_http_redirect(const char **args, int *orig_arg, struct proxy *px,
1411 struct act_rule *rule, char **err)
1412{
1413 struct redirect_rule *redir;
1414 int dir, cur_arg;
1415
1416 rule->action = ACT_HTTP_REDIR;
Christopher Faulet245cf792019-12-18 14:58:12 +01001417 rule->flags |= ACT_FLAG_FINAL;
Christopher Faulet2eb53962020-01-14 14:47:34 +01001418 rule->release_ptr = release_http_redir;
Christopher Faulet81e20172019-12-12 16:40:30 +01001419
1420 cur_arg = *orig_arg;
1421
1422 dir = (rule->from == ACT_F_HTTP_REQ ? 0 : 1);
1423 if ((redir = http_parse_redirect_rule(px->conf.args.file, px->conf.args.line, px, &args[cur_arg], err, 1, dir)) == NULL)
1424 return ACT_RET_PRS_ERR;
1425
1426 rule->arg.redir = redir;
1427 rule->cond = redir->cond;
1428 redir->cond = NULL;
1429
1430 /* skip all arguments */
1431 while (*args[cur_arg])
1432 cur_arg++;
1433
1434 *orig_arg = cur_arg;
1435 return ACT_RET_PRS_OK;
1436}
1437
Christopher Faulet046cf442019-12-17 15:45:23 +01001438/* This function executes a add-acl, del-acl, set-map or del-map actions. On
1439 * success, it returns ACT_RET_CONT. Otherwsize ACT_RET_ERR is returned.
1440 */
1441static enum act_return http_action_set_map(struct act_rule *rule, struct proxy *px,
1442 struct session *sess, struct stream *s, int flags)
1443{
1444 struct pat_ref *ref;
1445 struct buffer *key = NULL, *value = NULL;
1446 enum act_return ret = ACT_RET_CONT;
1447
1448 /* collect reference */
1449 ref = pat_ref_lookup(rule->arg.map.ref);
1450 if (!ref)
1451 goto leave;
1452
1453 /* allocate key */
1454 key = alloc_trash_chunk();
1455 if (!key)
1456 goto fail_alloc;
1457
1458 /* collect key */
1459 key->data = build_logline(s, key->area, key->size, &rule->arg.map.key);
1460 key->area[key->data] = '\0';
1461
1462 switch (rule->action) {
1463 case 0: // add-acl
1464 /* add entry only if it does not already exist */
1465 HA_SPIN_LOCK(PATREF_LOCK, &ref->lock);
1466 if (pat_ref_find_elt(ref, key->area) == NULL)
1467 pat_ref_add(ref, key->area, NULL, NULL);
1468 HA_SPIN_UNLOCK(PATREF_LOCK, &ref->lock);
1469 break;
1470
1471 case 1: // set-map
1472 /* allocate value */
1473 value = alloc_trash_chunk();
1474 if (!value)
1475 goto fail_alloc;
1476
1477 /* collect value */
1478 value->data = build_logline(s, value->area, value->size, &rule->arg.map.value);
1479 value->area[value->data] = '\0';
1480
1481 HA_SPIN_LOCK(PATREF_LOCK, &ref->lock);
1482 if (pat_ref_find_elt(ref, key->area) != NULL) {
1483 /* update entry if it exists */
1484 pat_ref_set(ref, key->area, value->area, NULL);
1485 }
1486 else {
1487 /* insert a new entry */
1488 pat_ref_add(ref, key->area, value->area, NULL);
1489 }
1490 HA_SPIN_UNLOCK(PATREF_LOCK, &ref->lock);
1491 break;
1492
1493 case 2: // del-acl
1494 case 3: // del-map
1495 /* returned code: 1=ok, 0=ko */
1496 HA_SPIN_LOCK(PATREF_LOCK, &ref->lock);
1497 pat_ref_delete(ref, key->area);
1498 HA_SPIN_UNLOCK(PATREF_LOCK, &ref->lock);
1499 break;
1500
1501 default:
1502 ret = ACT_RET_ERR;
1503 }
1504
1505
1506 leave:
1507 free_trash_chunk(key);
1508 free_trash_chunk(value);
1509 return ret;
1510
1511 fail_alloc:
1512 if (!(s->flags & SF_ERR_MASK))
1513 s->flags |= SF_ERR_RESOURCE;
1514 ret = ACT_RET_ERR;
1515 goto leave;
1516}
1517
Christopher Faulet2eb53962020-01-14 14:47:34 +01001518/* Release memory allocated by an http map/acl action. */
1519static void release_http_map(struct act_rule *rule)
1520{
1521 struct logformat_node *lf, *lfb;
1522
1523 free(rule->arg.map.ref);
1524 list_for_each_entry_safe(lf, lfb, &rule->arg.map.key, list) {
1525 LIST_DEL(&lf->list);
1526 release_sample_expr(lf->expr);
1527 free(lf->arg);
1528 free(lf);
1529 }
1530 if (rule->action == 1) {
1531 list_for_each_entry_safe(lf, lfb, &rule->arg.map.value, list) {
1532 LIST_DEL(&lf->list);
1533 release_sample_expr(lf->expr);
1534 free(lf->arg);
1535 free(lf);
1536 }
1537 }
1538}
1539
Christopher Faulet81e20172019-12-12 16:40:30 +01001540/* Parse a "add-acl", "del-acl", "set-map" or "del-map" actions. It takes one or
Christopher Faulet046cf442019-12-17 15:45:23 +01001541 * two log-format string as argument depending on the action. The action is
1542 * stored in <.action> as an int (0=add-acl, 1=set-map, 2=del-acl,
1543 * 3=del-map). It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
Christopher Faulet81e20172019-12-12 16:40:30 +01001544 */
1545static enum act_parse_ret parse_http_set_map(const char **args, int *orig_arg, struct proxy *px,
1546 struct act_rule *rule, char **err)
1547{
1548 int cap, cur_arg;
1549
Christopher Faulet046cf442019-12-17 15:45:23 +01001550 if (args[*orig_arg-1][0] == 'a') // add-acl
1551 rule->action = 0;
1552 else if (args[*orig_arg-1][0] == 's') // set-map
1553 rule->action = 1;
1554 else if (args[*orig_arg-1][4] == 'a') // del-acl
1555 rule->action = 2;
1556 else if (args[*orig_arg-1][4] == 'm') // del-map
1557 rule->action = 3;
1558 else {
1559 memprintf(err, "internal error: unhandled action '%s'", args[0]);
1560 return ACT_RET_PRS_ERR;
1561 }
1562 rule->action_ptr = http_action_set_map;
Christopher Faulet2eb53962020-01-14 14:47:34 +01001563 rule->release_ptr = release_http_map;
Christopher Faulet81e20172019-12-12 16:40:30 +01001564
1565 cur_arg = *orig_arg;
Christopher Faulet046cf442019-12-17 15:45:23 +01001566 if (rule->action == 1 && (!*args[cur_arg] || !*args[cur_arg+1])) {
1567 /* 2 args for set-map */
Christopher Faulet81e20172019-12-12 16:40:30 +01001568 memprintf(err, "expects exactly 2 arguments");
1569 return ACT_RET_PRS_ERR;
1570 }
1571 else if (!*args[cur_arg]) {
Christopher Faulet046cf442019-12-17 15:45:23 +01001572 /* only one arg for other actions */
Christopher Faulet81e20172019-12-12 16:40:30 +01001573 memprintf(err, "expects exactly 1 arguments");
1574 return ACT_RET_PRS_ERR;
1575 }
1576
1577 /*
1578 * '+ 8' for 'set-map(' (same for del-map)
1579 * '- 9' for 'set-map(' + trailing ')' (same for del-map)
1580 */
1581 rule->arg.map.ref = my_strndup(args[cur_arg-1] + 8, strlen(args[cur_arg-1]) - 9);
1582
1583 if (rule->from == ACT_F_HTTP_REQ) {
1584 px->conf.args.ctx = ARGC_HRQ;
1585 cap = (px->cap & PR_CAP_FE) ? SMP_VAL_FE_HRQ_HDR : SMP_VAL_BE_HRQ_HDR;
1586 }
1587 else{
1588 px->conf.args.ctx = ARGC_HRS;
1589 cap = (px->cap & PR_CAP_BE) ? SMP_VAL_BE_HRS_HDR : SMP_VAL_FE_HRS_HDR;
1590 }
1591
1592 /* key pattern */
1593 LIST_INIT(&rule->arg.map.key);
Christopher Faulet1337b322020-01-14 14:50:55 +01001594 if (!parse_logformat_string(args[cur_arg], px, &rule->arg.map.key, LOG_OPT_HTTP, cap, err)) {
1595 free(rule->arg.map.ref);
Christopher Faulet81e20172019-12-12 16:40:30 +01001596 return ACT_RET_PRS_ERR;
Christopher Faulet1337b322020-01-14 14:50:55 +01001597 }
Christopher Faulet81e20172019-12-12 16:40:30 +01001598
Christopher Faulet046cf442019-12-17 15:45:23 +01001599 if (rule->action == 1) {
Christopher Faulet81e20172019-12-12 16:40:30 +01001600 /* value pattern for set-map only */
1601 cur_arg++;
1602 LIST_INIT(&rule->arg.map.value);
Christopher Faulet1337b322020-01-14 14:50:55 +01001603 if (!parse_logformat_string(args[cur_arg], px, &rule->arg.map.value, LOG_OPT_HTTP, cap, err)) {
1604 free(rule->arg.map.ref);
Christopher Faulet81e20172019-12-12 16:40:30 +01001605 return ACT_RET_PRS_ERR;
Christopher Faulet1337b322020-01-14 14:50:55 +01001606 }
Christopher Faulet81e20172019-12-12 16:40:30 +01001607 }
1608
1609 free(px->conf.lfs_file);
1610 px->conf.lfs_file = strdup(px->conf.args.file);
1611 px->conf.lfs_line = px->conf.args.line;
1612
1613 *orig_arg = cur_arg + 1;
1614 return ACT_RET_PRS_OK;
1615}
1616
Christopher Fauletac98d812019-12-18 09:20:16 +01001617/* This function executes a track-sc* actions. On success, it returns
1618 * ACT_RET_CONT. Otherwsize ACT_RET_ERR is returned.
1619 */
1620static enum act_return http_action_track_sc(struct act_rule *rule, struct proxy *px,
1621 struct session *sess, struct stream *s, int flags)
1622{
1623 struct stktable *t;
1624 struct stksess *ts;
1625 struct stktable_key *key;
1626 void *ptr1, *ptr2, *ptr3, *ptr4;
1627 int opt;
1628
1629 ptr1 = ptr2 = ptr3 = ptr4 = NULL;
1630 opt = ((rule->from == ACT_F_HTTP_REQ) ? SMP_OPT_DIR_REQ : SMP_OPT_DIR_RES) | SMP_OPT_FINAL;
1631
1632 t = rule->arg.trk_ctr.table.t;
1633 key = stktable_fetch_key(t, s->be, sess, s, opt, rule->arg.trk_ctr.expr, NULL);
1634
1635 if (!key)
1636 goto end;
1637 ts = stktable_get_entry(t, key);
1638 if (!ts)
1639 goto end;
1640
1641 stream_track_stkctr(&s->stkctr[rule->action], t, ts);
1642
1643 /* let's count a new HTTP request as it's the first time we do it */
1644 ptr1 = stktable_data_ptr(t, ts, STKTABLE_DT_HTTP_REQ_CNT);
1645 ptr2 = stktable_data_ptr(t, ts, STKTABLE_DT_HTTP_REQ_RATE);
1646
1647 /* When the client triggers a 4xx from the server, it's most often due
1648 * to a missing object or permission. These events should be tracked
1649 * because if they happen often, it may indicate a brute force or a
1650 * vulnerability scan. Normally this is done when receiving the response
1651 * but here we're tracking after this ought to have been done so we have
1652 * to do it on purpose.
1653 */
1654 if (rule->from == ACT_F_HTTP_RES && (unsigned)(s->txn->status - 400) < 100) {
1655 ptr3 = stktable_data_ptr(t, ts, STKTABLE_DT_HTTP_ERR_CNT);
1656 ptr4 = stktable_data_ptr(t, ts, STKTABLE_DT_HTTP_ERR_RATE);
1657 }
1658
1659 if (ptr1 || ptr2 || ptr3 || ptr4) {
1660 HA_RWLOCK_WRLOCK(STK_SESS_LOCK, &ts->lock);
1661
1662 if (ptr1)
1663 stktable_data_cast(ptr1, http_req_cnt)++;
1664 if (ptr2)
1665 update_freq_ctr_period(&stktable_data_cast(ptr2, http_req_rate),
1666 t->data_arg[STKTABLE_DT_HTTP_REQ_RATE].u, 1);
1667 if (ptr3)
1668 stktable_data_cast(ptr3, http_err_cnt)++;
1669 if (ptr4)
1670 update_freq_ctr_period(&stktable_data_cast(ptr4, http_err_rate),
1671 t->data_arg[STKTABLE_DT_HTTP_ERR_RATE].u, 1);
1672
1673 HA_RWLOCK_WRUNLOCK(STK_SESS_LOCK, &ts->lock);
1674
1675 /* If data was modified, we need to touch to re-schedule sync */
1676 stktable_touch_local(t, ts, 0);
1677 }
1678
1679 stkctr_set_flags(&s->stkctr[rule->action], STKCTR_TRACK_CONTENT);
1680 if (sess->fe != s->be)
1681 stkctr_set_flags(&s->stkctr[rule->action], STKCTR_TRACK_BACKEND);
1682
1683 end:
1684 return ACT_RET_CONT;
1685}
Christopher Faulet81e20172019-12-12 16:40:30 +01001686
Christopher Faulet2eb53962020-01-14 14:47:34 +01001687static void release_http_track_sc(struct act_rule *rule)
1688{
1689 release_sample_expr(rule->arg.trk_ctr.expr);
1690}
1691
Christopher Faulet81e20172019-12-12 16:40:30 +01001692/* Parse a "track-sc*" actions. It returns ACT_RET_PRS_OK on success,
1693 * ACT_RET_PRS_ERR on error.
1694 */
1695static enum act_parse_ret parse_http_track_sc(const char **args, int *orig_arg, struct proxy *px,
1696 struct act_rule *rule, char **err)
1697{
1698 struct sample_expr *expr;
1699 unsigned int where;
1700 unsigned int tsc_num;
1701 const char *tsc_num_str;
1702 int cur_arg;
1703
1704 tsc_num_str = &args[*orig_arg-1][8];
1705 if (cfg_parse_track_sc_num(&tsc_num, tsc_num_str, tsc_num_str + strlen(tsc_num_str), err) == -1)
1706 return ACT_RET_PRS_ERR;
1707
1708 cur_arg = *orig_arg;
1709 expr = sample_parse_expr((char **)args, &cur_arg, px->conf.args.file, px->conf.args.line,
1710 err, &px->conf.args);
1711 if (!expr)
1712 return ACT_RET_PRS_ERR;
1713
1714 where = 0;
1715 if (px->cap & PR_CAP_FE)
1716 where |= (rule->from == ACT_F_HTTP_REQ ? SMP_VAL_FE_HRQ_HDR : SMP_VAL_FE_HRS_HDR);
1717 if (px->cap & PR_CAP_BE)
1718 where |= (rule->from == ACT_F_HTTP_REQ ? SMP_VAL_BE_HRQ_HDR : SMP_VAL_BE_HRS_HDR);
1719
1720 if (!(expr->fetch->val & where)) {
1721 memprintf(err, "fetch method '%s' extracts information from '%s', none of which is available here",
1722 args[cur_arg-1], sample_src_names(expr->fetch->use));
Christopher Faulet1337b322020-01-14 14:50:55 +01001723 release_sample_expr(expr);
Christopher Faulet81e20172019-12-12 16:40:30 +01001724 return ACT_RET_PRS_ERR;
1725 }
1726
1727 if (strcmp(args[cur_arg], "table") == 0) {
1728 cur_arg++;
1729 if (!*args[cur_arg]) {
1730 memprintf(err, "missing table name");
Christopher Faulet1337b322020-01-14 14:50:55 +01001731 release_sample_expr(expr);
Christopher Faulet81e20172019-12-12 16:40:30 +01001732 return ACT_RET_PRS_ERR;
1733 }
1734
1735 /* we copy the table name for now, it will be resolved later */
1736 rule->arg.trk_ctr.table.n = strdup(args[cur_arg]);
1737 cur_arg++;
1738 }
1739
Christopher Fauletac98d812019-12-18 09:20:16 +01001740 rule->action = tsc_num;
Christopher Faulet81e20172019-12-12 16:40:30 +01001741 rule->arg.trk_ctr.expr = expr;
Christopher Fauletac98d812019-12-18 09:20:16 +01001742 rule->action_ptr = http_action_track_sc;
Christopher Faulet2eb53962020-01-14 14:47:34 +01001743 rule->release_ptr = release_http_track_sc;
Christopher Faulet81e20172019-12-12 16:40:30 +01001744 rule->check_ptr = check_trk_action;
1745
1746 *orig_arg = cur_arg;
1747 return ACT_RET_PRS_OK;
1748}
1749
Christopher Faulet46f95542019-12-20 10:07:22 +01001750/* This function executes a strict-mode actions. On success, it always returns
1751 * ACT_RET_CONT
1752 */
1753static enum act_return http_action_strict_mode(struct act_rule *rule, struct proxy *px,
1754 struct session *sess, struct stream *s, int flags)
1755{
1756 struct http_msg *msg = ((rule->from == ACT_F_HTTP_REQ) ? &s->txn->req : &s->txn->rsp);
1757
1758 if (rule->action == 0) // strict-mode on
1759 msg->flags &= ~HTTP_MSGF_SOFT_RW;
1760 else // strict-mode off
1761 msg->flags |= HTTP_MSGF_SOFT_RW;
1762 return ACT_RET_CONT;
1763}
1764
1765/* Parse a "strict-mode" action. It returns ACT_RET_PRS_OK on success,
1766 * ACT_RET_PRS_ERR on error.
1767 */
1768static enum act_parse_ret parse_http_strict_mode(const char **args, int *orig_arg, struct proxy *px,
1769 struct act_rule *rule, char **err)
1770{
1771 int cur_arg;
1772
1773
1774 cur_arg = *orig_arg;
1775 if (!*args[cur_arg]) {
1776 memprintf(err, "expects exactly 1 arguments");
1777 return ACT_RET_PRS_ERR;
1778 }
1779
1780 if (strcasecmp(args[cur_arg], "on") == 0)
1781 rule->action = 0; // strict-mode on
1782 else if (strcasecmp(args[cur_arg], "off") == 0)
1783 rule->action = 1; // strict-mode off
1784 else {
1785 memprintf(err, "Unexpected value '%s'. Only 'on' and 'off' are supported", args[cur_arg]);
1786 return ACT_RET_PRS_ERR;
1787 }
1788 rule->action_ptr = http_action_strict_mode;
1789
1790 *orig_arg = cur_arg + 1;
1791 return ACT_RET_PRS_OK;
1792}
1793
Willy Tarreau79e57332018-10-02 16:01:16 +02001794/************************************************************************/
1795/* All supported http-request action keywords must be declared here. */
1796/************************************************************************/
1797
1798static struct action_kw_list http_req_actions = {
1799 .kw = {
Christopher Faulet81e20172019-12-12 16:40:30 +01001800 { "add-acl", parse_http_set_map, 1 },
1801 { "add-header", parse_http_set_header, 0 },
1802 { "allow", parse_http_allow, 0 },
1803 { "auth", parse_http_auth, 0 },
1804 { "capture", parse_http_req_capture, 0 },
1805 { "del-acl", parse_http_set_map, 1 },
1806 { "del-header", parse_http_del_header, 0 },
1807 { "del-map", parse_http_set_map, 1 },
Christopher Faulete0fca292020-01-13 21:49:03 +01001808 { "deny", parse_http_deny, 0 },
Christopher Faulet81e20172019-12-12 16:40:30 +01001809 { "disable-l7-retry", parse_http_req_disable_l7_retry, 0 },
1810 { "early-hint", parse_http_set_header, 0 },
1811 { "redirect", parse_http_redirect, 0 },
1812 { "reject", parse_http_action_reject, 0 },
1813 { "replace-header", parse_http_replace_header, 0 },
1814 { "replace-path", parse_replace_uri, 0 },
1815 { "replace-uri", parse_replace_uri, 0 },
1816 { "replace-value", parse_http_replace_header, 0 },
1817 { "set-header", parse_http_set_header, 0 },
1818 { "set-log-level", parse_http_set_log_level, 0 },
1819 { "set-map", parse_http_set_map, 1 },
1820 { "set-method", parse_set_req_line, 0 },
1821 { "set-mark", parse_http_set_mark, 0 },
1822 { "set-nice", parse_http_set_nice, 0 },
1823 { "set-path", parse_set_req_line, 0 },
1824 { "set-query", parse_set_req_line, 0 },
1825 { "set-tos", parse_http_set_tos, 0 },
1826 { "set-uri", parse_set_req_line, 0 },
Christopher Faulet46f95542019-12-20 10:07:22 +01001827 { "strict-mode", parse_http_strict_mode, 0 },
Christopher Faulete0fca292020-01-13 21:49:03 +01001828 { "tarpit", parse_http_deny, 0 },
Christopher Faulet81e20172019-12-12 16:40:30 +01001829 { "track-sc", parse_http_track_sc, 1 },
Willy Tarreau79e57332018-10-02 16:01:16 +02001830 { NULL, NULL }
1831 }
1832};
1833
Willy Tarreau0108d902018-11-25 19:14:37 +01001834INITCALL1(STG_REGISTER, http_req_keywords_register, &http_req_actions);
1835
Willy Tarreau79e57332018-10-02 16:01:16 +02001836static struct action_kw_list http_res_actions = {
1837 .kw = {
Christopher Faulet81e20172019-12-12 16:40:30 +01001838 { "add-acl", parse_http_set_map, 1 },
1839 { "add-header", parse_http_set_header, 0 },
1840 { "allow", parse_http_allow, 0 },
1841 { "capture", parse_http_res_capture, 0 },
1842 { "del-acl", parse_http_set_map, 1 },
1843 { "del-header", parse_http_del_header, 0 },
1844 { "del-map", parse_http_set_map, 1 },
Christopher Faulete0fca292020-01-13 21:49:03 +01001845 { "deny", parse_http_deny, 0 },
Christopher Faulet81e20172019-12-12 16:40:30 +01001846 { "redirect", parse_http_redirect, 0 },
1847 { "replace-header", parse_http_replace_header, 0 },
1848 { "replace-value", parse_http_replace_header, 0 },
1849 { "set-header", parse_http_set_header, 0 },
1850 { "set-log-level", parse_http_set_log_level, 0 },
1851 { "set-map", parse_http_set_map, 1 },
1852 { "set-mark", parse_http_set_mark, 0 },
1853 { "set-nice", parse_http_set_nice, 0 },
1854 { "set-status", parse_http_set_status, 0 },
1855 { "set-tos", parse_http_set_tos, 0 },
Christopher Faulet46f95542019-12-20 10:07:22 +01001856 { "strict-mode", parse_http_strict_mode, 0 },
Christopher Faulet81e20172019-12-12 16:40:30 +01001857 { "track-sc", parse_http_track_sc, 1 },
Willy Tarreau79e57332018-10-02 16:01:16 +02001858 { NULL, NULL }
1859 }
1860};
1861
Willy Tarreau0108d902018-11-25 19:14:37 +01001862INITCALL1(STG_REGISTER, http_res_keywords_register, &http_res_actions);
Willy Tarreau79e57332018-10-02 16:01:16 +02001863
1864/*
1865 * Local variables:
1866 * c-indent-level: 8
1867 * c-basic-offset: 8
1868 * End:
1869 */