blob: 3875d3b748d7b614b2592ddd7e197b461c2eb08e [file] [log] [blame]
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001/*
2 * HTTP extensions logic and helpers
3 *
4 * Copyright 2022 HAProxy Technologies
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.1 of the License, or (at your option) any later version.
10 *
11 */
12
13/* forwarded header (7239 RFC) */
14
15#include <haproxy/sample.h>
16#include <haproxy/http_htx.h>
17#include <haproxy/http_ext.h>
18#include <haproxy/chunk.h>
19#include <haproxy/stream.h>
20#include <haproxy/proxy.h>
21#include <haproxy/sc_strm.h>
22#include <haproxy/obj_type.h>
23#include <haproxy/cfgparse.h>
Aurelien DARRAGON82faad12022-12-29 18:32:19 +010024#include <haproxy/arg.h>
25#include <haproxy/initcall.h>
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +010026#include <haproxy/tools.h>
27
28/* check if char is a valid obfuscated identifier char
29 * (according to 7239 RFC)
30 * Returns non zero value for valid char
31 */
32static int http_7239_valid_obfsc(char c)
33{
34 return (isalnum((unsigned char)c) ||
35 (c == '.' || c == '-' || c == '_'));
36}
37
38/*
39 * =========== ANALYZE ===========
40 * below are http process/ana helpers
41 */
42
43/* checks if <input> contains rfc7239 compliant port
44 * Returns 1 for success and 0 for failure
45 * if <port> is not NULL, it will be set to the extracted value contained
46 * in <input>
47 * <input> will be consumed accordingly (parsed/extracted characters are
48 * removed from <input>)
49 */
50static inline int http_7239_extract_port(struct ist *input, uint16_t *port)
51{
52 char *start = istptr(*input);
53 uint32_t port_cast = 0;
54 int it = 0;
55
56 /* strtol does not support non-null terminated str,
57 * we extract port ourselves
58 */
59 while (it < istlen(*input) &&
60 isdigit((unsigned char)start[it])) {
61 port_cast = (port_cast * 10) + (start[it] - '0');
62 if (port_cast > 65535)
63 return 0; /* invalid port */
64 it += 1;
65 }
66 if (!port_cast)
67 return 0; /* invalid port */
68 /* ok */
69 if (port)
70 *port = (uint16_t)port_cast;
71 *input = istadv(*input, it);
72 return 1;
73}
74
75/* checks if <input> contains rfc7239 compliant obfuscated identifier
76 * Returns 1 for success and 0 for failure
77 * if <obfs> is not NULL, it will be set to the extracted value contained
78 * in <input>
79 * <input> will be consumed accordingly (parsed/extracted characters are
80 * removed from <input>)
81 */
82static inline int http_7239_extract_obfs(struct ist *input, struct ist *obfs)
83{
84 int it = 0;
85
86 if (obfs)
87 obfs->ptr = input->ptr;
88
89 while (it < istlen(*input) && istptr(*input)[it] != ';') {
90 if (!http_7239_valid_obfsc(istptr(*input)[it]))
91 break; /* end of obfs token */
92 it += 1;
93 }
94 if (obfs)
95 obfs->len = it;
96 *input = istadv(*input, it);
97 return !!it;
98}
99
100/* checks if <input> contains rfc7239 compliant IPV4 address
101 * Returns 1 for success and 0 for failure
102 * if <ip> is not NULL, it will be set to the extracted value contained
103 * in <input>
104 * <input> will be consumed accordingly (parsed/extracted characters are
105 * removed from <input>)
106 */
107static inline int http_7239_extract_ipv4(struct ist *input, struct in_addr *ip)
108{
109 char ip4[INET_ADDRSTRLEN];
110 unsigned char buf[sizeof(struct in_addr)];
111 int it = 0;
112
113 /* extract ipv4 addr */
114 while (it < istlen(*input) && it < (sizeof(ip4) - 1)) {
115 if (!isdigit((unsigned char)istptr(*input)[it]) &&
116 istptr(*input)[it] != '.')
117 break; /* no more ip4 char */
118 ip4[it] = istptr(*input)[it];
119 it += 1;
120 }
121 ip4[it] = 0;
122 if (inet_pton(AF_INET, ip4, buf) != 1)
123 return 0; /* invalid ip4 addr */
124 /* ok */
125 if (ip)
126 memcpy(ip, buf, sizeof(buf));
127 *input = istadv(*input, it);
128 return 1;
129}
130
131/* checks if <input> contains rfc7239 compliant IPV6 address
132 * assuming input.len >= 1 and first char is '['
133 * Returns 1 for success and 0 for failure
134 * if <ip> is not NULL, it will be set to the extracted value contained
135 * in <input>
136 * <input> will be consumed accordingly (parsed/extracted characters are
137 * removed from <input>)
138 */
139static inline int http_7239_extract_ipv6(struct ist *input, struct in6_addr *ip)
140{
141 char ip6[INET6_ADDRSTRLEN];
142 unsigned char buf[sizeof(struct in6_addr)];
143 int it = 0;
144
145 *input = istnext(*input); /* skip '[' leading char */
146 /* extract ipv6 addr */
147 while (it < istlen(*input) &&
148 it < (sizeof(ip6) - 1)) {
149 if (!isalnum((unsigned char)istptr(*input)[it]) &&
150 istptr(*input)[it] != ':')
151 break; /* no more ip6 char */
152 ip6[it] = istptr(*input)[it];
153 it += 1;
154 }
155 ip6[it] = 0;
156 if ((istlen(*input)-it) < 1 || istptr(*input)[it] != ']')
157 return 0; /* missing ending "]" char */
158 it += 1;
159 if (inet_pton(AF_INET6, ip6, buf) != 1)
160 return 0; /* invalid ip6 addr */
161 /* ok */
162 if (ip)
163 memcpy(ip, buf, sizeof(buf));
164 *input = istadv(*input, it);
165 return 1;
166}
167
168/* checks if <input> contains rfc7239 compliant host
169 * <quoted> is used to determine if the current input is being extracted
170 * from a quoted (non zero) or unquoted (zero) token, as the parsing rules
171 * differ wheteher the input is quoted or not according to the rfc.
172 * Returns 1 for success and 0 for failure
173 * if <host> is not NULL, it will be set to the extracted value contained
174 * in <input>
175 * <input> will be consumed accordingly (parsed/extracted characters are
176 * removed from <input>)
177 */
178static inline int http_7239_extract_host(struct ist *input, struct ist *host, int quoted)
179{
180 if (istlen(*input) < 1)
181 return 0; /* invalid input */
182
183 if (host)
184 host->ptr = input->ptr;
185
186 if (quoted && *istptr(*input) == '[') {
187 /* raw ipv6 address */
188 if (!http_7239_extract_ipv6(input, NULL))
189 return 0; /* invalid addr */
190 }
191 else {
192 /* ipv4 or dns */
193 while (istlen(*input)) {
194 if (!isalnum((unsigned char)*istptr(*input)) &&
195 *istptr(*input) != '.')
196 break; /* end of hostname token */
197 *input = istnext(*input);
198 }
199 }
200 if (istlen(*input) < 1 || *istptr(*input) != ':') {
201 goto out; /* no optional port provided */
202 }
203 if (!quoted)
204 return 0; /* not supported */
205 *input = istnext(*input); /* skip ':' */
206 /* validate port */
207 if (!http_7239_extract_port(input, NULL))
208 return 0; /* invalid port */
209 out:
210 if (host)
211 host->len = (input->ptr - host->ptr);
212 return 1;
213}
214
215/* checks if <input> contains rfc7239 compliant nodename
216 * <quoted> is used to determine if the current input is being extracted
217 * from a quoted (non zero) or unquoted (zero) token, as the parsing rules
218 * differ wheteher the input is quoted or not according to the rfc.
219 * Returns 1 for success and 0 for failure
220 * if <nodename> is not NULL, it will be set to the extracted value contained
221 * in <input>
222 * <input> will be consumed accordingly (parsed/extracted characters are
223 * removed from <input>)
224 */
225static inline int http_7239_extract_nodename(struct ist *input, struct forwarded_header_nodename *nodename, int quoted)
226{
227 if (istlen(*input) < 1)
228 return 0; /* invalid input */
229 if (*istptr(*input) == '_') {
230 struct ist *obfs = NULL;
231
232 /* obfuscated nodename */
233 *input = istnext(*input); /* skip '_' */
234 if (nodename) {
235 nodename->type = FORWARDED_HEADER_OBFS;
236 obfs = &nodename->obfs;
237 }
238 if (!http_7239_extract_obfs(input, obfs))
239 return 0; /* invalid obfs */
240 } else if (*istptr(*input) == 'u') {
241 /* "unknown" nodename? */
242 if (istlen(*input) < 7 ||
243 strncmp("unknown", istptr(*input), 7))
244 return 0; /* syntax error */
245 *input = istadv(*input, 7); /* skip "unknown" */
246 if (nodename)
247 nodename->type = FORWARDED_HEADER_UNK;
248 } else if (quoted && *istptr(*input) == '[') {
249 struct in6_addr *ip6 = NULL;
250
251 /* ipv6 address */
252 if (nodename) {
253 struct sockaddr_in6 *addr = (void *)&nodename->ip;
254
255 ip6 = &addr->sin6_addr;
256 addr->sin6_family = AF_INET6;
257 nodename->type = FORWARDED_HEADER_IP;
258 }
259 if (!http_7239_extract_ipv6(input, ip6))
260 return 0; /* invalid ip6 */
261 } else if (*istptr(*input)) {
262 struct in_addr *ip = NULL;
263
264 /* ipv4 address */
265 if (nodename) {
266 struct sockaddr_in *addr = (void *)&nodename->ip;
267
268 ip = &addr->sin_addr;
269 addr->sin_family = AF_INET;
270 nodename->type = FORWARDED_HEADER_IP;
271 }
272 if (!http_7239_extract_ipv4(input, ip))
273 return 0; /* invalid ip */
274 } else
275 return 0; /* unexpected char */
276
277 /* ok */
278 return 1;
279}
280
281/* checks if <input> contains rfc7239 compliant nodeport
282 * <quoted> is used to determine if the current input is being extracted
283 * from a quoted (non zero) or unquoted (zero) token, as the parsing rules
284 * differ wheteher the input is quoted or not according to the rfc.
285 * Returns 1 for success and 0 for failure
286 * if <nodeport> is not NULL, it will be set to the extracted value contained
287 * in <input>
288 * <input> will be consumed accordingly (parsed/extracted characters are
289 * removed from <input>)
290 */
291static inline int http_7239_extract_nodeport(struct ist *input, struct forwarded_header_nodeport *nodeport)
292{
293 if (*istptr(*input) == '_') {
294 struct ist *obfs = NULL;
295
296 /* obfuscated nodeport */
297 *input = istnext(*input); /* skip '_' */
298 if (nodeport) {
299 nodeport->type = FORWARDED_HEADER_OBFS;
300 obfs = &nodeport->obfs;
301 }
302 if (!http_7239_extract_obfs(input, obfs))
303 return 0; /* invalid obfs */
304 } else {
305 uint16_t *port = NULL;
306
307 /* normal port */
308 if (nodeport) {
309 nodeport->type = FORWARDED_HEADER_PORT;
310 port = &nodeport->port;
311 }
312 if (!http_7239_extract_port(input, port))
313 return 0; /* invalid port */
314 }
315 /* ok */
316 return 1;
317}
318
319/* checks if <input> contains rfc7239 compliant node (nodename:nodeport token)
320 * <quoted> is used to determine if the current input is being extracted
321 * from a quoted (non zero) or unquoted (zero) token, as the parsing rules
322 * differ wheteher the input is quoted or not according to the rfc.
323 * Returns 1 for success and 0 for failure
324 * if <node> is not NULL, it will be set to the extracted value contained
325 * in <input>
326 * <input> will be consumed accordingly (parsed/extracted characters are
327 * removed from <input>)
328 */
329static inline int http_7239_extract_node(struct ist *input, struct forwarded_header_node *node, int quoted)
330{
331 struct forwarded_header_nodename *nodename = NULL;
332 struct forwarded_header_nodeport *nodeport = NULL;
333
334 if (node) {
335 nodename = &node->nodename;
336 nodeport = &node->nodeport;
337 node->raw.ptr = input->ptr;
338 }
339 if (!http_7239_extract_nodename(input, nodename, quoted))
340 return 0; /* invalid nodename */
341 if (istlen(*input) < 1 || *istptr(*input) != ':') {
342 if (node)
343 node->nodeport.type = FORWARDED_HEADER_UNK;
344 goto out; /* no optional port provided */
345 }
346 if (!quoted)
347 return 0; /* not supported */
348 *input = istnext(*input);
349 if (!http_7239_extract_nodeport(input, nodeport))
350 return 0; /* invalid nodeport */
351 out:
352 /* ok */
353 if (node)
354 node->raw.len = input->ptr - node->raw.ptr;
355 return 1;
356}
357
358static inline int _forwarded_header_save_ctx(struct forwarded_header_ctx *ctx, int current_step, int required_steps)
359{
360 return (ctx && (current_step & required_steps));
361}
362
363static inline void _forwarded_header_quote_expected(struct ist *hdr, uint8_t *quoted)
364{
365 if (istlen(*hdr) > 0 && *istptr(*hdr) == '"') {
366 *quoted = 1;
367 /* node is quoted, we must find corresponding
368 * ending quote at the end of the token
369 */
370 *hdr = istnext(*hdr); /* skip quote */
371 }
372}
373
374/* checks if current header <hdr> is RFC 7239 compliant and can be "trusted".
375 * function will stop parsing as soon as every <required_steps> have
376 * been validated or error is encountered.
377 * Provide FORWARDED_HEADER_ALL for a full header validating spectrum.
378 * You may provide limited scope to perform quick searches on specific attributes
379 * If <ctx> is provided (not NULL), parsed attributes will be stored according to
380 * their types, allowing you to extract some useful information from the header.
381 * Returns 0 on failure and <validated_steps> bitfield on success.
382 */
383int http_validate_7239_header(struct ist hdr, int required_steps, struct forwarded_header_ctx *ctx)
384{
385 int validated_steps = 0;
386 int current_step = 0;
387 uint8_t first = 1;
388 uint8_t quoted = 0;
389
390 while (istlen(hdr) && (required_steps & ~validated_steps)) {
391 if (!first) {
392 if (*istptr(hdr) == ';')
393 hdr = istnext(hdr); /* skip ';' */
394 else
395 goto not_ok; /* unexpected char */
396 }
397 else
398 first = 0;
399
400 if (!(validated_steps & FORWARDED_HEADER_FOR) && istlen(hdr) > 4 &&
401 strncmp("for=", istptr(hdr), 4) == 0) {
402 struct forwarded_header_node *node = NULL;
403
404 /* for parameter */
405 current_step = FORWARDED_HEADER_FOR;
406 hdr = istadv(hdr, 4); /* skip "for=" */
407 _forwarded_header_quote_expected(&hdr, &quoted);
408 if (_forwarded_header_save_ctx(ctx, current_step, required_steps))
409 node = &ctx->nfor;
410 /* validate node */
411 if (!http_7239_extract_node(&hdr, node, quoted))
412 goto not_ok; /* invalid node */
413 }
414 else if (!(validated_steps & FORWARDED_HEADER_BY) && istlen(hdr) > 3 &&
415 strncmp("by=", istptr(hdr), 3) == 0) {
416 struct forwarded_header_node *node = NULL;
417
418 /* by parameter */
419 current_step = FORWARDED_HEADER_BY;
420 hdr = istadv(hdr, 3); /* skip "by=" */
421 _forwarded_header_quote_expected(&hdr, &quoted);
422 if (_forwarded_header_save_ctx(ctx, current_step, required_steps))
423 node = &ctx->nby;
424 /* validate node */
425 if (!http_7239_extract_node(&hdr, node, quoted))
426 goto not_ok; /* invalid node */
427 }
428 else if (!(validated_steps & FORWARDED_HEADER_HOST) && istlen(hdr) > 5 &&
429 strncmp("host=", istptr(hdr), 5) == 0) {
430 struct ist *host = NULL;
431
432 /* host parameter */
433 current_step = FORWARDED_HEADER_HOST;
434 hdr = istadv(hdr, 5); /* skip "host=" */
435 _forwarded_header_quote_expected(&hdr, &quoted);
436 if (_forwarded_header_save_ctx(ctx, current_step, required_steps))
437 host = &ctx->host;
438 /* validate host */
439 if (!http_7239_extract_host(&hdr, host, quoted))
440 goto not_ok; /* invalid host */
441 }
442 else if (!(validated_steps & FORWARDED_HEADER_PROTO) && istlen(hdr) > 6 &&
443 strncmp("proto=", istptr(hdr), 6) == 0) {
444 /* proto parameter */
445 current_step = FORWARDED_HEADER_PROTO;
446 hdr = istadv(hdr, 6); /* skip "proto=" */
447 /* validate proto (only common used http|https are supported for now) */
448 if (istlen(hdr) < 4 || strncmp("http", istptr(hdr), 4))
449 goto not_ok;
450 hdr = istadv(hdr, 4); /* skip "http" */
451 if (istlen(hdr) && *istptr(hdr) == 's') {
452 hdr = istnext(hdr);
453 if (_forwarded_header_save_ctx(ctx, current_step, required_steps))
454 ctx->proto = FORWARDED_HEADER_HTTPS;
455 } else if (_forwarded_header_save_ctx(ctx, current_step, required_steps))
456 ctx->proto = FORWARDED_HEADER_HTTP;
457 /* rfc allows for potential proto quoting, but we don't support
458 * it: it is not common usage
459 */
460 }
461 else {
462 /* not supported
463 * rfc allows for upcoming extensions
464 * but obviously, we can't trust them
465 * as they are not yet standardized
466 */
467
468 goto not_ok;
469 }
470 /* quote check */
471 if (quoted) {
472 if (istlen(hdr) < 1 || *istptr(hdr) != '"') {
473 /* matching ending quote not found */
474 goto not_ok;
475 }
476 hdr = istnext(hdr); /* skip ending quote */
477 quoted = 0; /* reset */
478 }
479 validated_steps |= current_step;
480 }
481
482 return validated_steps;
483
484 not_ok:
485 return 0;
486}
487
Aurelien DARRAGON8436c912023-01-30 09:28:57 +0100488static inline void _7239_print_ip6(struct buffer *out, struct in6_addr *ip6_addr, int quoted)
489{
490 char pn[INET6_ADDRSTRLEN];
491
492 inet_ntop(AF_INET6,
493 ip6_addr,
494 pn, sizeof(pn));
495 if (!quoted)
496 chunk_appendf(out, "\""); /* explicit quoting required for ipv6 */
497 chunk_appendf(out, "[%s]", pn);
498}
499
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +0100500static inline void http_build_7239_header_nodename(struct buffer *out,
501 struct stream *s, struct proxy *curproxy,
502 const struct sockaddr_storage *addr,
503 struct http_ext_7239_forby *forby)
504{
505 struct in6_addr *ip6_addr;
Aurelien DARRAGON8436c912023-01-30 09:28:57 +0100506 int quoted = !!forby->np_mode;
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +0100507
508 if (forby->nn_mode == HTTP_7239_FORBY_ORIG) {
509 if (addr && addr->ss_family == AF_INET) {
510 unsigned char *pn = (unsigned char *)&((struct sockaddr_in *)addr)->sin_addr;
511
512 chunk_appendf(out, "%d.%d.%d.%d", pn[0], pn[1], pn[2], pn[3]);
513 }
514 else if (addr && addr->ss_family == AF_INET6) {
515 ip6_addr = &((struct sockaddr_in6 *)addr)->sin6_addr;
Aurelien DARRAGON8436c912023-01-30 09:28:57 +0100516 _7239_print_ip6(out, ip6_addr, quoted);
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +0100517 }
518 /* else: not supported */
519 }
520 else if (forby->nn_mode == HTTP_7239_FORBY_SMP && forby->nn_expr) {
521 struct sample *smp;
522
523 smp = sample_process(curproxy, s->sess, s,
524 SMP_OPT_DIR_REQ | SMP_OPT_FINAL, forby->nn_expr, NULL);
525
526 if (smp) {
527 if (smp->data.type == SMP_T_IPV6) {
528 /* smp is valid IP6, print with RFC compliant output */
529 ip6_addr = &smp->data.u.ipv6;
Aurelien DARRAGON8436c912023-01-30 09:28:57 +0100530 _7239_print_ip6(out, ip6_addr, quoted);
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +0100531 }
Aurelien DARRAGON8436c912023-01-30 09:28:57 +0100532 else if (sample_casts[smp->data.type][SMP_T_STR] &&
533 sample_casts[smp->data.type][SMP_T_STR](smp)) {
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +0100534 struct ist validate_n = ist2(smp->data.u.str.area, smp->data.u.str.data);
535 struct ist validate_o = ist2(smp->data.u.str.area, smp->data.u.str.data);
536 struct forwarded_header_nodename nodename;
537
538 /* validate nodename */
539 if (http_7239_extract_nodename(&validate_n, &nodename, 1) &&
540 !istlen(validate_n)) {
541 if (nodename.type == FORWARDED_HEADER_IP &&
542 nodename.ip.ss_family == AF_INET6) {
543 /* special care needed for valid ip6 nodename (quoting) */
544 ip6_addr = &((struct sockaddr_in6 *)&nodename.ip)->sin6_addr;
Aurelien DARRAGON8436c912023-01-30 09:28:57 +0100545 _7239_print_ip6(out, ip6_addr, quoted);
546 } else {
547 /* no special care needed, input is already rfc compliant,
548 * just print as regular non quoted string
549 */
550 chunk_cat(out, &smp->data.u.str);
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +0100551 }
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +0100552 }
553 else if (http_7239_extract_obfs(&validate_o, NULL) &&
554 !istlen(validate_o)) {
555 /* raw user input that should be printed as 7239 obfs */
556 chunk_appendf(out, "_%.*s", (int)smp->data.u.str.data, smp->data.u.str.area);
557 }
558 /* else: not compliant */
559 }
560 /* else: cannot be casted to str */
561 }
562 /* else: smp error */
563 }
564}
565
566static inline void http_build_7239_header_nodeport(struct buffer *out,
567 struct stream *s, struct proxy *curproxy,
568 const struct sockaddr_storage *addr,
569 struct http_ext_7239_forby *forby)
570{
571 if (forby->np_mode == HTTP_7239_FORBY_ORIG) {
572 if (addr && addr->ss_family == AF_INET)
573 chunk_appendf(out, "%d", ntohs(((struct sockaddr_in *)addr)->sin_port));
574 else if (addr && addr->ss_family == AF_INET6)
575 chunk_appendf(out, "%d", ntohs(((struct sockaddr_in6 *)addr)->sin6_port));
576 /* else: not supported */
577 }
578 else if (forby->np_mode == HTTP_7239_FORBY_SMP && forby->np_expr) {
579 struct sample *smp;
580
581 smp = sample_fetch_as_type(curproxy, s->sess, s,
582 SMP_OPT_DIR_REQ | SMP_OPT_FINAL, forby->np_expr, SMP_T_STR);
583 if (smp) {
584 struct ist validate_n = ist2(smp->data.u.str.area, smp->data.u.str.data);
585 struct ist validate_o = ist2(smp->data.u.str.area, smp->data.u.str.data);
586
587 /* validate nodeport */
588 if (http_7239_extract_nodeport(&validate_n, NULL) &&
589 !istlen(validate_n)) {
590 /* no special care needed, input is already rfc compliant,
591 * just print as regular non quoted string
592 */
593 chunk_cat(out, &smp->data.u.str);
594 }
595 else if (http_7239_extract_obfs(&validate_o, NULL) &&
596 !istlen(validate_o)) {
597 /* raw user input that should be printed as 7239 obfs */
598 chunk_appendf(out, "_%.*s", (int)smp->data.u.str.data, smp->data.u.str.area);
599 }
600 /* else: not compliant */
601 }
602 /* else: smp error */
603 }
604}
605
606static inline void http_build_7239_header_node(struct buffer *out,
607 struct stream *s, struct proxy *curproxy,
608 const struct sockaddr_storage *addr,
609 struct http_ext_7239_forby *forby)
610{
611 size_t offset_start;
612 size_t offset_save;
613
614 offset_start = out->data;
615 if (forby->np_mode)
616 chunk_appendf(out, "\"");
617 offset_save = out->data;
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +0100618 http_build_7239_header_nodename(out, s, curproxy, addr, forby);
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +0100619 if (offset_save == out->data) {
620 /* could not build nodename, either because some
621 * data is not available or user is providing bad input
622 */
623 chunk_appendf(out, "unknown");
624 }
625 if (forby->np_mode) {
626 chunk_appendf(out, ":");
627 offset_save = out->data;
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +0100628 http_build_7239_header_nodeport(out, s, curproxy, addr, forby);
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +0100629 if (offset_save == out->data) {
630 /* could not build nodeport, either because some data is
631 * not available or user is providing bad input
632 */
633 out->data = offset_save - 1;
634 }
635 }
636 if (out->data != offset_start && out->area[offset_start] == '"')
637 chunk_appendf(out, "\""); /* add matching end quote */
638}
639
640static inline void http_build_7239_header_host(struct buffer *out,
641 struct stream *s, struct proxy *curproxy,
642 struct htx *htx, struct http_ext_7239_host *host)
643{
644 struct http_hdr_ctx ctx = { .blk = NULL };
645 char *str = NULL;
646 int str_len = 0;
647
648 if (host->mode == HTTP_7239_HOST_ORIG &&
649 http_find_header(htx, ist("host"), &ctx, 0)) {
650 str = ctx.value.ptr;
651 str_len = ctx.value.len;
652 print_host:
653 {
654 struct ist validate = ist2(str, str_len);
655 /* host check, to ensure rfc compliant output
656 * (assumming host is quoted/escaped)
657 */
658 if (http_7239_extract_host(&validate, NULL, 1) && !istlen(validate))
659 chunk_memcat(out, str, str_len);
660 /* else: not compliant or partially compliant */
661 }
662
663 }
664 else if (host->mode == HTTP_7239_HOST_SMP && host->expr) {
665 struct sample *smp;
666
667 smp = sample_fetch_as_type(curproxy, s->sess, s,
668 SMP_OPT_DIR_REQ | SMP_OPT_FINAL, host->expr, SMP_T_STR);
669 if (smp) {
670 str = smp->data.u.str.area;
671 str_len = smp->data.u.str.data;
672 goto print_host;
673 }
674 /* else: smp error */
675 }
676}
677
678/* Tries build 7239 header according to <curproxy> parameters and <s> context
679 * It both depends on <curproxy>->http_ext->fwd for config and <s> for request
680 * context data.
681 * The function will write output to <out> buffer
682 * Returns 1 for success and 0 for error (ie: not enough space in buffer)
683 */
684static int http_build_7239_header(struct buffer *out,
685 struct stream *s, struct proxy *curproxy, struct htx *htx)
686{
687 struct connection *cli_conn = objt_conn(strm_sess(s)->origin);
688
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +0100689 if (curproxy->http_ext->fwd->p_proto) {
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +0100690 chunk_appendf(out, "%sproto=%s", ((out->data) ? ";" : ""),
691 ((conn_is_ssl(cli_conn)) ? "https" : "http"));
692 }
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +0100693 if (curproxy->http_ext->fwd->p_host.mode) {
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +0100694 /* always add quotes for host parameter to make output compliancy checks simpler */
695 chunk_appendf(out, "%shost=\"", ((out->data) ? ";" : ""));
696 /* ignore return value for now, but could be useful some day */
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +0100697 http_build_7239_header_host(out, s, curproxy, htx, &curproxy->http_ext->fwd->p_host);
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +0100698 chunk_appendf(out, "\"");
699 }
700
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +0100701 if (curproxy->http_ext->fwd->p_by.nn_mode) {
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +0100702 const struct sockaddr_storage *dst = sc_dst(s->scf);
703
704 chunk_appendf(out, "%sby=", ((out->data) ? ";" : ""));
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +0100705 http_build_7239_header_node(out, s, curproxy, dst, &curproxy->http_ext->fwd->p_by);
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +0100706 }
707
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +0100708 if (curproxy->http_ext->fwd->p_for.nn_mode) {
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +0100709 const struct sockaddr_storage *src = sc_src(s->scf);
710
711 chunk_appendf(out, "%sfor=", ((out->data) ? ";" : ""));
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +0100712 http_build_7239_header_node(out, s, curproxy, src, &curproxy->http_ext->fwd->p_for);
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +0100713 }
714 if (unlikely(out->data == out->size)) {
715 /* not enough space in buffer, error */
716 return 0;
717 }
718 return 1;
719}
720
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +0100721/* This function will try to inject RFC 7239 forwarded header if
722 * configured on the backend (ignored for frontends).
723 * Will do nothing if the option is not enabled on the proxy.
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +0100724 * Returns 1 for success and 0 for failure
725 */
726int http_handle_7239_header(struct stream *s, struct channel *req)
727{
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +0100728 struct proxy *curproxy = s->be; /* ignore frontend */
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +0100729
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +0100730 if (curproxy->http_ext && curproxy->http_ext->fwd) {
731 struct htx *htx = htxbuf(&req->buf);
732 int validate = 1;
733 struct http_hdr_ctx find = { .blk = NULL };
734 struct http_hdr_ctx last = { .blk = NULL};
735 struct ist hdr = ist("forwarded");
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +0100736
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +0100737 /* ok, let's build forwarded header */
738 chunk_reset(&trash);
739 if (unlikely(!http_build_7239_header(&trash, s, curproxy, htx)))
740 return 0; /* error when building header (bad user conf or memory error) */
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +0100741
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +0100742 /* validate existing forwarded header (including multiple values),
743 * hard stop if error is encountered
744 */
745 while (http_find_header(htx, hdr, &find, 0)) {
746 /* validate current header chunk */
747 if (!http_validate_7239_header(find.value, FORWARDED_HEADER_ALL, NULL)) {
748 /* at least one error, existing forwarded header not OK, add our own
749 * forwarded header, so that it can be trusted
750 */
751 validate = 0;
752 break;
753 }
754 last = find;
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +0100755 }
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +0100756 /* no errors, append our data at the end of existing header */
757 if (last.blk && validate) {
758 if (unlikely(!http_append_header_value(htx, &last, ist2(trash.area, trash.data))))
759 return 0; /* htx error */
760 }
761 else {
762 if (unlikely(!http_add_header(htx, hdr, ist2(trash.area, trash.data))))
763 return 0; /* htx error */
764 }
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +0100765 }
766 return 1;
767}
768
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +0100769/*
770 * add X-Forwarded-For if either the frontend or the backend
771 * asks for it.
Aurelien DARRAGON730b9832022-12-28 18:53:05 +0100772 * Returns 1 for success and 0 for failure
773 */
774int http_handle_xff_header(struct stream *s, struct channel *req)
775{
776 struct session *sess = s->sess;
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +0100777 struct http_ext_xff *f_xff = NULL;
778 struct http_ext_xff *b_xff = NULL;
Aurelien DARRAGON730b9832022-12-28 18:53:05 +0100779
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +0100780 if (sess->fe->http_ext && sess->fe->http_ext->xff) {
781 /* frontend */
782 f_xff = sess->fe->http_ext->xff;
Aurelien DARRAGON730b9832022-12-28 18:53:05 +0100783 }
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +0100784 if (s->be->http_ext && s->be->http_ext->xff) {
785 /* backend */
786 b_xff = s->be->http_ext->xff;
787 }
788
789 if (f_xff || b_xff) {
790 struct htx *htx = htxbuf(&req->buf);
791 const struct sockaddr_storage *src = sc_src(s->scf);
792 struct http_hdr_ctx ctx = { .blk = NULL };
793 struct ist hdr = ((b_xff) ? b_xff->hdr_name : f_xff->hdr_name);
Aurelien DARRAGON730b9832022-12-28 18:53:05 +0100794
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +0100795 if (f_xff && f_xff->mode == HTTP_XFF_IFNONE &&
796 b_xff && b_xff->mode == HTTP_XFF_IFNONE &&
797 http_find_header(htx, hdr, &ctx, 0)) {
798 /* The header is set to be added only if none is present
799 * and we found it, so don't do anything.
Aurelien DARRAGON730b9832022-12-28 18:53:05 +0100800 */
Aurelien DARRAGON730b9832022-12-28 18:53:05 +0100801 }
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +0100802 else if (src && src->ss_family == AF_INET) {
803 /* Add an X-Forwarded-For header unless the source IP is
804 * in the 'except' network range.
805 */
806 if ((!f_xff || ipcmp2net(src, &f_xff->except_net)) &&
807 (!b_xff || ipcmp2net(src, &b_xff->except_net))) {
808 unsigned char *pn = (unsigned char *)&((struct sockaddr_in *)src)->sin_addr;
Aurelien DARRAGON730b9832022-12-28 18:53:05 +0100809
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +0100810 /* Note: we rely on the backend to get the header name to be used for
811 * x-forwarded-for, because the header is really meant for the backends.
812 * However, if the backend did not specify any option, we have to rely
813 * on the frontend's header name.
814 */
815 chunk_printf(&trash, "%d.%d.%d.%d", pn[0], pn[1], pn[2], pn[3]);
816 if (unlikely(!http_add_header(htx, hdr, ist2(trash.area, trash.data))))
817 return 0;
818 }
819 }
820 else if (src && src->ss_family == AF_INET6) {
821 /* Add an X-Forwarded-For header unless the source IP is
822 * in the 'except' network range.
Aurelien DARRAGON730b9832022-12-28 18:53:05 +0100823 */
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +0100824 if ((!f_xff || ipcmp2net(src, &f_xff->except_net)) &&
825 (!b_xff || ipcmp2net(src, &b_xff->except_net))) {
826 char pn[INET6_ADDRSTRLEN];
827
828 inet_ntop(AF_INET6,
829 (const void *)&((struct sockaddr_in6 *)(src))->sin6_addr,
830 pn, sizeof(pn));
831
832 /* Note: we rely on the backend to get the header name to be used for
833 * x-forwarded-for, because the header is really meant for the backends.
834 * However, if the backend did not specify any option, we have to rely
835 * on the frontend's header name.
836 */
837 chunk_printf(&trash, "%s", pn);
838 if (unlikely(!http_add_header(htx, hdr, ist2(trash.area, trash.data))))
839 return 0;
840 }
Aurelien DARRAGON730b9832022-12-28 18:53:05 +0100841 }
842 }
Aurelien DARRAGON730b9832022-12-28 18:53:05 +0100843 return 1;
844}
845
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +0100846/*
847 * add X-Original-To if either the frontend or the backend
848 * asks for it.
Aurelien DARRAGONf9583412022-12-29 15:45:41 +0100849 * Returns 1 for success and 0 for failure
850 */
851int http_handle_xot_header(struct stream *s, struct channel *req)
852{
853 struct session *sess = s->sess;
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +0100854 struct http_ext_xot *f_xot = NULL;
855 struct http_ext_xot *b_xot = NULL;
Aurelien DARRAGONf9583412022-12-29 15:45:41 +0100856
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +0100857 if (sess->fe->http_ext && sess->fe->http_ext->xot) {
858 /* frontend */
859 f_xot = sess->fe->http_ext->xot;
860 }
861 if (s->be->http_ext && s->be->http_ext->xot) {
862 /* backend */
863 BUG_ON(!s->be->http_ext);
864 b_xot = s->be->http_ext->xot;
865 }
Aurelien DARRAGONf9583412022-12-29 15:45:41 +0100866
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +0100867 if (f_xot || b_xot) {
868 struct htx *htx = htxbuf(&req->buf);
869 const struct sockaddr_storage *dst = sc_dst(s->scf);
870 struct ist hdr = ((b_xot) ? b_xot->hdr_name : f_xot->hdr_name);
Aurelien DARRAGONf9583412022-12-29 15:45:41 +0100871
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +0100872 if (dst && dst->ss_family == AF_INET) {
873 /* Add an X-Original-To header unless the destination IP is
874 * in the 'except' network range.
Aurelien DARRAGONf9583412022-12-29 15:45:41 +0100875 */
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +0100876 if ((!f_xot || ipcmp2net(dst, &f_xot->except_net)) &&
877 (!b_xot || ipcmp2net(dst, &b_xot->except_net))) {
878 unsigned char *pn = (unsigned char *)&((struct sockaddr_in *)dst)->sin_addr;
879
880 /* Note: we rely on the backend to get the header name to be used for
881 * x-original-to, because the header is really meant for the backends.
882 * However, if the backend did not specify any option, we have to rely
883 * on the frontend's header name.
884 */
885 chunk_printf(&trash, "%d.%d.%d.%d", pn[0], pn[1], pn[2], pn[3]);
886 if (unlikely(!http_add_header(htx, hdr, ist2(trash.area, trash.data))))
887 return 0;
888 }
Aurelien DARRAGONf9583412022-12-29 15:45:41 +0100889 }
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +0100890 else if (dst && dst->ss_family == AF_INET6) {
891 /* Add an X-Original-To header unless the source IP is
892 * in the 'except' network range.
893 */
894 if ((!f_xot || ipcmp2net(dst, &f_xot->except_net)) &&
895 (!b_xot || ipcmp2net(dst, &b_xot->except_net))) {
896 char pn[INET6_ADDRSTRLEN];
Aurelien DARRAGONf9583412022-12-29 15:45:41 +0100897
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +0100898 inet_ntop(AF_INET6,
899 (const void *)&((struct sockaddr_in6 *)dst)->sin6_addr,
900 pn, sizeof(pn));
Aurelien DARRAGONf9583412022-12-29 15:45:41 +0100901
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +0100902 /* Note: we rely on the backend to get the header name to be used for
903 * x-forwarded-for, because the header is really meant for the backends.
904 * However, if the backend did not specify any option, we have to rely
905 * on the frontend's header name.
906 */
907 chunk_printf(&trash, "%s", pn);
908 if (unlikely(!http_add_header(htx, hdr, ist2(trash.area, trash.data))))
909 return 0;
910 }
Aurelien DARRAGONf9583412022-12-29 15:45:41 +0100911 }
912 }
913 return 1;
914}
915
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +0100916/*
917 * =========== CONFIG ===========
918 * below are helpers to parse http ext options from the config
919 */
920static int proxy_http_parse_oom(const char *file, int linenum)
921{
922 int err_code = 0;
923
924 ha_alert("parsing [%s:%d]: out of memory.\n", file, linenum);
925 err_code |= ERR_ALERT | ERR_ABORT;
926 return err_code;
927}
928
929static inline int _proxy_http_parse_7239_expr(char **args, int *cur_arg,
930 const char *file, int linenum,
931 char **expr_s)
932{
933 int err_code = 0;
934
935 if (!*args[*cur_arg + 1]) {
936 ha_alert("parsing [%s:%d]: '%s' expects <expr> as argument.\n",
937 file, linenum, args[*cur_arg]);
938 err_code |= ERR_ALERT | ERR_FATAL;
939 goto out;
940 }
941 *cur_arg += 1;
942 ha_free(expr_s);
943 *expr_s = strdup(args[*cur_arg]);
944 if (!*expr_s)
945 return proxy_http_parse_oom(file, linenum);
946 *cur_arg += 1;
947 out:
948 return err_code;
949}
950
951int proxy_http_parse_7239(char **args, int cur_arg,
952 struct proxy *curproxy, const struct proxy *defpx,
953 const char *file, int linenum)
954{
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +0100955 struct http_ext_7239 *fwd;
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +0100956 int err_code = 0;
957
958 if (warnifnotcap(curproxy, PR_CAP_BE, file, linenum, "option forwarded", NULL)) {
959 /* option is ignored for frontends */
960 err_code |= ERR_WARN;
961 goto out;
962 }
963
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +0100964 if (!http_ext_7239_prepare(curproxy))
965 return proxy_http_parse_oom(file, linenum);
966
967 fwd = curproxy->http_ext->fwd;
968
969 fwd->p_proto = 0;
970 fwd->p_host.mode = 0;
971 fwd->p_for.nn_mode = 0;
972 fwd->p_for.np_mode = 0;
973 fwd->p_by.nn_mode = 0;
974 fwd->p_by.np_mode = 0;
975 ha_free(&fwd->c_file);
976 fwd->c_file = strdup(file);
977 fwd->c_line = linenum;
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +0100978
979 /* start at 2, since 0+1 = "option" "forwarded" */
980 cur_arg = 2;
981 if (!*(args[cur_arg])) {
982 /* no optional argument provided, use default settings */
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +0100983 fwd->p_for.nn_mode = HTTP_7239_FORBY_ORIG; /* enable for and mimic xff */
984 fwd->p_proto = 1; /* enable proto */
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +0100985 goto out;
986 }
987 /* loop to go through optional arguments */
988 while (*(args[cur_arg])) {
989 if (strcmp(args[cur_arg], "proto") == 0) {
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +0100990 fwd->p_proto = 1;
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +0100991 cur_arg += 1;
992 } else if (strcmp(args[cur_arg], "host") == 0) {
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +0100993 fwd->p_host.mode = HTTP_7239_HOST_ORIG;
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +0100994 cur_arg += 1;
995 } else if (strcmp(args[cur_arg], "host-expr") == 0) {
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +0100996 fwd->p_host.mode = HTTP_7239_HOST_SMP;
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +0100997 err_code |= _proxy_http_parse_7239_expr(args, &cur_arg, file, linenum,
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +0100998 &fwd->p_host.expr_s);
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +0100999 if (err_code & ERR_FATAL)
1000 goto out;
1001 } else if (strcmp(args[cur_arg], "by") == 0) {
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001002 fwd->p_by.nn_mode = HTTP_7239_FORBY_ORIG;
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001003 cur_arg += 1;
1004 } else if (strcmp(args[cur_arg], "by-expr") == 0) {
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001005 fwd->p_by.nn_mode = HTTP_7239_FORBY_SMP;
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001006 err_code |= _proxy_http_parse_7239_expr(args, &cur_arg, file, linenum,
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001007 &fwd->p_by.nn_expr_s);
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001008 if (err_code & ERR_FATAL)
1009 goto out;
1010 } else if (strcmp(args[cur_arg], "for") == 0) {
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001011 fwd->p_for.nn_mode = HTTP_7239_FORBY_ORIG;
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001012 cur_arg += 1;
1013 } else if (strcmp(args[cur_arg], "for-expr") == 0) {
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001014 fwd->p_for.nn_mode = HTTP_7239_FORBY_SMP;
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001015 err_code |= _proxy_http_parse_7239_expr(args, &cur_arg, file, linenum,
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001016 &fwd->p_for.nn_expr_s);
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001017 if (err_code & ERR_FATAL)
1018 goto out;
1019 } else if (strcmp(args[cur_arg], "by_port") == 0) {
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001020 fwd->p_by.np_mode = HTTP_7239_FORBY_ORIG;
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001021 cur_arg += 1;
1022 } else if (strcmp(args[cur_arg], "by_port-expr") == 0) {
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001023 fwd->p_by.np_mode = HTTP_7239_FORBY_SMP;
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001024 err_code |= _proxy_http_parse_7239_expr(args, &cur_arg, file, linenum,
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001025 &fwd->p_by.np_expr_s);
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001026 if (err_code & ERR_FATAL)
1027 goto out;
1028 } else if (strcmp(args[cur_arg], "for_port") == 0) {
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001029 fwd->p_for.np_mode = HTTP_7239_FORBY_ORIG;
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001030 cur_arg += 1;
1031 } else if (strcmp(args[cur_arg], "for_port-expr") == 0) {
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001032 fwd->p_for.np_mode = HTTP_7239_FORBY_SMP;
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001033 err_code |= _proxy_http_parse_7239_expr(args, &cur_arg, file, linenum,
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001034 &fwd->p_for.np_expr_s);
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001035 if (err_code & ERR_FATAL)
1036 goto out;
1037 } else {
1038 /* unknown suboption - catchall */
1039 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'proto', 'host', "
1040 "'host-expr', 'by', 'by-expr', 'by_port', 'by_port-expr', "
1041 "'for', 'for-expr', 'for_port' and 'for_port-expr'.\n",
1042 file, linenum, args[0], args[1]);
1043 err_code |= ERR_ALERT | ERR_FATAL;
1044 goto out;
1045 }
1046 } /* end while loop */
1047
1048 /* consistency check */
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001049 if (fwd->p_by.np_mode &&
1050 !fwd->p_by.nn_mode) {
1051 fwd->p_by.np_mode = 0;
1052 ha_free(&fwd->p_by.np_expr_s);
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001053 ha_warning("parsing [%s:%d] : '%s %s' : '%s' will be ignored because both 'by' "
1054 "and 'by-expr' are unset\n",
1055 file, linenum, args[0], args[1],
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001056 ((fwd->p_by.np_mode == HTTP_7239_FORBY_ORIG) ? "by_port" : "by_port-expr"));
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001057 err_code |= ERR_WARN;
1058 }
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001059 if (fwd->p_for.np_mode &&
1060 !fwd->p_for.nn_mode) {
1061 fwd->p_for.np_mode = 0;
1062 ha_free(&fwd->p_for.np_expr_s);
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001063 ha_warning("parsing [%s:%d] : '%s %s' : '%s' will be ignored because both 'for' "
1064 "and 'for-expr' are unset\n",
1065 file, linenum, args[0], args[1],
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001066 ((fwd->p_for.np_mode == HTTP_7239_FORBY_ORIG) ? "for_port" : "for_port-expr"));
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001067 err_code |= ERR_WARN;
1068 }
1069
1070 out:
1071 return err_code;
1072}
1073
Aurelien DARRAGONc07001b2023-01-24 17:55:09 +01001074/* rfc7239 forwarded option needs a postparsing step
1075 * to convert parsing hints into runtime usable sample expressions
1076 * Returns a composition of ERR_NONE, ERR_FATAL, ERR_ALERT, ERR_WARN
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001077 */
1078int proxy_http_compile_7239(struct proxy *curproxy)
1079{
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001080 struct http_ext_7239 *fwd;
Aurelien DARRAGONc07001b2023-01-24 17:55:09 +01001081 int err = ERR_NONE;
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001082 int loop;
1083
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001084 if (!(curproxy->cap & PR_CAP_BE)) {
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001085 /* no backend cap: not supported (ie: frontend) */
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001086 goto out;
1087 }
1088
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001089 /* should not happen (test should be performed after BE cap test) */
1090 BUG_ON(!curproxy->http_ext || !curproxy->http_ext->fwd);
1091
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001092 curproxy->conf.args.ctx = ARGC_OPT; /* option */
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001093 curproxy->conf.args.file = curproxy->http_ext->fwd->c_file;
1094 curproxy->conf.args.line = curproxy->http_ext->fwd->c_line;
1095 fwd = curproxy->http_ext->fwd;
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001096
Aurelien DARRAGON9ded8342023-01-06 12:16:28 +01001097 /* it is important that we keep iterating on error to make sure
1098 * all fwd config fields are in the same state (post-parsing state)
1099 */
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001100 for (loop = 0; loop < 5; loop++) {
Aurelien DARRAGON9ded8342023-01-06 12:16:28 +01001101 char **expr_str = NULL;
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001102 struct sample_expr **expr = NULL;
Aurelien DARRAGON9ded8342023-01-06 12:16:28 +01001103 struct sample_expr *cur_expr;
Aurelien DARRAGONc07001b2023-01-24 17:55:09 +01001104 char *err_str = NULL;
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001105 int smp = 0;
1106 int idx = 0;
1107
1108 switch (loop) {
1109 case 0:
1110 /* host */
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001111 expr_str = &fwd->p_host.expr_s;
1112 expr = &fwd->p_host.expr;
1113 smp = (fwd->p_host.mode == HTTP_7239_HOST_SMP);
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001114 break;
1115 case 1:
1116 /* by->node */
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001117 expr_str = &fwd->p_by.nn_expr_s;
1118 expr = &fwd->p_by.nn_expr;
1119 smp = (fwd->p_by.nn_mode == HTTP_7239_FORBY_SMP);
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001120 break;
1121 case 2:
1122 /* by->nodeport */
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001123 expr_str = &fwd->p_by.np_expr_s;
1124 expr = &fwd->p_by.np_expr;
1125 smp = (fwd->p_by.np_mode == HTTP_7239_FORBY_SMP);
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001126 break;
1127 case 3:
1128 /* for->node */
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001129 expr_str = &fwd->p_for.nn_expr_s;
1130 expr = &fwd->p_for.nn_expr;
1131 smp = (fwd->p_for.nn_mode == HTTP_7239_FORBY_SMP);
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001132 break;
1133 case 4:
1134 /* for->nodeport */
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001135 expr_str = &fwd->p_for.np_expr_s;
1136 expr = &fwd->p_for.np_expr;
1137 smp = (fwd->p_for.np_mode == HTTP_7239_FORBY_SMP);
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001138 break;
1139 }
Aurelien DARRAGON9ded8342023-01-06 12:16:28 +01001140 if (!smp)
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001141 continue; /* no expr */
Aurelien DARRAGON9ded8342023-01-06 12:16:28 +01001142
1143 /* expr and expr_str cannot be NULL past this point */
1144 BUG_ON(!expr || !expr_str);
1145
1146 if (!*expr_str) {
1147 /* should not happen unless system memory exhaustion */
1148 ha_alert("%s '%s' [%s:%d]: failed to parse 'option forwarded' expression : %s.\n",
1149 proxy_type_str(curproxy), curproxy->id,
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001150 fwd->c_file, fwd->c_line,
Aurelien DARRAGON9ded8342023-01-06 12:16:28 +01001151 "memory error");
Aurelien DARRAGONc07001b2023-01-24 17:55:09 +01001152 err |= ERR_ALERT | ERR_FATAL;
Aurelien DARRAGON9ded8342023-01-06 12:16:28 +01001153 continue;
1154 }
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001155
Aurelien DARRAGON9ded8342023-01-06 12:16:28 +01001156 cur_expr =
1157 sample_parse_expr((char*[]){*expr_str, NULL}, &idx,
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001158 fwd->c_file,
1159 fwd->c_line,
Aurelien DARRAGONc07001b2023-01-24 17:55:09 +01001160 &err_str, &curproxy->conf.args, NULL);
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001161
Aurelien DARRAGON9ded8342023-01-06 12:16:28 +01001162 if (!cur_expr) {
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001163 ha_alert("%s '%s' [%s:%d]: failed to parse 'option forwarded' expression '%s' in : %s.\n",
1164 proxy_type_str(curproxy), curproxy->id,
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001165 fwd->c_file, fwd->c_line,
Aurelien DARRAGONc07001b2023-01-24 17:55:09 +01001166 *expr_str, err_str);
1167 ha_free(&err_str);
1168 err |= ERR_ALERT | ERR_FATAL;
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001169 }
Aurelien DARRAGONd745a3f2023-01-06 15:06:49 +01001170 else if (!(cur_expr->fetch->val & SMP_VAL_BE_HRQ_HDR)) {
1171 /* fetch not available in this context: sample expr is resolved
1172 * within backend right after headers are processed.
1173 * (in http_process_request())
1174 * -> we simply warn the user about the misuse
1175 */
1176 ha_warning("%s '%s' [%s:%d]: in 'option forwarded' sample expression '%s' : "
1177 "some args extract information from '%s', "
1178 "none of which is available here.\n",
1179 proxy_type_str(curproxy), curproxy->id,
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001180 fwd->c_file, fwd->c_line,
Aurelien DARRAGONd745a3f2023-01-06 15:06:49 +01001181 *expr_str, sample_ckp_names(cur_expr->fetch->use));
Aurelien DARRAGONc07001b2023-01-24 17:55:09 +01001182 err |= ERR_WARN;
Aurelien DARRAGONd745a3f2023-01-06 15:06:49 +01001183 }
Aurelien DARRAGON9ded8342023-01-06 12:16:28 +01001184 /* post parsing individual expr cleanup */
1185 ha_free(expr_str);
1186
1187 /* expr assignment */
1188 *expr = cur_expr;
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001189 }
1190 curproxy->conf.args.file = NULL;
1191 curproxy->conf.args.line = 0;
1192
Aurelien DARRAGON9ded8342023-01-06 12:16:28 +01001193 /* post parsing general cleanup */
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001194 ha_free(&fwd->c_file);
1195 fwd->c_line = 0;
Aurelien DARRAGON9ded8342023-01-06 12:16:28 +01001196
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001197 fwd->c_mode = 1; /* parsing completed */
Aurelien DARRAGON9ded8342023-01-06 12:16:28 +01001198
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001199 out:
Aurelien DARRAGONc07001b2023-01-24 17:55:09 +01001200 return err;
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001201}
1202
Aurelien DARRAGON730b9832022-12-28 18:53:05 +01001203/* x-forwarded-for */
1204int proxy_http_parse_xff(char **args, int cur_arg,
1205 struct proxy *curproxy, const struct proxy *defpx,
1206 const char *file, int linenum)
1207{
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001208 struct http_ext_xff *xff;
Aurelien DARRAGON730b9832022-12-28 18:53:05 +01001209 int err_code = 0;
1210
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001211 if (!http_ext_xff_prepare(curproxy))
1212 return proxy_http_parse_oom(file, linenum);
1213
1214 xff = curproxy->http_ext->xff;
1215
Aurelien DARRAGON730b9832022-12-28 18:53:05 +01001216 /* insert x-forwarded-for field, but not for the IP address listed as an except.
1217 * set default options (ie: bitfield, header name, etc)
1218 */
1219
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001220 xff->mode = HTTP_XFF_ALWAYS;
Aurelien DARRAGON730b9832022-12-28 18:53:05 +01001221
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001222 istfree(&xff->hdr_name);
1223 xff->hdr_name = istdup(ist(DEF_XFORWARDFOR_HDR));
1224 if (!isttest(xff->hdr_name))
Aurelien DARRAGON730b9832022-12-28 18:53:05 +01001225 return proxy_http_parse_oom(file, linenum);
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001226 xff->except_net.family = AF_UNSPEC;
Aurelien DARRAGON730b9832022-12-28 18:53:05 +01001227
1228 /* loop to go through arguments - start at 2, since 0+1 = "option" "forwardfor" */
1229 cur_arg = 2;
1230 while (*(args[cur_arg])) {
1231 if (strcmp(args[cur_arg], "except") == 0) {
1232 unsigned char mask;
1233 int i;
1234
1235 /* suboption except - needs additional argument for it */
1236 if (*(args[cur_arg+1]) &&
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001237 str2net(args[cur_arg+1], 1, &xff->except_net.addr.v4.ip, &xff->except_net.addr.v4.mask)) {
1238 xff->except_net.family = AF_INET;
1239 xff->except_net.addr.v4.ip.s_addr &= xff->except_net.addr.v4.mask.s_addr;
Aurelien DARRAGON730b9832022-12-28 18:53:05 +01001240 }
1241 else if (*(args[cur_arg+1]) &&
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001242 str62net(args[cur_arg+1], &xff->except_net.addr.v6.ip, &mask)) {
1243 xff->except_net.family = AF_INET6;
1244 len2mask6(mask, &xff->except_net.addr.v6.mask);
Aurelien DARRAGON730b9832022-12-28 18:53:05 +01001245 for (i = 0; i < 16; i++)
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001246 xff->except_net.addr.v6.ip.s6_addr[i] &= xff->except_net.addr.v6.mask.s6_addr[i];
Aurelien DARRAGON730b9832022-12-28 18:53:05 +01001247 }
1248 else {
1249 ha_alert("parsing [%s:%d] : '%s %s %s' expects <address>[/mask] as argument.\n",
1250 file, linenum, args[0], args[1], args[cur_arg]);
1251 err_code |= ERR_ALERT | ERR_FATAL;
1252 goto out;
1253 }
1254 /* flush useless bits */
1255 cur_arg += 2;
1256 } else if (strcmp(args[cur_arg], "header") == 0) {
1257 /* suboption header - needs additional argument for it */
1258 if (*(args[cur_arg+1]) == 0) {
1259 ha_alert("parsing [%s:%d] : '%s %s %s' expects <header_name> as argument.\n",
1260 file, linenum, args[0], args[1], args[cur_arg]);
1261 err_code |= ERR_ALERT | ERR_FATAL;
1262 goto out;
1263 }
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001264 istfree(&xff->hdr_name);
1265 xff->hdr_name = istdup(ist(args[cur_arg+1]));
1266 if (!isttest(xff->hdr_name))
Aurelien DARRAGON730b9832022-12-28 18:53:05 +01001267 return proxy_http_parse_oom(file, linenum);
1268 cur_arg += 2;
1269 } else if (strcmp(args[cur_arg], "if-none") == 0) {
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001270 xff->mode = HTTP_XFF_IFNONE;
Aurelien DARRAGON730b9832022-12-28 18:53:05 +01001271 cur_arg += 1;
1272 } else {
1273 /* unknown suboption - catchall */
1274 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'except', 'header' and 'if-none'.\n",
1275 file, linenum, args[0], args[1]);
1276 err_code |= ERR_ALERT | ERR_FATAL;
1277 goto out;
1278 }
1279 } /* end while loop */
1280 out:
1281 return err_code;
1282}
1283
Aurelien DARRAGONf9583412022-12-29 15:45:41 +01001284/* x-original-to */
1285int proxy_http_parse_xot(char **args, int cur_arg,
1286 struct proxy *curproxy, const struct proxy *defpx,
1287 const char *file, int linenum)
1288{
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001289 struct http_ext_xot *xot;
Aurelien DARRAGONf9583412022-12-29 15:45:41 +01001290 int err_code = 0;
1291
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001292 if (!http_ext_xot_prepare(curproxy))
1293 return proxy_http_parse_oom(file, linenum);
1294
1295 xot = curproxy->http_ext->xot;
1296
Aurelien DARRAGONf9583412022-12-29 15:45:41 +01001297 /* insert x-original-to field, but not for the IP address listed as an except.
1298 * set default options (ie: bitfield, header name, etc)
1299 */
1300
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001301 istfree(&xot->hdr_name);
1302 xot->hdr_name = istdup(ist(DEF_XORIGINALTO_HDR));
1303 if (!isttest(xot->hdr_name))
Aurelien DARRAGONf9583412022-12-29 15:45:41 +01001304 return proxy_http_parse_oom(file, linenum);
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001305 xot->except_net.family = AF_UNSPEC;
Aurelien DARRAGONf9583412022-12-29 15:45:41 +01001306
1307 /* loop to go through arguments - start at 2, since 0+1 = "option" "originalto" */
1308 cur_arg = 2;
1309 while (*(args[cur_arg])) {
1310 if (strcmp(args[cur_arg], "except") == 0) {
1311 unsigned char mask;
1312 int i;
1313
1314 /* suboption except - needs additional argument for it */
1315 if (*(args[cur_arg+1]) &&
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001316 str2net(args[cur_arg+1], 1, &xot->except_net.addr.v4.ip, &xot->except_net.addr.v4.mask)) {
1317 xot->except_net.family = AF_INET;
1318 xot->except_net.addr.v4.ip.s_addr &= xot->except_net.addr.v4.mask.s_addr;
Aurelien DARRAGONf9583412022-12-29 15:45:41 +01001319 }
1320 else if (*(args[cur_arg+1]) &&
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001321 str62net(args[cur_arg+1], &xot->except_net.addr.v6.ip, &mask)) {
1322 xot->except_net.family = AF_INET6;
1323 len2mask6(mask, &xot->except_net.addr.v6.mask);
Aurelien DARRAGONf9583412022-12-29 15:45:41 +01001324 for (i = 0; i < 16; i++)
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001325 xot->except_net.addr.v6.ip.s6_addr[i] &= xot->except_net.addr.v6.mask.s6_addr[i];
Aurelien DARRAGONf9583412022-12-29 15:45:41 +01001326 }
1327 else {
1328 ha_alert("parsing [%s:%d] : '%s %s %s' expects <address>[/mask] as argument.\n",
1329 file, linenum, args[0], args[1], args[cur_arg]);
1330 err_code |= ERR_ALERT | ERR_FATAL;
1331 goto out;
1332 }
1333 cur_arg += 2;
1334 } else if (strcmp(args[cur_arg], "header") == 0) {
1335 /* suboption header - needs additional argument for it */
1336 if (*(args[cur_arg+1]) == 0) {
1337 ha_alert("parsing [%s:%d] : '%s %s %s' expects <header_name> as argument.\n",
1338 file, linenum, args[0], args[1], args[cur_arg]);
1339 err_code |= ERR_ALERT | ERR_FATAL;
1340 goto out;
1341 }
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001342 istfree(&xot->hdr_name);
1343 xot->hdr_name = istdup(ist(args[cur_arg+1]));
1344 if (!isttest(xot->hdr_name))
Aurelien DARRAGONf9583412022-12-29 15:45:41 +01001345 return proxy_http_parse_oom(file, linenum);
1346 cur_arg += 2;
1347 } else {
1348 /* unknown suboption - catchall */
1349 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'except' and 'header'.\n",
1350 file, linenum, args[0], args[1]);
1351 err_code |= ERR_ALERT | ERR_FATAL;
1352 goto out;
1353 }
1354 } /* end while loop */
1355
1356 out:
1357 return err_code;
1358}
1359
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001360/*
1361 * =========== MGMT ===========
1362 * below are helpers to manage http ext options
1363 */
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001364
1365/* Ensure http_ext->fwd is properly allocated and
1366 * initialized for <curproxy>.
1367 * The function will leverage http_ext_prepare() to make
1368 * sure http_ext is properly allocated and initialized as well.
1369 * Returns 1 for success and 0 for failure (memory error)
1370 */
1371int http_ext_7239_prepare(struct proxy *curproxy)
1372{
1373 struct http_ext_7239 *fwd;
1374
1375 if (!http_ext_prepare(curproxy))
1376 return 0;
1377 if (curproxy->http_ext->fwd)
1378 return 1; /* nothing to do */
1379
1380 fwd = malloc(sizeof(*fwd));
1381 if (!fwd)
1382 return 0;
1383 /* initialize fwd mandatory fields */
1384 fwd->c_mode = 0; /* pre-compile (parse) time */
1385 fwd->c_file = NULL;
1386 fwd->p_host.expr_s = NULL;
1387 fwd->p_by.nn_expr_s = NULL;
1388 fwd->p_by.np_expr_s = NULL;
1389 fwd->p_for.nn_expr_s = NULL;
1390 fwd->p_for.np_expr_s = NULL;
1391 /* assign */
1392 curproxy->http_ext->fwd = fwd;
1393 return 1;
1394}
1395
1396/* Ensure http_ext->xff is properly allocated and
1397 * initialized for <curproxy>.
1398 * The function will leverage http_ext_prepare() to make
1399 * sure http_ext is properly allocated and initialized as well.
1400 * Returns 1 for success and 0 for failure (memory error)
1401 */
1402int http_ext_xff_prepare(struct proxy *curproxy)
1403{
1404 struct http_ext_xff *xff;
1405
1406 if (!http_ext_prepare(curproxy))
1407 return 0;
1408 if (curproxy->http_ext->xff)
1409 return 1; /* nothing to do */
1410
1411 xff = malloc(sizeof(*xff));
1412 if (!xff)
1413 return 0;
1414 /* initialize xff mandatory fields */
1415 xff->hdr_name = IST_NULL;
1416 /* assign */
1417 curproxy->http_ext->xff = xff;
1418 return 1;
1419}
1420
1421/* Ensure http_ext->xot is properly allocated and
1422 * initialized for <curproxy>.
1423 * The function will leverage http_ext_prepare() to make
1424 * sure http_ext is properly allocated and initialized as well.
1425 * Returns 1 for success and 0 for failure (memory error)
1426 */
1427int http_ext_xot_prepare(struct proxy *curproxy)
1428{
1429 struct http_ext_xot *xot;
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001430
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001431 if (!http_ext_prepare(curproxy))
1432 return 0;
1433 if (curproxy->http_ext->xot)
1434 return 1; /* nothing to do */
1435
1436 xot = malloc(sizeof(*xot));
1437 if (!xot)
1438 return 0;
1439 /* initialize xot mandatory fields */
1440 xot->hdr_name = IST_NULL;
1441 /* assign */
1442 curproxy->http_ext->xot = xot;
1443 return 1;
1444}
1445
1446/* deep clean http_ext->fwd parameter for <curproxy>
1447 * http_ext->fwd will be freed
1448 * clean behavior will differ depending on http_ext->fwd
1449 * state. If fwd is in 'parsed' state, parsing hints will be
1450 * cleaned. Else, it means fwd is in 'compiled' state, in this
1451 * case we're cleaning compiled results.
1452 * This is because parse and compile memory areas are shared in
1453 * a single union to optimize struct http_ext_7239 size.
1454 */
1455void http_ext_7239_clean(struct proxy *curproxy)
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001456{
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001457 struct http_ext_7239 *clean;
1458
1459 if (!curproxy->http_ext)
1460 return;
1461 clean = curproxy->http_ext->fwd;
1462 if (!clean)
1463 return; /* nothing to do */
Aurelien DARRAGON9ded8342023-01-06 12:16:28 +01001464 if (!clean->c_mode) {
1465 /* parsed */
1466 ha_free(&clean->c_file);
1467 ha_free(&clean->p_host.expr_s);
1468 ha_free(&clean->p_by.nn_expr_s);
1469 ha_free(&clean->p_by.np_expr_s);
1470 ha_free(&clean->p_for.nn_expr_s);
1471 ha_free(&clean->p_for.np_expr_s);
1472 }
1473 else {
1474 /* compiled */
1475 release_sample_expr(clean->p_host.expr);
1476 clean->p_host.expr = NULL;
1477 release_sample_expr(clean->p_by.nn_expr);
1478 clean->p_by.nn_expr = NULL;
1479 release_sample_expr(clean->p_by.np_expr);
1480 clean->p_by.np_expr = NULL;
1481 release_sample_expr(clean->p_for.nn_expr);
1482 clean->p_for.nn_expr = NULL;
1483 release_sample_expr(clean->p_for.np_expr);
1484 clean->p_for.np_expr = NULL;
1485 }
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001486 /* free fwd */
1487 ha_free(&curproxy->http_ext->fwd);
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001488}
1489
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001490/* deep clean http_ext->xff parameter for <curproxy>
1491 * http_ext->xff will be freed
1492 */
1493void http_ext_xff_clean(struct proxy *curproxy)
Aurelien DARRAGON730b9832022-12-28 18:53:05 +01001494{
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001495 struct http_ext_xff *clean;
1496
1497 if (!curproxy->http_ext)
1498 return;
1499 clean = curproxy->http_ext->xff;
1500 if (!clean)
1501 return; /* nothing to do */
Aurelien DARRAGON730b9832022-12-28 18:53:05 +01001502 istfree(&clean->hdr_name);
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001503 /* free xff */
1504 ha_free(&curproxy->http_ext->xff);
Aurelien DARRAGON730b9832022-12-28 18:53:05 +01001505}
1506
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001507/* deep clean http_ext->xot parameter for <curproxy>
1508 * http_ext->xot will be freed
1509 */
1510void http_ext_xot_clean(struct proxy *curproxy)
Aurelien DARRAGONf9583412022-12-29 15:45:41 +01001511{
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001512 struct http_ext_xot *clean;
1513
1514 if (!curproxy->http_ext)
1515 return;
1516 clean = curproxy->http_ext->xot;
1517 if (!clean)
1518 return; /* nothing to do */
Aurelien DARRAGONf9583412022-12-29 15:45:41 +01001519 istfree(&clean->hdr_name);
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001520 /* free xot */
1521 ha_free(&curproxy->http_ext->xot);
Aurelien DARRAGONf9583412022-12-29 15:45:41 +01001522}
1523
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001524/* duplicate http_ext->fwd parameters from <def> to <cpy>
1525 * performs the required memory allocation and initialization
1526 */
1527void http_ext_7239_dup(const struct proxy *def, struct proxy *cpy)
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001528{
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001529 struct http_ext_7239 *dest = NULL;
1530 struct http_ext_7239 *orig = NULL;
1531
1532 /* feature requires backend cap */
1533 if (!(cpy->cap & PR_CAP_BE))
1534 return;
1535
1536 if (def->http_ext == NULL || def->http_ext->fwd == NULL)
1537 return;
1538
1539 orig = def->http_ext->fwd;
1540
Aurelien DARRAGON9ded8342023-01-06 12:16:28 +01001541 if (orig->c_mode)
1542 return; /* copy not supported once compiled */
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001543
1544 if (!http_ext_7239_prepare(cpy))
1545 return;
1546
1547 dest = cpy->http_ext->fwd;
1548
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001549 if (orig->c_file)
1550 dest->c_file = strdup(orig->c_file);
1551 dest->c_line = orig->c_line;
1552 /* proto */
1553 dest->p_proto = orig->p_proto;
1554 /* host */
1555 dest->p_host.mode = orig->p_host.mode;
1556 if (orig->p_host.expr_s)
1557 dest->p_host.expr_s = strdup(orig->p_host.expr_s);
1558 /* by - nodename */
1559 dest->p_by.nn_mode = orig->p_by.nn_mode;
1560 if (orig->p_by.nn_expr_s)
1561 dest->p_by.nn_expr_s = strdup(orig->p_by.nn_expr_s);
1562 /* by - nodeport */
1563 dest->p_by.np_mode = orig->p_by.np_mode;
1564 if (orig->p_by.np_expr_s)
1565 dest->p_by.np_expr_s = strdup(orig->p_by.np_expr_s);
1566 /* for - nodename */
1567 dest->p_for.nn_mode = orig->p_for.nn_mode;
1568 if (orig->p_for.nn_expr_s)
1569 dest->p_for.nn_expr_s = strdup(orig->p_for.nn_expr_s);
1570 /* for - nodeport */
1571 dest->p_for.np_mode = orig->p_for.np_mode;
1572 if (orig->p_for.np_expr_s)
1573 dest->p_for.np_expr_s = strdup(orig->p_for.np_expr_s);
1574}
Aurelien DARRAGON730b9832022-12-28 18:53:05 +01001575
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001576/* duplicate http_ext->xff parameters from <def> to <cpy>
1577 * performs the required memory allocation and initialization
1578 */
1579void http_ext_xff_dup(const struct proxy *def, struct proxy *cpy)
Aurelien DARRAGON730b9832022-12-28 18:53:05 +01001580{
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001581 struct http_ext_xff *dest = NULL;
1582 struct http_ext_xff *orig = NULL;
1583
1584 if (def->http_ext == NULL || def->http_ext->xff == NULL ||
1585 !http_ext_xff_prepare(cpy))
1586 return;
1587
1588 orig = def->http_ext->xff;
1589 dest = cpy->http_ext->xff;
1590
Aurelien DARRAGON730b9832022-12-28 18:53:05 +01001591 if (isttest(orig->hdr_name))
1592 dest->hdr_name = istdup(orig->hdr_name);
1593 dest->mode = orig->mode;
1594 dest->except_net = orig->except_net;
1595}
Aurelien DARRAGONf9583412022-12-29 15:45:41 +01001596
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001597/* duplicate http_ext->xot parameters from <def> to <cpy>
1598 * performs the required memory allocation and initialization
1599 */
1600void http_ext_xot_dup(const struct proxy *def, struct proxy *cpy)
Aurelien DARRAGONf9583412022-12-29 15:45:41 +01001601{
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001602 struct http_ext_xot *dest = NULL;
1603 struct http_ext_xot *orig = NULL;
1604
1605 if (def->http_ext == NULL || def->http_ext->xot == NULL ||
1606 !http_ext_xot_prepare(cpy))
1607 return;
1608
1609 orig = def->http_ext->xot;
1610 dest = cpy->http_ext->xot;
1611
Aurelien DARRAGONf9583412022-12-29 15:45:41 +01001612 if (isttest(orig->hdr_name))
1613 dest->hdr_name = istdup(orig->hdr_name);
1614 dest->except_net = orig->except_net;
1615}
Aurelien DARRAGON82faad12022-12-29 18:32:19 +01001616
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001617/* Allocate new http_ext and initialize it
1618 * if needed
1619 * Returns 1 for success and 0 for failure
1620 */
1621int http_ext_prepare(struct proxy *curproxy)
1622{
1623 if (curproxy->http_ext)
1624 return 1; /* nothing to do */
1625
1626 curproxy->http_ext = malloc(sizeof(*curproxy->http_ext));
1627 if (!curproxy->http_ext)
1628 return 0; /* failure */
1629 /* first init, set supported ext to NULL */
1630 curproxy->http_ext->fwd = NULL;
1631 curproxy->http_ext->xff = NULL;
1632 curproxy->http_ext->xot = NULL;
1633 return 1;
1634}
1635
1636/* duplicate existing http_ext from <defproxy> to <curproxy>
1637 */
1638void http_ext_dup(const struct proxy *defproxy, struct proxy *curproxy)
1639{
1640 /* copy defproxy.http_ext members */
1641 http_ext_7239_dup(defproxy, curproxy);
1642 http_ext_xff_dup(defproxy, curproxy);
1643 http_ext_xot_dup(defproxy, curproxy);
1644}
1645
1646/* deep clean http_ext for <curproxy> (if previously allocated)
1647 */
1648void http_ext_clean(struct proxy *curproxy)
1649{
1650 if (!curproxy->http_ext)
1651 return; /* nothing to do */
1652 /* first, free supported ext */
1653 http_ext_7239_clean(curproxy);
1654 http_ext_xff_clean(curproxy);
1655 http_ext_xot_clean(curproxy);
1656
1657 /* then, free http_ext */
1658 ha_free(&curproxy->http_ext);
1659}
1660
1661/* soft clean (only clean http_ext if no more options are used) */
1662void http_ext_softclean(struct proxy *curproxy)
1663{
1664 if (!curproxy->http_ext)
1665 return; /* nothing to do */
1666 if (!curproxy->http_ext->fwd &&
1667 !curproxy->http_ext->xff &&
1668 !curproxy->http_ext->xot) {
1669 /* no more use for http_ext, all options are disabled */
1670 http_ext_clean(curproxy);
1671 }
1672}
1673
Aurelien DARRAGONc07001b2023-01-24 17:55:09 +01001674/* Perform some consitency checks on px.http_ext after parsing
1675 * is completed.
1676 * We make sure to perform a softclean in case some options were
1677 * to be disabled in this check. This way we can release some memory.
1678 * Returns a composition of ERR_NONE, ERR_ALERT, ERR_FATAL, ERR_WARN
1679 */
1680static int check_http_ext_postconf(struct proxy *px) {
1681 int err = ERR_NONE;
1682
1683 if (px->http_ext) {
1684 /* consistency check for http_ext */
1685 if (px->mode != PR_MODE_HTTP && !(px->options & PR_O_HTTP_UPG)) {
1686 /* http is disabled on px, yet it is required by http_ext */
1687 if (px->http_ext->fwd) {
1688 ha_warning("'option %s' ignored for %s '%s' as it requires HTTP mode.\n",
1689 "forwarded", proxy_type_str(px), px->id);
1690 err |= ERR_WARN;
1691 http_ext_7239_clean(px);
1692 }
1693 if (px->http_ext->xff) {
1694 ha_warning("'option %s' ignored for %s '%s' as it requires HTTP mode.\n",
1695 "forwardfor", proxy_type_str(px), px->id);
1696 err |= ERR_WARN;
1697 http_ext_xff_clean(px);
1698 }
1699 if (px->http_ext->xot) {
1700 ha_warning("'option %s' ignored for %s '%s' as it requires HTTP mode.\n",
1701 "originalto", proxy_type_str(px), px->id);
1702 err |= ERR_WARN;
1703 http_ext_xot_clean(px);
1704 }
1705 } else if (px->http_ext->fwd) {
1706 /* option "forwarded" may need to compile its expressions */
1707 err |= proxy_http_compile_7239(px);
1708 }
1709 /* http_ext post init early cleanup */
1710 http_ext_softclean(px);
1711
1712 }
1713 return err;
1714}
1715
1716REGISTER_POST_PROXY_CHECK(check_http_ext_postconf);
Aurelien DARRAGON82faad12022-12-29 18:32:19 +01001717/*
1718 * =========== CONV ===========
1719 * related converters
1720 */
1721
Aurelien DARRAGON5c6f86f2022-12-30 16:23:08 +01001722/* input: string representing 7239 forwarded header single value
1723 * does not take arguments
1724 * output: 1 if header is RFC compliant, 0 otherwise
1725 */
1726static int sample_conv_7239_valid(const struct arg *args, struct sample *smp, void *private)
1727{
1728 struct ist input = ist2(smp->data.u.str.area, smp->data.u.str.data);
1729
1730 smp->data.type = SMP_T_BOOL;
1731 smp->data.u.sint = !!http_validate_7239_header(input, FORWARDED_HEADER_ALL, NULL);
1732 return 1;
1733}
1734
Aurelien DARRAGON6fb58b82022-12-30 16:37:03 +01001735/* input: string representing 7239 forwarded header single value
1736 * argument: parameter name to look for in the header
1737 * output: header parameter raw value, as a string
1738 */
1739static int sample_conv_7239_field(const struct arg *args, struct sample *smp, void *private)
1740{
1741 struct ist input = ist2(smp->data.u.str.area, smp->data.u.str.data);
1742 struct buffer *output;
1743 struct forwarded_header_ctx ctx;
1744 int validate;
1745 int field = 0;
1746
1747 if (strcmp(args->data.str.area, "proto") == 0)
1748 field = FORWARDED_HEADER_PROTO;
1749 else if (strcmp(args->data.str.area, "host") == 0)
1750 field = FORWARDED_HEADER_HOST;
1751 else if (strcmp(args->data.str.area, "for") == 0)
1752 field = FORWARDED_HEADER_FOR;
1753 else if (strcmp(args->data.str.area, "by") == 0)
1754 field = FORWARDED_HEADER_BY;
1755
1756 validate = http_validate_7239_header(input, FORWARDED_HEADER_ALL, &ctx);
1757 if (!(validate & field))
1758 return 0; /* invalid header or header does not contain field */
1759 output = get_trash_chunk();
1760 switch (field) {
1761 case FORWARDED_HEADER_PROTO:
1762 if (ctx.proto == FORWARDED_HEADER_HTTP)
1763 chunk_appendf(output, "http");
1764 else if (ctx.proto == FORWARDED_HEADER_HTTPS)
1765 chunk_appendf(output, "https");
1766 break;
1767 case FORWARDED_HEADER_HOST:
1768 chunk_istcat(output, ctx.host);
1769 break;
1770 case FORWARDED_HEADER_FOR:
1771 chunk_istcat(output, ctx.nfor.raw);
1772 break;
1773 case FORWARDED_HEADER_BY:
1774 chunk_istcat(output, ctx.nby.raw);
1775 break;
1776 default:
1777 break;
1778 }
1779 smp->flags &= ~SMP_F_CONST;
1780 smp->data.type = SMP_T_STR;
1781 smp->data.u.str = *output;
1782 return 1;
1783}
1784
Aurelien DARRAGON07d67532022-12-30 16:45:42 +01001785/* input: substring representing 7239 forwarded header node
1786 * output: forwarded header nodename translated to either
1787 * ipv4 address, ipv6 address or str
1788 * ('_' prefix if obfuscated, or "unknown" if unknown)
1789 */
1790static int sample_conv_7239_n2nn(const struct arg *args, struct sample *smp, void *private)
1791{
1792 struct ist input = ist2(smp->data.u.str.area, smp->data.u.str.data);
1793 struct forwarded_header_node ctx;
1794 struct buffer *output;
1795
1796 if (http_7239_extract_node(&input, &ctx, 1) == 0)
1797 return 0; /* could not extract node */
1798 switch (ctx.nodename.type) {
1799 case FORWARDED_HEADER_UNK:
1800 output = get_trash_chunk();
1801 chunk_appendf(output, "unknown");
1802 smp->flags &= ~SMP_F_CONST;
1803 smp->data.type = SMP_T_STR;
1804 smp->data.u.str = *output;
1805 break;
1806 case FORWARDED_HEADER_OBFS:
1807 output = get_trash_chunk();
1808 chunk_appendf(output, "_"); /* append obfs prefix */
1809 chunk_istcat(output, ctx.nodename.obfs);
1810 smp->flags &= ~SMP_F_CONST;
1811 smp->data.type = SMP_T_STR;
1812 smp->data.u.str = *output;
1813 break;
1814 case FORWARDED_HEADER_IP:
1815 if (ctx.nodename.ip.ss_family == AF_INET) {
1816 smp->data.type = SMP_T_IPV4;
1817 smp->data.u.ipv4 = ((struct sockaddr_in *)&ctx.nodename.ip)->sin_addr;
1818 }
1819 else if (ctx.nodename.ip.ss_family == AF_INET6) {
1820 smp->data.type = SMP_T_IPV6;
1821 smp->data.u.ipv6 = ((struct sockaddr_in6 *)&ctx.nodename.ip)->sin6_addr;
1822 }
1823 else
1824 return 0; /* unsupported */
1825 break;
1826 default:
1827 return 0; /* unsupported */
1828 }
1829 return 1;
1830}
1831
Aurelien DARRAGON9a273b42022-12-30 16:56:08 +01001832/* input: substring representing 7239 forwarded header node
1833 * output: forwarded header nodeport translated to either
1834 * integer or str for obfuscated ('_' prefix)
1835 */
1836static int sample_conv_7239_n2np(const struct arg *args, struct sample *smp, void *private)
1837{
1838 struct ist input = ist2(smp->data.u.str.area, smp->data.u.str.data);
1839 struct forwarded_header_node ctx;
1840 struct buffer *output;
1841
1842 if (http_7239_extract_node(&input, &ctx, 1) == 0)
1843 return 0; /* could not extract node */
1844
1845 switch (ctx.nodeport.type) {
1846 case FORWARDED_HEADER_UNK:
1847 return 0; /* not provided */
1848 case FORWARDED_HEADER_OBFS:
1849 output = get_trash_chunk();
1850 chunk_appendf(output, "_"); /* append obfs prefix */
1851 chunk_istcat(output, ctx.nodeport.obfs);
1852 smp->flags &= ~SMP_F_CONST;
1853 smp->data.type = SMP_T_STR;
1854 smp->data.u.str = *output;
1855 break;
1856 case FORWARDED_HEADER_PORT:
1857 smp->data.type = SMP_T_SINT;
1858 smp->data.u.sint = ctx.nodeport.port;
1859 break;
1860 default:
1861 return 0; /* unsupported */
1862 }
1863
1864 return 1;
1865}
1866
Aurelien DARRAGON82faad12022-12-29 18:32:19 +01001867/* Note: must not be declared <const> as its list will be overwritten */
1868static struct sample_conv_kw_list sample_conv_kws = {ILH, {
Aurelien DARRAGON5c6f86f2022-12-30 16:23:08 +01001869 { "rfc7239_is_valid", sample_conv_7239_valid, 0, NULL, SMP_T_STR, SMP_T_BOOL},
Aurelien DARRAGON6fb58b82022-12-30 16:37:03 +01001870 { "rfc7239_field", sample_conv_7239_field, ARG1(1,STR), NULL, SMP_T_STR, SMP_T_STR},
Aurelien DARRAGON07d67532022-12-30 16:45:42 +01001871 { "rfc7239_n2nn", sample_conv_7239_n2nn, 0, NULL, SMP_T_STR, SMP_T_ANY},
Aurelien DARRAGON9a273b42022-12-30 16:56:08 +01001872 { "rfc7239_n2np", sample_conv_7239_n2np, 0, NULL, SMP_T_STR, SMP_T_ANY},
Aurelien DARRAGON82faad12022-12-29 18:32:19 +01001873 { NULL, NULL, 0, 0, 0 },
1874}};
1875
1876INITCALL1(STG_REGISTER, sample_register_convs, &sample_conv_kws);