blob: 4f51d4a2ed58b9c0d69151c8927c13548db131e3 [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
195 HA_ATOMIC_ADD(&s->be->be_counters.denied_req, 1);
196 HA_ATOMIC_ADD(&sess->fe->fe_counters.denied_req, 1);
197 if (sess->listener && sess->listener->counters)
198 HA_ATOMIC_ADD(&sess->listener->counters->denied_req, 1);
199
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
378 if (!len) {
379 memprintf(err, "a positive 'len' argument is mandatory");
380 free(expr);
381 return ACT_RET_PRS_ERR;
382 }
383
384 hdr = calloc(1, sizeof(*hdr));
385 hdr->next = px->req_cap;
386 hdr->name = NULL; /* not a header capture */
387 hdr->namelen = 0;
388 hdr->len = len;
389 hdr->pool = create_pool("caphdr", hdr->len + 1, MEM_F_SHARED);
390 hdr->index = px->nb_req_cap++;
391
392 px->req_cap = hdr;
393 px->to_log |= LW_REQHDR;
394
395 rule->action = ACT_CUSTOM;
396 rule->action_ptr = http_action_req_capture;
397 rule->arg.cap.expr = expr;
398 rule->arg.cap.hdr = hdr;
399 }
400
401 else if (strcmp(args[cur_arg], "id") == 0) {
402 int id;
403 char *error;
404
405 cur_arg++;
406
407 if (!args[cur_arg]) {
408 memprintf(err, "missing id value");
409 free(expr);
410 return ACT_RET_PRS_ERR;
411 }
412
413 id = strtol(args[cur_arg], &error, 10);
414 if (*error != '\0') {
415 memprintf(err, "cannot parse id '%s'", args[cur_arg]);
416 free(expr);
417 return ACT_RET_PRS_ERR;
418 }
419 cur_arg++;
420
421 px->conf.args.ctx = ARGC_CAP;
422
423 rule->action = ACT_CUSTOM;
424 rule->action_ptr = http_action_req_capture_by_id;
425 rule->check_ptr = check_http_req_capture;
426 rule->arg.capid.expr = expr;
427 rule->arg.capid.idx = id;
428 }
429
430 else {
431 memprintf(err, "expects 'len' or 'id', found '%s'", args[cur_arg]);
432 free(expr);
433 return ACT_RET_PRS_ERR;
434 }
435
436 *orig_arg = cur_arg;
437 return ACT_RET_PRS_OK;
438}
439
440/* This function executes the "capture" action and store the result in a
441 * capture slot if exists. It executes a fetch expression, turns the result
442 * into a string and puts it in a capture slot. It always returns 1. If an
443 * error occurs the action is cancelled, but the rule processing continues.
444 */
445static enum act_return http_action_res_capture_by_id(struct act_rule *rule, struct proxy *px,
446 struct session *sess, struct stream *s, int flags)
447{
448 struct sample *key;
449 struct cap_hdr *h;
450 char **cap = s->res_cap;
451 struct proxy *fe = strm_fe(s);
452 int len;
453 int i;
454
455 /* Look for the original configuration. */
456 for (h = fe->rsp_cap, i = fe->nb_rsp_cap - 1;
457 h != NULL && i != rule->arg.capid.idx ;
458 i--, h = h->next);
459 if (!h)
460 return ACT_RET_CONT;
461
462 key = sample_fetch_as_type(s->be, sess, s, SMP_OPT_DIR_RES|SMP_OPT_FINAL, rule->arg.capid.expr, SMP_T_STR);
463 if (!key)
464 return ACT_RET_CONT;
465
466 if (cap[h->index] == NULL)
467 cap[h->index] = pool_alloc(h->pool);
468
469 if (cap[h->index] == NULL) /* no more capture memory */
470 return ACT_RET_CONT;
471
472 len = key->data.u.str.data;
473 if (len > h->len)
474 len = h->len;
475
476 memcpy(cap[h->index], key->data.u.str.area, len);
477 cap[h->index][len] = 0;
478 return ACT_RET_CONT;
479}
480
481/* Check an "http-response capture" action.
482 *
483 * The function returns 1 in success case, otherwise, it returns 0 and err is
484 * filled.
485 */
486static int check_http_res_capture(struct act_rule *rule, struct proxy *px, char **err)
487{
488 if (rule->action_ptr != http_action_res_capture_by_id)
489 return 1;
490
491 if (rule->arg.capid.idx >= px->nb_rsp_cap) {
492 memprintf(err, "unable to find capture id '%d' referenced by http-response capture rule",
493 rule->arg.capid.idx);
494 return 0;
495 }
496
497 return 1;
498}
499
500/* parse an "http-response capture" action. It takes a single argument which is
501 * a sample fetch expression. It stores the expression into arg->act.p[0] and
502 * the allocated hdr_cap struct od the preallocated id into arg->act.p[1].
503 * It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
504 */
505static enum act_parse_ret parse_http_res_capture(const char **args, int *orig_arg, struct proxy *px,
506 struct act_rule *rule, char **err)
507{
508 struct sample_expr *expr;
509 int cur_arg;
510 int id;
511 char *error;
512
513 for (cur_arg = *orig_arg; cur_arg < *orig_arg + 3 && *args[cur_arg]; cur_arg++)
514 if (strcmp(args[cur_arg], "if") == 0 ||
515 strcmp(args[cur_arg], "unless") == 0)
516 break;
517
518 if (cur_arg < *orig_arg + 3) {
519 memprintf(err, "expects <expression> id <idx>");
520 return ACT_RET_PRS_ERR;
521 }
522
523 cur_arg = *orig_arg;
524 expr = sample_parse_expr((char **)args, &cur_arg, px->conf.args.file, px->conf.args.line, err, &px->conf.args);
525 if (!expr)
526 return ACT_RET_PRS_ERR;
527
528 if (!(expr->fetch->val & SMP_VAL_FE_HRS_HDR)) {
529 memprintf(err,
530 "fetch method '%s' extracts information from '%s', none of which is available here",
531 args[cur_arg-1], sample_src_names(expr->fetch->use));
532 free(expr);
533 return ACT_RET_PRS_ERR;
534 }
535
536 if (!args[cur_arg] || !*args[cur_arg]) {
537 memprintf(err, "expects 'id'");
538 free(expr);
539 return ACT_RET_PRS_ERR;
540 }
541
542 if (strcmp(args[cur_arg], "id") != 0) {
543 memprintf(err, "expects 'id', found '%s'", args[cur_arg]);
544 free(expr);
545 return ACT_RET_PRS_ERR;
546 }
547
548 cur_arg++;
549
550 if (!args[cur_arg]) {
551 memprintf(err, "missing id value");
552 free(expr);
553 return ACT_RET_PRS_ERR;
554 }
555
556 id = strtol(args[cur_arg], &error, 10);
557 if (*error != '\0') {
558 memprintf(err, "cannot parse id '%s'", args[cur_arg]);
559 free(expr);
560 return ACT_RET_PRS_ERR;
561 }
562 cur_arg++;
563
564 px->conf.args.ctx = ARGC_CAP;
565
566 rule->action = ACT_CUSTOM;
567 rule->action_ptr = http_action_res_capture_by_id;
568 rule->check_ptr = check_http_res_capture;
569 rule->arg.capid.expr = expr;
570 rule->arg.capid.idx = id;
571
572 *orig_arg = cur_arg;
573 return ACT_RET_PRS_OK;
574}
575
576/************************************************************************/
577/* All supported http-request action keywords must be declared here. */
578/************************************************************************/
579
580static struct action_kw_list http_req_actions = {
581 .kw = {
582 { "capture", parse_http_req_capture },
583 { "reject", parse_http_action_reject },
584 { "set-method", parse_set_req_line },
585 { "set-path", parse_set_req_line },
586 { "set-query", parse_set_req_line },
587 { "set-uri", parse_set_req_line },
588 { NULL, NULL }
589 }
590};
591
Willy Tarreau0108d902018-11-25 19:14:37 +0100592INITCALL1(STG_REGISTER, http_req_keywords_register, &http_req_actions);
593
Willy Tarreau79e57332018-10-02 16:01:16 +0200594static struct action_kw_list http_res_actions = {
595 .kw = {
596 { "capture", parse_http_res_capture },
597 { "set-status", parse_http_set_status },
598 { NULL, NULL }
599 }
600};
601
Willy Tarreau0108d902018-11-25 19:14:37 +0100602INITCALL1(STG_REGISTER, http_res_keywords_register, &http_res_actions);
Willy Tarreau79e57332018-10-02 16:01:16 +0200603
604/*
605 * Local variables:
606 * c-indent-level: 8
607 * c-basic-offset: 8
608 * End:
609 */