blob: c8618eac589429c0e391c89ac611f576936d254d [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
19#include <common/chunk.h>
20#include <common/compat.h>
21#include <common/config.h>
22#include <common/debug.h>
23#include <common/http.h>
Willy Tarreau0108d902018-11-25 19:14:37 +010024#include <common/initcall.h>
Willy Tarreau79e57332018-10-02 16:01:16 +020025#include <common/memory.h>
26#include <common/standard.h>
27#include <common/version.h>
28
29#include <types/capture.h>
30#include <types/global.h>
31
32#include <proto/acl.h>
33#include <proto/arg.h>
Willy Tarreau61c112a2018-10-02 16:43:32 +020034#include <proto/http_rules.h>
Willy Tarreau79e57332018-10-02 16:01:16 +020035#include <proto/log.h>
36#include <proto/proto_http.h>
Willy Tarreau0f9cd7b2019-01-31 19:02:43 +010037#include <proto/stream_interface.h>
Willy Tarreau79e57332018-10-02 16:01:16 +020038
39
40/* This function executes one of the set-{method,path,query,uri} actions. It
41 * builds a string in the trash from the specified format string. It finds
42 * the action to be performed in <http.action>, previously filled by function
43 * parse_set_req_line(). The replacement action is excuted by the function
44 * http_action_set_req_line(). It always returns ACT_RET_CONT. If an error
45 * occurs the action is canceled, but the rule processing continue.
46 */
47static enum act_return http_action_set_req_line(struct act_rule *rule, struct proxy *px,
48 struct session *sess, struct stream *s, int flags)
49{
50 struct buffer *replace;
51 enum act_return ret = ACT_RET_ERR;
52
53 replace = alloc_trash_chunk();
54 if (!replace)
55 goto leave;
56
57 /* If we have to create a query string, prepare a '?'. */
58 if (rule->arg.http.action == 2)
59 replace->area[replace->data++] = '?';
60 replace->data += build_logline(s, replace->area + replace->data,
61 replace->size - replace->data,
62 &rule->arg.http.logfmt);
63
64 http_replace_req_line(rule->arg.http.action, replace->area,
65 replace->data, px, s);
66
67 ret = ACT_RET_CONT;
68
69leave:
70 free_trash_chunk(replace);
71 return ret;
72}
73
74/* parse an http-request action among :
75 * set-method
76 * set-path
77 * set-query
78 * set-uri
79 *
80 * All of them accept a single argument of type string representing a log-format.
81 * The resulting rule makes use of arg->act.p[0..1] to store the log-format list
82 * head, and p[2] to store the action as an int (0=method, 1=path, 2=query, 3=uri).
83 * It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
84 */
85static enum act_parse_ret parse_set_req_line(const char **args, int *orig_arg, struct proxy *px,
86 struct act_rule *rule, char **err)
87{
88 int cur_arg = *orig_arg;
89
90 rule->action = ACT_CUSTOM;
91
92 switch (args[0][4]) {
93 case 'm' :
94 rule->arg.http.action = 0;
95 rule->action_ptr = http_action_set_req_line;
96 break;
97 case 'p' :
98 rule->arg.http.action = 1;
99 rule->action_ptr = http_action_set_req_line;
100 break;
101 case 'q' :
102 rule->arg.http.action = 2;
103 rule->action_ptr = http_action_set_req_line;
104 break;
105 case 'u' :
106 rule->arg.http.action = 3;
107 rule->action_ptr = http_action_set_req_line;
108 break;
109 default:
110 memprintf(err, "internal error: unhandled action '%s'", args[0]);
111 return ACT_RET_PRS_ERR;
112 }
113
114 if (!*args[cur_arg] ||
115 (*args[cur_arg + 1] && strcmp(args[cur_arg + 1], "if") != 0 && strcmp(args[cur_arg + 1], "unless") != 0)) {
116 memprintf(err, "expects exactly 1 argument <format>");
117 return ACT_RET_PRS_ERR;
118 }
119
120 LIST_INIT(&rule->arg.http.logfmt);
121 px->conf.args.ctx = ARGC_HRQ;
122 if (!parse_logformat_string(args[cur_arg], px, &rule->arg.http.logfmt, LOG_OPT_HTTP,
123 (px->cap & PR_CAP_FE) ? SMP_VAL_FE_HRQ_HDR : SMP_VAL_BE_HRQ_HDR, err)) {
124 return ACT_RET_PRS_ERR;
125 }
126
127 (*orig_arg)++;
128 return ACT_RET_PRS_OK;
129}
130
131/* This function is just a compliant action wrapper for "set-status". */
132static enum act_return action_http_set_status(struct act_rule *rule, struct proxy *px,
133 struct session *sess, struct stream *s, int flags)
134{
135 http_set_status(rule->arg.status.code, rule->arg.status.reason, s);
136 return ACT_RET_CONT;
137}
138
139/* parse set-status action:
140 * This action accepts a single argument of type int representing
141 * an http status code. It returns ACT_RET_PRS_OK on success,
142 * ACT_RET_PRS_ERR on error.
143 */
144static enum act_parse_ret parse_http_set_status(const char **args, int *orig_arg, struct proxy *px,
145 struct act_rule *rule, char **err)
146{
147 char *error;
148
149 rule->action = ACT_CUSTOM;
150 rule->action_ptr = action_http_set_status;
151
152 /* Check if an argument is available */
153 if (!*args[*orig_arg]) {
154 memprintf(err, "expects 1 argument: <status>; or 3 arguments: <status> reason <fmt>");
155 return ACT_RET_PRS_ERR;
156 }
157
158 /* convert status code as integer */
159 rule->arg.status.code = strtol(args[*orig_arg], &error, 10);
160 if (*error != '\0' || rule->arg.status.code < 100 || rule->arg.status.code > 999) {
161 memprintf(err, "expects an integer status code between 100 and 999");
162 return ACT_RET_PRS_ERR;
163 }
164
165 (*orig_arg)++;
166
167 /* set custom reason string */
168 rule->arg.status.reason = NULL; // If null, we use the default reason for the status code.
169 if (*args[*orig_arg] && strcmp(args[*orig_arg], "reason") == 0 &&
170 (*args[*orig_arg + 1] && strcmp(args[*orig_arg + 1], "if") != 0 && strcmp(args[*orig_arg + 1], "unless") != 0)) {
171 (*orig_arg)++;
172 rule->arg.status.reason = strdup(args[*orig_arg]);
173 (*orig_arg)++;
174 }
175
176 return ACT_RET_PRS_OK;
177}
178
179/* This function executes the "reject" HTTP action. It clears the request and
180 * response buffer without sending any response. It can be useful as an HTTP
181 * alternative to the silent-drop action to defend against DoS attacks, and may
182 * also be used with HTTP/2 to close a connection instead of just a stream.
183 * The txn status is unchanged, indicating no response was sent. The termination
184 * flags will indicate "PR". It always returns ACT_RET_STOP.
185 */
186static enum act_return http_action_reject(struct act_rule *rule, struct proxy *px,
187 struct session *sess, struct stream *s, int flags)
188{
Willy Tarreau0f9cd7b2019-01-31 19:02:43 +0100189 si_must_kill_conn(chn_prod(&s->req));
Willy Tarreau79e57332018-10-02 16:01:16 +0200190 channel_abort(&s->req);
191 channel_abort(&s->res);
192 s->req.analysers = 0;
193 s->res.analysers = 0;
194
Olivier Houcharda798bf52019-03-08 18:52:00 +0100195 _HA_ATOMIC_ADD(&s->be->be_counters.denied_req, 1);
196 _HA_ATOMIC_ADD(&sess->fe->fe_counters.denied_req, 1);
Willy Tarreau79e57332018-10-02 16:01:16 +0200197 if (sess->listener && sess->listener->counters)
Olivier Houcharda798bf52019-03-08 18:52:00 +0100198 _HA_ATOMIC_ADD(&sess->listener->counters->denied_req, 1);
Willy Tarreau79e57332018-10-02 16:01:16 +0200199
200 if (!(s->flags & SF_ERR_MASK))
201 s->flags |= SF_ERR_PRXCOND;
202 if (!(s->flags & SF_FINST_MASK))
203 s->flags |= SF_FINST_R;
204
205 return ACT_RET_CONT;
206}
207
208/* parse the "reject" action:
209 * This action takes no argument and returns ACT_RET_PRS_OK on success,
210 * ACT_RET_PRS_ERR on error.
211 */
212static enum act_parse_ret parse_http_action_reject(const char **args, int *orig_arg, struct proxy *px,
213 struct act_rule *rule, char **err)
214{
215 rule->action = ACT_CUSTOM;
216 rule->action_ptr = http_action_reject;
217 return ACT_RET_PRS_OK;
218}
219
220/* This function executes the "capture" action. It executes a fetch expression,
221 * turns the result into a string and puts it in a capture slot. It always
222 * returns 1. If an error occurs the action is cancelled, but the rule
223 * processing continues.
224 */
225static enum act_return http_action_req_capture(struct act_rule *rule, struct proxy *px,
226 struct session *sess, struct stream *s, int flags)
227{
228 struct sample *key;
229 struct cap_hdr *h = rule->arg.cap.hdr;
230 char **cap = s->req_cap;
231 int len;
232
233 key = sample_fetch_as_type(s->be, sess, s, SMP_OPT_DIR_REQ|SMP_OPT_FINAL, rule->arg.cap.expr, SMP_T_STR);
234 if (!key)
235 return ACT_RET_CONT;
236
237 if (cap[h->index] == NULL)
238 cap[h->index] = pool_alloc(h->pool);
239
240 if (cap[h->index] == NULL) /* no more capture memory */
241 return ACT_RET_CONT;
242
243 len = key->data.u.str.data;
244 if (len > h->len)
245 len = h->len;
246
247 memcpy(cap[h->index], key->data.u.str.area, len);
248 cap[h->index][len] = 0;
249 return ACT_RET_CONT;
250}
251
252/* This function executes the "capture" action and store the result in a
253 * capture slot if exists. It executes a fetch expression, turns the result
254 * into a string and puts it in a capture slot. It always returns 1. If an
255 * error occurs the action is cancelled, but the rule processing continues.
256 */
257static enum act_return http_action_req_capture_by_id(struct act_rule *rule, struct proxy *px,
258 struct session *sess, struct stream *s, int flags)
259{
260 struct sample *key;
261 struct cap_hdr *h;
262 char **cap = s->req_cap;
263 struct proxy *fe = strm_fe(s);
264 int len;
265 int i;
266
267 /* Look for the original configuration. */
268 for (h = fe->req_cap, i = fe->nb_req_cap - 1;
269 h != NULL && i != rule->arg.capid.idx ;
270 i--, h = h->next);
271 if (!h)
272 return ACT_RET_CONT;
273
274 key = sample_fetch_as_type(s->be, sess, s, SMP_OPT_DIR_REQ|SMP_OPT_FINAL, rule->arg.capid.expr, SMP_T_STR);
275 if (!key)
276 return ACT_RET_CONT;
277
278 if (cap[h->index] == NULL)
279 cap[h->index] = pool_alloc(h->pool);
280
281 if (cap[h->index] == NULL) /* no more capture memory */
282 return ACT_RET_CONT;
283
284 len = key->data.u.str.data;
285 if (len > h->len)
286 len = h->len;
287
288 memcpy(cap[h->index], key->data.u.str.area, len);
289 cap[h->index][len] = 0;
290 return ACT_RET_CONT;
291}
292
293/* Check an "http-request capture" action.
294 *
295 * The function returns 1 in success case, otherwise, it returns 0 and err is
296 * filled.
297 */
298static int check_http_req_capture(struct act_rule *rule, struct proxy *px, char **err)
299{
300 if (rule->action_ptr != http_action_req_capture_by_id)
301 return 1;
302
303 if (rule->arg.capid.idx >= px->nb_req_cap) {
304 memprintf(err, "unable to find capture id '%d' referenced by http-request capture rule",
305 rule->arg.capid.idx);
306 return 0;
307 }
308
309 return 1;
310}
311
312/* parse an "http-request capture" action. It takes a single argument which is
313 * a sample fetch expression. It stores the expression into arg->act.p[0] and
314 * the allocated hdr_cap struct or the preallocated "id" into arg->act.p[1].
315 * It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
316 */
317static enum act_parse_ret parse_http_req_capture(const char **args, int *orig_arg, struct proxy *px,
318 struct act_rule *rule, char **err)
319{
320 struct sample_expr *expr;
321 struct cap_hdr *hdr;
322 int cur_arg;
323 int len = 0;
324
325 for (cur_arg = *orig_arg; cur_arg < *orig_arg + 3 && *args[cur_arg]; cur_arg++)
326 if (strcmp(args[cur_arg], "if") == 0 ||
327 strcmp(args[cur_arg], "unless") == 0)
328 break;
329
330 if (cur_arg < *orig_arg + 3) {
331 memprintf(err, "expects <expression> [ 'len' <length> | id <idx> ]");
332 return ACT_RET_PRS_ERR;
333 }
334
335 cur_arg = *orig_arg;
336 expr = sample_parse_expr((char **)args, &cur_arg, px->conf.args.file, px->conf.args.line, err, &px->conf.args);
337 if (!expr)
338 return ACT_RET_PRS_ERR;
339
340 if (!(expr->fetch->val & SMP_VAL_FE_HRQ_HDR)) {
341 memprintf(err,
342 "fetch method '%s' extracts information from '%s', none of which is available here",
343 args[cur_arg-1], sample_src_names(expr->fetch->use));
344 free(expr);
345 return ACT_RET_PRS_ERR;
346 }
347
348 if (!args[cur_arg] || !*args[cur_arg]) {
349 memprintf(err, "expects 'len or 'id'");
350 free(expr);
351 return ACT_RET_PRS_ERR;
352 }
353
354 if (strcmp(args[cur_arg], "len") == 0) {
355 cur_arg++;
356
357 if (!(px->cap & PR_CAP_FE)) {
358 memprintf(err, "proxy '%s' has no frontend capability", px->id);
359 return ACT_RET_PRS_ERR;
360 }
361
362 px->conf.args.ctx = ARGC_CAP;
363
364 if (!args[cur_arg]) {
365 memprintf(err, "missing length value");
366 free(expr);
367 return ACT_RET_PRS_ERR;
368 }
369 /* we copy the table name for now, it will be resolved later */
370 len = atoi(args[cur_arg]);
371 if (len <= 0) {
372 memprintf(err, "length must be > 0");
373 free(expr);
374 return ACT_RET_PRS_ERR;
375 }
376 cur_arg++;
377
Willy Tarreau79e57332018-10-02 16:01:16 +0200378 hdr = calloc(1, sizeof(*hdr));
379 hdr->next = px->req_cap;
380 hdr->name = NULL; /* not a header capture */
381 hdr->namelen = 0;
382 hdr->len = len;
383 hdr->pool = create_pool("caphdr", hdr->len + 1, MEM_F_SHARED);
384 hdr->index = px->nb_req_cap++;
385
386 px->req_cap = hdr;
387 px->to_log |= LW_REQHDR;
388
389 rule->action = ACT_CUSTOM;
390 rule->action_ptr = http_action_req_capture;
391 rule->arg.cap.expr = expr;
392 rule->arg.cap.hdr = hdr;
393 }
394
395 else if (strcmp(args[cur_arg], "id") == 0) {
396 int id;
397 char *error;
398
399 cur_arg++;
400
401 if (!args[cur_arg]) {
402 memprintf(err, "missing id value");
403 free(expr);
404 return ACT_RET_PRS_ERR;
405 }
406
407 id = strtol(args[cur_arg], &error, 10);
408 if (*error != '\0') {
409 memprintf(err, "cannot parse id '%s'", args[cur_arg]);
410 free(expr);
411 return ACT_RET_PRS_ERR;
412 }
413 cur_arg++;
414
415 px->conf.args.ctx = ARGC_CAP;
416
417 rule->action = ACT_CUSTOM;
418 rule->action_ptr = http_action_req_capture_by_id;
419 rule->check_ptr = check_http_req_capture;
420 rule->arg.capid.expr = expr;
421 rule->arg.capid.idx = id;
422 }
423
424 else {
425 memprintf(err, "expects 'len' or 'id', found '%s'", args[cur_arg]);
426 free(expr);
427 return ACT_RET_PRS_ERR;
428 }
429
430 *orig_arg = cur_arg;
431 return ACT_RET_PRS_OK;
432}
433
434/* This function executes the "capture" action and store the result in a
435 * capture slot if exists. It executes a fetch expression, turns the result
436 * into a string and puts it in a capture slot. It always returns 1. If an
437 * error occurs the action is cancelled, but the rule processing continues.
438 */
439static enum act_return http_action_res_capture_by_id(struct act_rule *rule, struct proxy *px,
440 struct session *sess, struct stream *s, int flags)
441{
442 struct sample *key;
443 struct cap_hdr *h;
444 char **cap = s->res_cap;
445 struct proxy *fe = strm_fe(s);
446 int len;
447 int i;
448
449 /* Look for the original configuration. */
450 for (h = fe->rsp_cap, i = fe->nb_rsp_cap - 1;
451 h != NULL && i != rule->arg.capid.idx ;
452 i--, h = h->next);
453 if (!h)
454 return ACT_RET_CONT;
455
456 key = sample_fetch_as_type(s->be, sess, s, SMP_OPT_DIR_RES|SMP_OPT_FINAL, rule->arg.capid.expr, SMP_T_STR);
457 if (!key)
458 return ACT_RET_CONT;
459
460 if (cap[h->index] == NULL)
461 cap[h->index] = pool_alloc(h->pool);
462
463 if (cap[h->index] == NULL) /* no more capture memory */
464 return ACT_RET_CONT;
465
466 len = key->data.u.str.data;
467 if (len > h->len)
468 len = h->len;
469
470 memcpy(cap[h->index], key->data.u.str.area, len);
471 cap[h->index][len] = 0;
472 return ACT_RET_CONT;
473}
474
475/* Check an "http-response capture" action.
476 *
477 * The function returns 1 in success case, otherwise, it returns 0 and err is
478 * filled.
479 */
480static int check_http_res_capture(struct act_rule *rule, struct proxy *px, char **err)
481{
482 if (rule->action_ptr != http_action_res_capture_by_id)
483 return 1;
484
485 if (rule->arg.capid.idx >= px->nb_rsp_cap) {
486 memprintf(err, "unable to find capture id '%d' referenced by http-response capture rule",
487 rule->arg.capid.idx);
488 return 0;
489 }
490
491 return 1;
492}
493
494/* parse an "http-response capture" action. It takes a single argument which is
495 * a sample fetch expression. It stores the expression into arg->act.p[0] and
496 * the allocated hdr_cap struct od the preallocated id into arg->act.p[1].
497 * It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
498 */
499static enum act_parse_ret parse_http_res_capture(const char **args, int *orig_arg, struct proxy *px,
500 struct act_rule *rule, char **err)
501{
502 struct sample_expr *expr;
503 int cur_arg;
504 int id;
505 char *error;
506
507 for (cur_arg = *orig_arg; cur_arg < *orig_arg + 3 && *args[cur_arg]; cur_arg++)
508 if (strcmp(args[cur_arg], "if") == 0 ||
509 strcmp(args[cur_arg], "unless") == 0)
510 break;
511
512 if (cur_arg < *orig_arg + 3) {
513 memprintf(err, "expects <expression> id <idx>");
514 return ACT_RET_PRS_ERR;
515 }
516
517 cur_arg = *orig_arg;
518 expr = sample_parse_expr((char **)args, &cur_arg, px->conf.args.file, px->conf.args.line, err, &px->conf.args);
519 if (!expr)
520 return ACT_RET_PRS_ERR;
521
522 if (!(expr->fetch->val & SMP_VAL_FE_HRS_HDR)) {
523 memprintf(err,
524 "fetch method '%s' extracts information from '%s', none of which is available here",
525 args[cur_arg-1], sample_src_names(expr->fetch->use));
526 free(expr);
527 return ACT_RET_PRS_ERR;
528 }
529
530 if (!args[cur_arg] || !*args[cur_arg]) {
531 memprintf(err, "expects 'id'");
532 free(expr);
533 return ACT_RET_PRS_ERR;
534 }
535
536 if (strcmp(args[cur_arg], "id") != 0) {
537 memprintf(err, "expects 'id', found '%s'", args[cur_arg]);
538 free(expr);
539 return ACT_RET_PRS_ERR;
540 }
541
542 cur_arg++;
543
544 if (!args[cur_arg]) {
545 memprintf(err, "missing id value");
546 free(expr);
547 return ACT_RET_PRS_ERR;
548 }
549
550 id = strtol(args[cur_arg], &error, 10);
551 if (*error != '\0') {
552 memprintf(err, "cannot parse id '%s'", args[cur_arg]);
553 free(expr);
554 return ACT_RET_PRS_ERR;
555 }
556 cur_arg++;
557
558 px->conf.args.ctx = ARGC_CAP;
559
560 rule->action = ACT_CUSTOM;
561 rule->action_ptr = http_action_res_capture_by_id;
562 rule->check_ptr = check_http_res_capture;
563 rule->arg.capid.expr = expr;
564 rule->arg.capid.idx = id;
565
566 *orig_arg = cur_arg;
567 return ACT_RET_PRS_OK;
568}
569
570/************************************************************************/
571/* All supported http-request action keywords must be declared here. */
572/************************************************************************/
573
574static struct action_kw_list http_req_actions = {
575 .kw = {
576 { "capture", parse_http_req_capture },
577 { "reject", parse_http_action_reject },
578 { "set-method", parse_set_req_line },
579 { "set-path", parse_set_req_line },
580 { "set-query", parse_set_req_line },
581 { "set-uri", parse_set_req_line },
582 { NULL, NULL }
583 }
584};
585
Willy Tarreau0108d902018-11-25 19:14:37 +0100586INITCALL1(STG_REGISTER, http_req_keywords_register, &http_req_actions);
587
Willy Tarreau79e57332018-10-02 16:01:16 +0200588static struct action_kw_list http_res_actions = {
589 .kw = {
590 { "capture", parse_http_res_capture },
591 { "set-status", parse_http_set_status },
592 { NULL, NULL }
593 }
594};
595
Willy Tarreau0108d902018-11-25 19:14:37 +0100596INITCALL1(STG_REGISTER, http_res_keywords_register, &http_res_actions);
Willy Tarreau79e57332018-10-02 16:01:16 +0200597
598/*
599 * Local variables:
600 * c-indent-level: 8
601 * c-basic-offset: 8
602 * End:
603 */