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