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