blob: d1bae9c2ab5c48416010b8af5a48b6ce19cc8982 [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
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +010013#include <haproxy/sample.h>
14#include <haproxy/http_htx.h>
15#include <haproxy/http_ext.h>
16#include <haproxy/chunk.h>
17#include <haproxy/stream.h>
18#include <haproxy/proxy.h>
19#include <haproxy/sc_strm.h>
20#include <haproxy/obj_type.h>
21#include <haproxy/cfgparse.h>
Aurelien DARRAGON82faad12022-12-29 18:32:19 +010022#include <haproxy/arg.h>
23#include <haproxy/initcall.h>
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +010024#include <haproxy/tools.h>
25
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +010026/*
27 * =========== ANALYZE ===========
28 * below are http process/ana helpers
29 */
30
31/* checks if <input> contains rfc7239 compliant port
32 * Returns 1 for success and 0 for failure
33 * if <port> is not NULL, it will be set to the extracted value contained
34 * in <input>
35 * <input> will be consumed accordingly (parsed/extracted characters are
36 * removed from <input>)
37 */
38static inline int http_7239_extract_port(struct ist *input, uint16_t *port)
39{
40 char *start = istptr(*input);
41 uint32_t port_cast = 0;
42 int it = 0;
43
44 /* strtol does not support non-null terminated str,
45 * we extract port ourselves
46 */
47 while (it < istlen(*input) &&
48 isdigit((unsigned char)start[it])) {
49 port_cast = (port_cast * 10) + (start[it] - '0');
50 if (port_cast > 65535)
51 return 0; /* invalid port */
52 it += 1;
53 }
54 if (!port_cast)
55 return 0; /* invalid port */
56 /* ok */
57 if (port)
58 *port = (uint16_t)port_cast;
59 *input = istadv(*input, it);
60 return 1;
61}
62
Aurelien DARRAGON39254ca2023-03-03 13:11:36 +010063/* check if char is a valid obfuscated identifier char
64 * (according to 7239 RFC)
65 * Returns non zero value for valid char
66 */
67static inline int http_7239_valid_obfsc(char c)
68{
69 return (isalnum((unsigned char)c) ||
70 (c == '.' || c == '-' || c == '_'));
71}
72
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +010073/* checks if <input> contains rfc7239 compliant obfuscated identifier
74 * Returns 1 for success and 0 for failure
75 * if <obfs> is not NULL, it will be set to the extracted value contained
76 * in <input>
77 * <input> will be consumed accordingly (parsed/extracted characters are
78 * removed from <input>)
79 */
80static inline int http_7239_extract_obfs(struct ist *input, struct ist *obfs)
81{
82 int it = 0;
83
84 if (obfs)
85 obfs->ptr = input->ptr;
86
87 while (it < istlen(*input) && istptr(*input)[it] != ';') {
88 if (!http_7239_valid_obfsc(istptr(*input)[it]))
89 break; /* end of obfs token */
90 it += 1;
91 }
92 if (obfs)
93 obfs->len = it;
94 *input = istadv(*input, it);
95 return !!it;
96}
97
98/* checks if <input> contains rfc7239 compliant IPV4 address
99 * Returns 1 for success and 0 for failure
100 * if <ip> is not NULL, it will be set to the extracted value contained
101 * in <input>
102 * <input> will be consumed accordingly (parsed/extracted characters are
103 * removed from <input>)
104 */
105static inline int http_7239_extract_ipv4(struct ist *input, struct in_addr *ip)
106{
107 char ip4[INET_ADDRSTRLEN];
108 unsigned char buf[sizeof(struct in_addr)];
109 int it = 0;
110
111 /* extract ipv4 addr */
112 while (it < istlen(*input) && it < (sizeof(ip4) - 1)) {
113 if (!isdigit((unsigned char)istptr(*input)[it]) &&
114 istptr(*input)[it] != '.')
115 break; /* no more ip4 char */
116 ip4[it] = istptr(*input)[it];
117 it += 1;
118 }
119 ip4[it] = 0;
120 if (inet_pton(AF_INET, ip4, buf) != 1)
121 return 0; /* invalid ip4 addr */
122 /* ok */
123 if (ip)
124 memcpy(ip, buf, sizeof(buf));
125 *input = istadv(*input, it);
126 return 1;
127}
128
129/* checks if <input> contains rfc7239 compliant IPV6 address
130 * assuming input.len >= 1 and first char is '['
131 * Returns 1 for success and 0 for failure
132 * if <ip> is not NULL, it will be set to the extracted value contained
133 * in <input>
134 * <input> will be consumed accordingly (parsed/extracted characters are
135 * removed from <input>)
136 */
137static inline int http_7239_extract_ipv6(struct ist *input, struct in6_addr *ip)
138{
139 char ip6[INET6_ADDRSTRLEN];
140 unsigned char buf[sizeof(struct in6_addr)];
141 int it = 0;
142
143 *input = istnext(*input); /* skip '[' leading char */
144 /* extract ipv6 addr */
145 while (it < istlen(*input) &&
146 it < (sizeof(ip6) - 1)) {
147 if (!isalnum((unsigned char)istptr(*input)[it]) &&
148 istptr(*input)[it] != ':')
149 break; /* no more ip6 char */
150 ip6[it] = istptr(*input)[it];
151 it += 1;
152 }
153 ip6[it] = 0;
154 if ((istlen(*input)-it) < 1 || istptr(*input)[it] != ']')
155 return 0; /* missing ending "]" char */
156 it += 1;
157 if (inet_pton(AF_INET6, ip6, buf) != 1)
158 return 0; /* invalid ip6 addr */
159 /* ok */
160 if (ip)
161 memcpy(ip, buf, sizeof(buf));
162 *input = istadv(*input, it);
163 return 1;
164}
165
166/* checks if <input> contains rfc7239 compliant host
167 * <quoted> is used to determine if the current input is being extracted
168 * from a quoted (non zero) or unquoted (zero) token, as the parsing rules
Ilya Shipitsin07be66d2023-04-01 12:26:42 +0200169 * differ whether the input is quoted or not according to the rfc.
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +0100170 * Returns 1 for success and 0 for failure
171 * if <host> is not NULL, it will be set to the extracted value contained
172 * in <input>
173 * <input> will be consumed accordingly (parsed/extracted characters are
174 * removed from <input>)
175 */
176static inline int http_7239_extract_host(struct ist *input, struct ist *host, int quoted)
177{
178 if (istlen(*input) < 1)
179 return 0; /* invalid input */
180
181 if (host)
182 host->ptr = input->ptr;
183
184 if (quoted && *istptr(*input) == '[') {
185 /* raw ipv6 address */
186 if (!http_7239_extract_ipv6(input, NULL))
187 return 0; /* invalid addr */
188 }
189 else {
190 /* ipv4 or dns */
191 while (istlen(*input)) {
192 if (!isalnum((unsigned char)*istptr(*input)) &&
193 *istptr(*input) != '.')
194 break; /* end of hostname token */
195 *input = istnext(*input);
196 }
197 }
198 if (istlen(*input) < 1 || *istptr(*input) != ':') {
199 goto out; /* no optional port provided */
200 }
201 if (!quoted)
202 return 0; /* not supported */
203 *input = istnext(*input); /* skip ':' */
204 /* validate port */
205 if (!http_7239_extract_port(input, NULL))
206 return 0; /* invalid port */
207 out:
208 if (host)
209 host->len = (input->ptr - host->ptr);
210 return 1;
211}
212
213/* checks if <input> contains rfc7239 compliant nodename
214 * <quoted> is used to determine if the current input is being extracted
215 * from a quoted (non zero) or unquoted (zero) token, as the parsing rules
Ilya Shipitsin07be66d2023-04-01 12:26:42 +0200216 * differ whether the input is quoted or not according to the rfc.
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +0100217 * Returns 1 for success and 0 for failure
218 * if <nodename> is not NULL, it will be set to the extracted value contained
219 * in <input>
220 * <input> will be consumed accordingly (parsed/extracted characters are
221 * removed from <input>)
222 */
223static inline int http_7239_extract_nodename(struct ist *input, struct forwarded_header_nodename *nodename, int quoted)
224{
225 if (istlen(*input) < 1)
226 return 0; /* invalid input */
227 if (*istptr(*input) == '_') {
228 struct ist *obfs = NULL;
229
230 /* obfuscated nodename */
231 *input = istnext(*input); /* skip '_' */
232 if (nodename) {
233 nodename->type = FORWARDED_HEADER_OBFS;
234 obfs = &nodename->obfs;
235 }
236 if (!http_7239_extract_obfs(input, obfs))
237 return 0; /* invalid obfs */
238 } else if (*istptr(*input) == 'u') {
239 /* "unknown" nodename? */
240 if (istlen(*input) < 7 ||
241 strncmp("unknown", istptr(*input), 7))
242 return 0; /* syntax error */
243 *input = istadv(*input, 7); /* skip "unknown" */
244 if (nodename)
245 nodename->type = FORWARDED_HEADER_UNK;
246 } else if (quoted && *istptr(*input) == '[') {
247 struct in6_addr *ip6 = NULL;
248
249 /* ipv6 address */
250 if (nodename) {
251 struct sockaddr_in6 *addr = (void *)&nodename->ip;
252
253 ip6 = &addr->sin6_addr;
254 addr->sin6_family = AF_INET6;
255 nodename->type = FORWARDED_HEADER_IP;
256 }
257 if (!http_7239_extract_ipv6(input, ip6))
258 return 0; /* invalid ip6 */
259 } else if (*istptr(*input)) {
260 struct in_addr *ip = NULL;
261
262 /* ipv4 address */
263 if (nodename) {
264 struct sockaddr_in *addr = (void *)&nodename->ip;
265
266 ip = &addr->sin_addr;
267 addr->sin_family = AF_INET;
268 nodename->type = FORWARDED_HEADER_IP;
269 }
270 if (!http_7239_extract_ipv4(input, ip))
271 return 0; /* invalid ip */
272 } else
273 return 0; /* unexpected char */
274
275 /* ok */
276 return 1;
277}
278
279/* checks if <input> contains rfc7239 compliant nodeport
280 * <quoted> is used to determine if the current input is being extracted
281 * from a quoted (non zero) or unquoted (zero) token, as the parsing rules
Ilya Shipitsin07be66d2023-04-01 12:26:42 +0200282 * differ whether the input is quoted or not according to the rfc.
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +0100283 * Returns 1 for success and 0 for failure
284 * if <nodeport> is not NULL, it will be set to the extracted value contained
285 * in <input>
286 * <input> will be consumed accordingly (parsed/extracted characters are
287 * removed from <input>)
288 */
289static inline int http_7239_extract_nodeport(struct ist *input, struct forwarded_header_nodeport *nodeport)
290{
291 if (*istptr(*input) == '_') {
292 struct ist *obfs = NULL;
293
294 /* obfuscated nodeport */
295 *input = istnext(*input); /* skip '_' */
296 if (nodeport) {
297 nodeport->type = FORWARDED_HEADER_OBFS;
298 obfs = &nodeport->obfs;
299 }
300 if (!http_7239_extract_obfs(input, obfs))
301 return 0; /* invalid obfs */
302 } else {
303 uint16_t *port = NULL;
304
305 /* normal port */
306 if (nodeport) {
307 nodeport->type = FORWARDED_HEADER_PORT;
308 port = &nodeport->port;
309 }
310 if (!http_7239_extract_port(input, port))
311 return 0; /* invalid port */
312 }
313 /* ok */
314 return 1;
315}
316
317/* checks if <input> contains rfc7239 compliant node (nodename:nodeport token)
318 * <quoted> is used to determine if the current input is being extracted
319 * from a quoted (non zero) or unquoted (zero) token, as the parsing rules
Ilya Shipitsin07be66d2023-04-01 12:26:42 +0200320 * differ whether the input is quoted or not according to the rfc.
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +0100321 * Returns 1 for success and 0 for failure
322 * if <node> is not NULL, it will be set to the extracted value contained
323 * in <input>
324 * <input> will be consumed accordingly (parsed/extracted characters are
325 * removed from <input>)
326 */
327static inline int http_7239_extract_node(struct ist *input, struct forwarded_header_node *node, int quoted)
328{
329 struct forwarded_header_nodename *nodename = NULL;
330 struct forwarded_header_nodeport *nodeport = NULL;
331
332 if (node) {
333 nodename = &node->nodename;
334 nodeport = &node->nodeport;
335 node->raw.ptr = input->ptr;
336 }
337 if (!http_7239_extract_nodename(input, nodename, quoted))
338 return 0; /* invalid nodename */
339 if (istlen(*input) < 1 || *istptr(*input) != ':') {
340 if (node)
341 node->nodeport.type = FORWARDED_HEADER_UNK;
342 goto out; /* no optional port provided */
343 }
344 if (!quoted)
345 return 0; /* not supported */
346 *input = istnext(*input);
347 if (!http_7239_extract_nodeport(input, nodeport))
348 return 0; /* invalid nodeport */
349 out:
350 /* ok */
351 if (node)
352 node->raw.len = input->ptr - node->raw.ptr;
353 return 1;
354}
355
356static inline int _forwarded_header_save_ctx(struct forwarded_header_ctx *ctx, int current_step, int required_steps)
357{
358 return (ctx && (current_step & required_steps));
359}
360
361static inline void _forwarded_header_quote_expected(struct ist *hdr, uint8_t *quoted)
362{
363 if (istlen(*hdr) > 0 && *istptr(*hdr) == '"') {
364 *quoted = 1;
365 /* node is quoted, we must find corresponding
366 * ending quote at the end of the token
367 */
368 *hdr = istnext(*hdr); /* skip quote */
369 }
370}
371
372/* checks if current header <hdr> is RFC 7239 compliant and can be "trusted".
373 * function will stop parsing as soon as every <required_steps> have
374 * been validated or error is encountered.
375 * Provide FORWARDED_HEADER_ALL for a full header validating spectrum.
376 * You may provide limited scope to perform quick searches on specific attributes
377 * If <ctx> is provided (not NULL), parsed attributes will be stored according to
378 * their types, allowing you to extract some useful information from the header.
379 * Returns 0 on failure and <validated_steps> bitfield on success.
380 */
381int http_validate_7239_header(struct ist hdr, int required_steps, struct forwarded_header_ctx *ctx)
382{
383 int validated_steps = 0;
384 int current_step = 0;
385 uint8_t first = 1;
386 uint8_t quoted = 0;
387
388 while (istlen(hdr) && (required_steps & ~validated_steps)) {
389 if (!first) {
390 if (*istptr(hdr) == ';')
391 hdr = istnext(hdr); /* skip ';' */
392 else
393 goto not_ok; /* unexpected char */
394 }
395 else
396 first = 0;
397
398 if (!(validated_steps & FORWARDED_HEADER_FOR) && istlen(hdr) > 4 &&
399 strncmp("for=", istptr(hdr), 4) == 0) {
400 struct forwarded_header_node *node = NULL;
401
402 /* for parameter */
403 current_step = FORWARDED_HEADER_FOR;
404 hdr = istadv(hdr, 4); /* skip "for=" */
405 _forwarded_header_quote_expected(&hdr, &quoted);
406 if (_forwarded_header_save_ctx(ctx, current_step, required_steps))
407 node = &ctx->nfor;
408 /* validate node */
409 if (!http_7239_extract_node(&hdr, node, quoted))
410 goto not_ok; /* invalid node */
411 }
412 else if (!(validated_steps & FORWARDED_HEADER_BY) && istlen(hdr) > 3 &&
413 strncmp("by=", istptr(hdr), 3) == 0) {
414 struct forwarded_header_node *node = NULL;
415
416 /* by parameter */
417 current_step = FORWARDED_HEADER_BY;
418 hdr = istadv(hdr, 3); /* skip "by=" */
419 _forwarded_header_quote_expected(&hdr, &quoted);
420 if (_forwarded_header_save_ctx(ctx, current_step, required_steps))
421 node = &ctx->nby;
422 /* validate node */
423 if (!http_7239_extract_node(&hdr, node, quoted))
424 goto not_ok; /* invalid node */
425 }
426 else if (!(validated_steps & FORWARDED_HEADER_HOST) && istlen(hdr) > 5 &&
427 strncmp("host=", istptr(hdr), 5) == 0) {
428 struct ist *host = NULL;
429
430 /* host parameter */
431 current_step = FORWARDED_HEADER_HOST;
432 hdr = istadv(hdr, 5); /* skip "host=" */
433 _forwarded_header_quote_expected(&hdr, &quoted);
434 if (_forwarded_header_save_ctx(ctx, current_step, required_steps))
435 host = &ctx->host;
436 /* validate host */
437 if (!http_7239_extract_host(&hdr, host, quoted))
438 goto not_ok; /* invalid host */
439 }
440 else if (!(validated_steps & FORWARDED_HEADER_PROTO) && istlen(hdr) > 6 &&
441 strncmp("proto=", istptr(hdr), 6) == 0) {
442 /* proto parameter */
443 current_step = FORWARDED_HEADER_PROTO;
444 hdr = istadv(hdr, 6); /* skip "proto=" */
445 /* validate proto (only common used http|https are supported for now) */
446 if (istlen(hdr) < 4 || strncmp("http", istptr(hdr), 4))
447 goto not_ok;
448 hdr = istadv(hdr, 4); /* skip "http" */
449 if (istlen(hdr) && *istptr(hdr) == 's') {
450 hdr = istnext(hdr);
451 if (_forwarded_header_save_ctx(ctx, current_step, required_steps))
452 ctx->proto = FORWARDED_HEADER_HTTPS;
453 } else if (_forwarded_header_save_ctx(ctx, current_step, required_steps))
454 ctx->proto = FORWARDED_HEADER_HTTP;
455 /* rfc allows for potential proto quoting, but we don't support
456 * it: it is not common usage
457 */
458 }
459 else {
460 /* not supported
461 * rfc allows for upcoming extensions
462 * but obviously, we can't trust them
463 * as they are not yet standardized
464 */
465
466 goto not_ok;
467 }
468 /* quote check */
469 if (quoted) {
470 if (istlen(hdr) < 1 || *istptr(hdr) != '"') {
471 /* matching ending quote not found */
472 goto not_ok;
473 }
474 hdr = istnext(hdr); /* skip ending quote */
475 quoted = 0; /* reset */
476 }
477 validated_steps |= current_step;
478 }
479
480 return validated_steps;
481
482 not_ok:
483 return 0;
484}
485
Aurelien DARRAGON8436c912023-01-30 09:28:57 +0100486static inline void _7239_print_ip6(struct buffer *out, struct in6_addr *ip6_addr, int quoted)
487{
488 char pn[INET6_ADDRSTRLEN];
489
490 inet_ntop(AF_INET6,
491 ip6_addr,
492 pn, sizeof(pn));
493 if (!quoted)
494 chunk_appendf(out, "\""); /* explicit quoting required for ipv6 */
495 chunk_appendf(out, "[%s]", pn);
496}
497
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +0100498static inline void http_build_7239_header_nodename(struct buffer *out,
499 struct stream *s, struct proxy *curproxy,
500 const struct sockaddr_storage *addr,
501 struct http_ext_7239_forby *forby)
502{
503 struct in6_addr *ip6_addr;
Aurelien DARRAGON8436c912023-01-30 09:28:57 +0100504 int quoted = !!forby->np_mode;
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +0100505
506 if (forby->nn_mode == HTTP_7239_FORBY_ORIG) {
507 if (addr && addr->ss_family == AF_INET) {
508 unsigned char *pn = (unsigned char *)&((struct sockaddr_in *)addr)->sin_addr;
509
510 chunk_appendf(out, "%d.%d.%d.%d", pn[0], pn[1], pn[2], pn[3]);
511 }
512 else if (addr && addr->ss_family == AF_INET6) {
513 ip6_addr = &((struct sockaddr_in6 *)addr)->sin6_addr;
Aurelien DARRAGON8436c912023-01-30 09:28:57 +0100514 _7239_print_ip6(out, ip6_addr, quoted);
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +0100515 }
516 /* else: not supported */
517 }
518 else if (forby->nn_mode == HTTP_7239_FORBY_SMP && forby->nn_expr) {
519 struct sample *smp;
520
521 smp = sample_process(curproxy, s->sess, s,
522 SMP_OPT_DIR_REQ | SMP_OPT_FINAL, forby->nn_expr, NULL);
523
524 if (smp) {
525 if (smp->data.type == SMP_T_IPV6) {
526 /* smp is valid IP6, print with RFC compliant output */
527 ip6_addr = &smp->data.u.ipv6;
Aurelien DARRAGON8436c912023-01-30 09:28:57 +0100528 _7239_print_ip6(out, ip6_addr, quoted);
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +0100529 }
Aurelien DARRAGON8436c912023-01-30 09:28:57 +0100530 else if (sample_casts[smp->data.type][SMP_T_STR] &&
531 sample_casts[smp->data.type][SMP_T_STR](smp)) {
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +0100532 struct ist validate_n = ist2(smp->data.u.str.area, smp->data.u.str.data);
533 struct ist validate_o = ist2(smp->data.u.str.area, smp->data.u.str.data);
534 struct forwarded_header_nodename nodename;
535
536 /* validate nodename */
537 if (http_7239_extract_nodename(&validate_n, &nodename, 1) &&
538 !istlen(validate_n)) {
539 if (nodename.type == FORWARDED_HEADER_IP &&
540 nodename.ip.ss_family == AF_INET6) {
541 /* special care needed for valid ip6 nodename (quoting) */
542 ip6_addr = &((struct sockaddr_in6 *)&nodename.ip)->sin6_addr;
Aurelien DARRAGON8436c912023-01-30 09:28:57 +0100543 _7239_print_ip6(out, ip6_addr, quoted);
544 } else {
545 /* no special care needed, input is already rfc compliant,
546 * just print as regular non quoted string
547 */
548 chunk_cat(out, &smp->data.u.str);
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +0100549 }
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +0100550 }
551 else if (http_7239_extract_obfs(&validate_o, NULL) &&
552 !istlen(validate_o)) {
553 /* raw user input that should be printed as 7239 obfs */
554 chunk_appendf(out, "_%.*s", (int)smp->data.u.str.data, smp->data.u.str.area);
555 }
556 /* else: not compliant */
557 }
558 /* else: cannot be casted to str */
559 }
560 /* else: smp error */
561 }
562}
563
564static inline void http_build_7239_header_nodeport(struct buffer *out,
565 struct stream *s, struct proxy *curproxy,
566 const struct sockaddr_storage *addr,
567 struct http_ext_7239_forby *forby)
568{
569 if (forby->np_mode == HTTP_7239_FORBY_ORIG) {
570 if (addr && addr->ss_family == AF_INET)
571 chunk_appendf(out, "%d", ntohs(((struct sockaddr_in *)addr)->sin_port));
572 else if (addr && addr->ss_family == AF_INET6)
573 chunk_appendf(out, "%d", ntohs(((struct sockaddr_in6 *)addr)->sin6_port));
574 /* else: not supported */
575 }
576 else if (forby->np_mode == HTTP_7239_FORBY_SMP && forby->np_expr) {
577 struct sample *smp;
578
579 smp = sample_fetch_as_type(curproxy, s->sess, s,
580 SMP_OPT_DIR_REQ | SMP_OPT_FINAL, forby->np_expr, SMP_T_STR);
581 if (smp) {
582 struct ist validate_n = ist2(smp->data.u.str.area, smp->data.u.str.data);
583 struct ist validate_o = ist2(smp->data.u.str.area, smp->data.u.str.data);
584
585 /* validate nodeport */
586 if (http_7239_extract_nodeport(&validate_n, NULL) &&
587 !istlen(validate_n)) {
588 /* no special care needed, input is already rfc compliant,
589 * just print as regular non quoted string
590 */
591 chunk_cat(out, &smp->data.u.str);
592 }
593 else if (http_7239_extract_obfs(&validate_o, NULL) &&
594 !istlen(validate_o)) {
595 /* raw user input that should be printed as 7239 obfs */
596 chunk_appendf(out, "_%.*s", (int)smp->data.u.str.data, smp->data.u.str.area);
597 }
598 /* else: not compliant */
599 }
600 /* else: smp error */
601 }
602}
603
604static inline void http_build_7239_header_node(struct buffer *out,
605 struct stream *s, struct proxy *curproxy,
606 const struct sockaddr_storage *addr,
607 struct http_ext_7239_forby *forby)
608{
609 size_t offset_start;
610 size_t offset_save;
611
612 offset_start = out->data;
613 if (forby->np_mode)
614 chunk_appendf(out, "\"");
615 offset_save = out->data;
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +0100616 http_build_7239_header_nodename(out, s, curproxy, addr, forby);
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +0100617 if (offset_save == out->data) {
618 /* could not build nodename, either because some
619 * data is not available or user is providing bad input
620 */
621 chunk_appendf(out, "unknown");
622 }
623 if (forby->np_mode) {
624 chunk_appendf(out, ":");
625 offset_save = out->data;
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +0100626 http_build_7239_header_nodeport(out, s, curproxy, addr, forby);
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +0100627 if (offset_save == out->data) {
628 /* could not build nodeport, either because some data is
629 * not available or user is providing bad input
630 */
631 out->data = offset_save - 1;
632 }
633 }
634 if (out->data != offset_start && out->area[offset_start] == '"')
635 chunk_appendf(out, "\""); /* add matching end quote */
636}
637
638static inline void http_build_7239_header_host(struct buffer *out,
639 struct stream *s, struct proxy *curproxy,
640 struct htx *htx, struct http_ext_7239_host *host)
641{
642 struct http_hdr_ctx ctx = { .blk = NULL };
643 char *str = NULL;
644 int str_len = 0;
645
646 if (host->mode == HTTP_7239_HOST_ORIG &&
647 http_find_header(htx, ist("host"), &ctx, 0)) {
648 str = ctx.value.ptr;
649 str_len = ctx.value.len;
650 print_host:
651 {
652 struct ist validate = ist2(str, str_len);
653 /* host check, to ensure rfc compliant output
Ilya Shipitsin07be66d2023-04-01 12:26:42 +0200654 * (assuming host is quoted/escaped)
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +0100655 */
656 if (http_7239_extract_host(&validate, NULL, 1) && !istlen(validate))
657 chunk_memcat(out, str, str_len);
658 /* else: not compliant or partially compliant */
659 }
660
661 }
662 else if (host->mode == HTTP_7239_HOST_SMP && host->expr) {
663 struct sample *smp;
664
665 smp = sample_fetch_as_type(curproxy, s->sess, s,
666 SMP_OPT_DIR_REQ | SMP_OPT_FINAL, host->expr, SMP_T_STR);
667 if (smp) {
668 str = smp->data.u.str.area;
669 str_len = smp->data.u.str.data;
670 goto print_host;
671 }
672 /* else: smp error */
673 }
674}
675
676/* Tries build 7239 header according to <curproxy> parameters and <s> context
677 * It both depends on <curproxy>->http_ext->fwd for config and <s> for request
678 * context data.
679 * The function will write output to <out> buffer
680 * Returns 1 for success and 0 for error (ie: not enough space in buffer)
681 */
682static int http_build_7239_header(struct buffer *out,
683 struct stream *s, struct proxy *curproxy, struct htx *htx)
684{
685 struct connection *cli_conn = objt_conn(strm_sess(s)->origin);
686
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +0100687 if (curproxy->http_ext->fwd->p_proto) {
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +0100688 chunk_appendf(out, "%sproto=%s", ((out->data) ? ";" : ""),
689 ((conn_is_ssl(cli_conn)) ? "https" : "http"));
690 }
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +0100691 if (curproxy->http_ext->fwd->p_host.mode) {
Ilya Shipitsin07be66d2023-04-01 12:26:42 +0200692 /* always add quotes for host parameter to make output compliance checks simpler */
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +0100693 chunk_appendf(out, "%shost=\"", ((out->data) ? ";" : ""));
694 /* ignore return value for now, but could be useful some day */
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +0100695 http_build_7239_header_host(out, s, curproxy, htx, &curproxy->http_ext->fwd->p_host);
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +0100696 chunk_appendf(out, "\"");
697 }
698
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +0100699 if (curproxy->http_ext->fwd->p_by.nn_mode) {
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +0100700 const struct sockaddr_storage *dst = sc_dst(s->scf);
701
702 chunk_appendf(out, "%sby=", ((out->data) ? ";" : ""));
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +0100703 http_build_7239_header_node(out, s, curproxy, dst, &curproxy->http_ext->fwd->p_by);
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +0100704 }
705
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +0100706 if (curproxy->http_ext->fwd->p_for.nn_mode) {
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +0100707 const struct sockaddr_storage *src = sc_src(s->scf);
708
709 chunk_appendf(out, "%sfor=", ((out->data) ? ";" : ""));
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +0100710 http_build_7239_header_node(out, s, curproxy, src, &curproxy->http_ext->fwd->p_for);
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +0100711 }
712 if (unlikely(out->data == out->size)) {
713 /* not enough space in buffer, error */
714 return 0;
715 }
716 return 1;
717}
718
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +0100719/* This function will try to inject RFC 7239 forwarded header if
720 * configured on the backend (ignored for frontends).
721 * Will do nothing if the option is not enabled on the proxy.
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +0100722 * Returns 1 for success and 0 for failure
723 */
724int http_handle_7239_header(struct stream *s, struct channel *req)
725{
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +0100726 struct proxy *curproxy = s->be; /* ignore frontend */
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +0100727
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +0100728 if (curproxy->http_ext && curproxy->http_ext->fwd) {
729 struct htx *htx = htxbuf(&req->buf);
730 int validate = 1;
731 struct http_hdr_ctx find = { .blk = NULL };
732 struct http_hdr_ctx last = { .blk = NULL};
733 struct ist hdr = ist("forwarded");
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +0100734
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +0100735 /* ok, let's build forwarded header */
736 chunk_reset(&trash);
737 if (unlikely(!http_build_7239_header(&trash, s, curproxy, htx)))
738 return 0; /* error when building header (bad user conf or memory error) */
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +0100739
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +0100740 /* validate existing forwarded header (including multiple values),
741 * hard stop if error is encountered
742 */
743 while (http_find_header(htx, hdr, &find, 0)) {
744 /* validate current header chunk */
745 if (!http_validate_7239_header(find.value, FORWARDED_HEADER_ALL, NULL)) {
746 /* at least one error, existing forwarded header not OK, add our own
747 * forwarded header, so that it can be trusted
748 */
749 validate = 0;
750 break;
751 }
752 last = find;
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +0100753 }
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +0100754 /* no errors, append our data at the end of existing header */
755 if (last.blk && validate) {
756 if (unlikely(!http_append_header_value(htx, &last, ist2(trash.area, trash.data))))
757 return 0; /* htx error */
758 }
759 else {
760 if (unlikely(!http_add_header(htx, hdr, ist2(trash.area, trash.data))))
761 return 0; /* htx error */
762 }
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +0100763 }
764 return 1;
765}
766
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +0100767/*
768 * add X-Forwarded-For if either the frontend or the backend
769 * asks for it.
Aurelien DARRAGON730b9832022-12-28 18:53:05 +0100770 * Returns 1 for success and 0 for failure
771 */
772int http_handle_xff_header(struct stream *s, struct channel *req)
773{
774 struct session *sess = s->sess;
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +0100775 struct http_ext_xff *f_xff = NULL;
776 struct http_ext_xff *b_xff = NULL;
Aurelien DARRAGON730b9832022-12-28 18:53:05 +0100777
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +0100778 if (sess->fe->http_ext && sess->fe->http_ext->xff) {
779 /* frontend */
780 f_xff = sess->fe->http_ext->xff;
Aurelien DARRAGON730b9832022-12-28 18:53:05 +0100781 }
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +0100782 if (s->be->http_ext && s->be->http_ext->xff) {
783 /* backend */
784 b_xff = s->be->http_ext->xff;
785 }
786
787 if (f_xff || b_xff) {
788 struct htx *htx = htxbuf(&req->buf);
789 const struct sockaddr_storage *src = sc_src(s->scf);
790 struct http_hdr_ctx ctx = { .blk = NULL };
791 struct ist hdr = ((b_xff) ? b_xff->hdr_name : f_xff->hdr_name);
Aurelien DARRAGON730b9832022-12-28 18:53:05 +0100792
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +0100793 if (f_xff && f_xff->mode == HTTP_XFF_IFNONE &&
794 b_xff && b_xff->mode == HTTP_XFF_IFNONE &&
795 http_find_header(htx, hdr, &ctx, 0)) {
796 /* The header is set to be added only if none is present
797 * and we found it, so don't do anything.
Aurelien DARRAGON730b9832022-12-28 18:53:05 +0100798 */
Aurelien DARRAGON730b9832022-12-28 18:53:05 +0100799 }
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +0100800 else if (src && src->ss_family == AF_INET) {
801 /* Add an X-Forwarded-For header unless the source IP is
802 * in the 'except' network range.
803 */
804 if ((!f_xff || ipcmp2net(src, &f_xff->except_net)) &&
805 (!b_xff || ipcmp2net(src, &b_xff->except_net))) {
806 unsigned char *pn = (unsigned char *)&((struct sockaddr_in *)src)->sin_addr;
Aurelien DARRAGON730b9832022-12-28 18:53:05 +0100807
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +0100808 /* Note: we rely on the backend to get the header name to be used for
809 * x-forwarded-for, because the header is really meant for the backends.
810 * However, if the backend did not specify any option, we have to rely
811 * on the frontend's header name.
812 */
813 chunk_printf(&trash, "%d.%d.%d.%d", pn[0], pn[1], pn[2], pn[3]);
814 if (unlikely(!http_add_header(htx, hdr, ist2(trash.area, trash.data))))
815 return 0;
816 }
817 }
818 else if (src && src->ss_family == AF_INET6) {
819 /* Add an X-Forwarded-For header unless the source IP is
820 * in the 'except' network range.
Aurelien DARRAGON730b9832022-12-28 18:53:05 +0100821 */
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +0100822 if ((!f_xff || ipcmp2net(src, &f_xff->except_net)) &&
823 (!b_xff || ipcmp2net(src, &b_xff->except_net))) {
824 char pn[INET6_ADDRSTRLEN];
825
826 inet_ntop(AF_INET6,
827 (const void *)&((struct sockaddr_in6 *)(src))->sin6_addr,
828 pn, sizeof(pn));
829
830 /* Note: we rely on the backend to get the header name to be used for
831 * x-forwarded-for, because the header is really meant for the backends.
832 * However, if the backend did not specify any option, we have to rely
833 * on the frontend's header name.
834 */
835 chunk_printf(&trash, "%s", pn);
836 if (unlikely(!http_add_header(htx, hdr, ist2(trash.area, trash.data))))
837 return 0;
838 }
Aurelien DARRAGON730b9832022-12-28 18:53:05 +0100839 }
840 }
Aurelien DARRAGON730b9832022-12-28 18:53:05 +0100841 return 1;
842}
843
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +0100844/*
845 * add X-Original-To if either the frontend or the backend
846 * asks for it.
Aurelien DARRAGONf9583412022-12-29 15:45:41 +0100847 * Returns 1 for success and 0 for failure
848 */
849int http_handle_xot_header(struct stream *s, struct channel *req)
850{
851 struct session *sess = s->sess;
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +0100852 struct http_ext_xot *f_xot = NULL;
853 struct http_ext_xot *b_xot = NULL;
Aurelien DARRAGONf9583412022-12-29 15:45:41 +0100854
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +0100855 if (sess->fe->http_ext && sess->fe->http_ext->xot) {
856 /* frontend */
857 f_xot = sess->fe->http_ext->xot;
858 }
859 if (s->be->http_ext && s->be->http_ext->xot) {
860 /* backend */
861 BUG_ON(!s->be->http_ext);
862 b_xot = s->be->http_ext->xot;
863 }
Aurelien DARRAGONf9583412022-12-29 15:45:41 +0100864
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +0100865 if (f_xot || b_xot) {
866 struct htx *htx = htxbuf(&req->buf);
867 const struct sockaddr_storage *dst = sc_dst(s->scf);
868 struct ist hdr = ((b_xot) ? b_xot->hdr_name : f_xot->hdr_name);
Aurelien DARRAGONf9583412022-12-29 15:45:41 +0100869
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +0100870 if (dst && dst->ss_family == AF_INET) {
871 /* Add an X-Original-To header unless the destination IP is
872 * in the 'except' network range.
Aurelien DARRAGONf9583412022-12-29 15:45:41 +0100873 */
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +0100874 if ((!f_xot || ipcmp2net(dst, &f_xot->except_net)) &&
875 (!b_xot || ipcmp2net(dst, &b_xot->except_net))) {
876 unsigned char *pn = (unsigned char *)&((struct sockaddr_in *)dst)->sin_addr;
877
878 /* Note: we rely on the backend to get the header name to be used for
879 * x-original-to, because the header is really meant for the backends.
880 * However, if the backend did not specify any option, we have to rely
881 * on the frontend's header name.
882 */
883 chunk_printf(&trash, "%d.%d.%d.%d", pn[0], pn[1], pn[2], pn[3]);
884 if (unlikely(!http_add_header(htx, hdr, ist2(trash.area, trash.data))))
885 return 0;
886 }
Aurelien DARRAGONf9583412022-12-29 15:45:41 +0100887 }
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +0100888 else if (dst && dst->ss_family == AF_INET6) {
889 /* Add an X-Original-To header unless the source IP is
890 * in the 'except' network range.
891 */
892 if ((!f_xot || ipcmp2net(dst, &f_xot->except_net)) &&
893 (!b_xot || ipcmp2net(dst, &b_xot->except_net))) {
894 char pn[INET6_ADDRSTRLEN];
Aurelien DARRAGONf9583412022-12-29 15:45:41 +0100895
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +0100896 inet_ntop(AF_INET6,
897 (const void *)&((struct sockaddr_in6 *)dst)->sin6_addr,
898 pn, sizeof(pn));
Aurelien DARRAGONf9583412022-12-29 15:45:41 +0100899
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +0100900 /* Note: we rely on the backend to get the header name to be used for
901 * x-forwarded-for, because the header is really meant for the backends.
902 * However, if the backend did not specify any option, we have to rely
903 * on the frontend's header name.
904 */
905 chunk_printf(&trash, "%s", pn);
906 if (unlikely(!http_add_header(htx, hdr, ist2(trash.area, trash.data))))
907 return 0;
908 }
Aurelien DARRAGONf9583412022-12-29 15:45:41 +0100909 }
910 }
911 return 1;
912}
913
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +0100914/*
915 * =========== CONFIG ===========
916 * below are helpers to parse http ext options from the config
917 */
918static int proxy_http_parse_oom(const char *file, int linenum)
919{
920 int err_code = 0;
921
922 ha_alert("parsing [%s:%d]: out of memory.\n", file, linenum);
923 err_code |= ERR_ALERT | ERR_ABORT;
924 return err_code;
925}
926
927static inline int _proxy_http_parse_7239_expr(char **args, int *cur_arg,
928 const char *file, int linenum,
929 char **expr_s)
930{
931 int err_code = 0;
932
933 if (!*args[*cur_arg + 1]) {
934 ha_alert("parsing [%s:%d]: '%s' expects <expr> as argument.\n",
935 file, linenum, args[*cur_arg]);
936 err_code |= ERR_ALERT | ERR_FATAL;
937 goto out;
938 }
939 *cur_arg += 1;
940 ha_free(expr_s);
941 *expr_s = strdup(args[*cur_arg]);
942 if (!*expr_s)
943 return proxy_http_parse_oom(file, linenum);
944 *cur_arg += 1;
945 out:
946 return err_code;
947}
948
Aurelien DARRAGON39254ca2023-03-03 13:11:36 +0100949/* forwarded/7239 RFC: tries to parse "option forwarded" config keyword
950 * Returns a composition of ERR_ABORT, ERR_ALERT, ERR_FATAL, ERR_WARN
951 */
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +0100952int proxy_http_parse_7239(char **args, int cur_arg,
953 struct proxy *curproxy, const struct proxy *defpx,
954 const char *file, int linenum)
955{
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +0100956 struct http_ext_7239 *fwd;
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +0100957 int err_code = 0;
958
959 if (warnifnotcap(curproxy, PR_CAP_BE, file, linenum, "option forwarded", NULL)) {
960 /* option is ignored for frontends */
961 err_code |= ERR_WARN;
962 goto out;
963 }
964
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +0100965 if (!http_ext_7239_prepare(curproxy))
966 return proxy_http_parse_oom(file, linenum);
967
968 fwd = curproxy->http_ext->fwd;
969
970 fwd->p_proto = 0;
971 fwd->p_host.mode = 0;
972 fwd->p_for.nn_mode = 0;
973 fwd->p_for.np_mode = 0;
974 fwd->p_by.nn_mode = 0;
975 fwd->p_by.np_mode = 0;
976 ha_free(&fwd->c_file);
977 fwd->c_file = strdup(file);
978 fwd->c_line = linenum;
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +0100979
980 /* start at 2, since 0+1 = "option" "forwarded" */
981 cur_arg = 2;
982 if (!*(args[cur_arg])) {
983 /* no optional argument provided, use default settings */
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +0100984 fwd->p_for.nn_mode = HTTP_7239_FORBY_ORIG; /* enable for and mimic xff */
985 fwd->p_proto = 1; /* enable proto */
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +0100986 goto out;
987 }
988 /* loop to go through optional arguments */
989 while (*(args[cur_arg])) {
990 if (strcmp(args[cur_arg], "proto") == 0) {
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +0100991 fwd->p_proto = 1;
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +0100992 cur_arg += 1;
993 } else if (strcmp(args[cur_arg], "host") == 0) {
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +0100994 fwd->p_host.mode = HTTP_7239_HOST_ORIG;
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +0100995 cur_arg += 1;
996 } else if (strcmp(args[cur_arg], "host-expr") == 0) {
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +0100997 fwd->p_host.mode = HTTP_7239_HOST_SMP;
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +0100998 err_code |= _proxy_http_parse_7239_expr(args, &cur_arg, file, linenum,
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +0100999 &fwd->p_host.expr_s);
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001000 if (err_code & ERR_FATAL)
1001 goto out;
1002 } else if (strcmp(args[cur_arg], "by") == 0) {
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001003 fwd->p_by.nn_mode = HTTP_7239_FORBY_ORIG;
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001004 cur_arg += 1;
1005 } else if (strcmp(args[cur_arg], "by-expr") == 0) {
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001006 fwd->p_by.nn_mode = HTTP_7239_FORBY_SMP;
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001007 err_code |= _proxy_http_parse_7239_expr(args, &cur_arg, file, linenum,
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001008 &fwd->p_by.nn_expr_s);
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001009 if (err_code & ERR_FATAL)
1010 goto out;
1011 } else if (strcmp(args[cur_arg], "for") == 0) {
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001012 fwd->p_for.nn_mode = HTTP_7239_FORBY_ORIG;
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001013 cur_arg += 1;
1014 } else if (strcmp(args[cur_arg], "for-expr") == 0) {
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001015 fwd->p_for.nn_mode = HTTP_7239_FORBY_SMP;
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001016 err_code |= _proxy_http_parse_7239_expr(args, &cur_arg, file, linenum,
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001017 &fwd->p_for.nn_expr_s);
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001018 if (err_code & ERR_FATAL)
1019 goto out;
1020 } else if (strcmp(args[cur_arg], "by_port") == 0) {
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001021 fwd->p_by.np_mode = HTTP_7239_FORBY_ORIG;
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001022 cur_arg += 1;
1023 } else if (strcmp(args[cur_arg], "by_port-expr") == 0) {
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001024 fwd->p_by.np_mode = HTTP_7239_FORBY_SMP;
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001025 err_code |= _proxy_http_parse_7239_expr(args, &cur_arg, file, linenum,
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001026 &fwd->p_by.np_expr_s);
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001027 if (err_code & ERR_FATAL)
1028 goto out;
1029 } else if (strcmp(args[cur_arg], "for_port") == 0) {
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001030 fwd->p_for.np_mode = HTTP_7239_FORBY_ORIG;
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001031 cur_arg += 1;
1032 } else if (strcmp(args[cur_arg], "for_port-expr") == 0) {
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001033 fwd->p_for.np_mode = HTTP_7239_FORBY_SMP;
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001034 err_code |= _proxy_http_parse_7239_expr(args, &cur_arg, file, linenum,
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001035 &fwd->p_for.np_expr_s);
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001036 if (err_code & ERR_FATAL)
1037 goto out;
1038 } else {
1039 /* unknown suboption - catchall */
1040 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'proto', 'host', "
1041 "'host-expr', 'by', 'by-expr', 'by_port', 'by_port-expr', "
1042 "'for', 'for-expr', 'for_port' and 'for_port-expr'.\n",
1043 file, linenum, args[0], args[1]);
1044 err_code |= ERR_ALERT | ERR_FATAL;
1045 goto out;
1046 }
1047 } /* end while loop */
1048
1049 /* consistency check */
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001050 if (fwd->p_by.np_mode &&
1051 !fwd->p_by.nn_mode) {
1052 fwd->p_by.np_mode = 0;
1053 ha_free(&fwd->p_by.np_expr_s);
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001054 ha_warning("parsing [%s:%d] : '%s %s' : '%s' will be ignored because both 'by' "
1055 "and 'by-expr' are unset\n",
1056 file, linenum, args[0], args[1],
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001057 ((fwd->p_by.np_mode == HTTP_7239_FORBY_ORIG) ? "by_port" : "by_port-expr"));
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001058 err_code |= ERR_WARN;
1059 }
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001060 if (fwd->p_for.np_mode &&
1061 !fwd->p_for.nn_mode) {
1062 fwd->p_for.np_mode = 0;
1063 ha_free(&fwd->p_for.np_expr_s);
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001064 ha_warning("parsing [%s:%d] : '%s %s' : '%s' will be ignored because both 'for' "
1065 "and 'for-expr' are unset\n",
1066 file, linenum, args[0], args[1],
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001067 ((fwd->p_for.np_mode == HTTP_7239_FORBY_ORIG) ? "for_port" : "for_port-expr"));
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001068 err_code |= ERR_WARN;
1069 }
1070
1071 out:
1072 return err_code;
1073}
1074
Aurelien DARRAGONc07001b2023-01-24 17:55:09 +01001075/* rfc7239 forwarded option needs a postparsing step
1076 * to convert parsing hints into runtime usable sample expressions
1077 * Returns a composition of ERR_NONE, ERR_FATAL, ERR_ALERT, ERR_WARN
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001078 */
1079int proxy_http_compile_7239(struct proxy *curproxy)
1080{
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001081 struct http_ext_7239 *fwd;
Aurelien DARRAGONc07001b2023-01-24 17:55:09 +01001082 int err = ERR_NONE;
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001083 int loop;
1084
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001085 if (!(curproxy->cap & PR_CAP_BE)) {
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001086 /* no backend cap: not supported (ie: frontend) */
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001087 goto out;
1088 }
1089
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001090 /* should not happen (test should be performed after BE cap test) */
1091 BUG_ON(!curproxy->http_ext || !curproxy->http_ext->fwd);
1092
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001093 curproxy->conf.args.ctx = ARGC_OPT; /* option */
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001094 curproxy->conf.args.file = curproxy->http_ext->fwd->c_file;
1095 curproxy->conf.args.line = curproxy->http_ext->fwd->c_line;
1096 fwd = curproxy->http_ext->fwd;
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001097
Aurelien DARRAGON9ded8342023-01-06 12:16:28 +01001098 /* it is important that we keep iterating on error to make sure
1099 * all fwd config fields are in the same state (post-parsing state)
1100 */
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001101 for (loop = 0; loop < 5; loop++) {
Aurelien DARRAGON9ded8342023-01-06 12:16:28 +01001102 char **expr_str = NULL;
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001103 struct sample_expr **expr = NULL;
Aurelien DARRAGON9ded8342023-01-06 12:16:28 +01001104 struct sample_expr *cur_expr;
Aurelien DARRAGONc07001b2023-01-24 17:55:09 +01001105 char *err_str = NULL;
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001106 int smp = 0;
1107 int idx = 0;
1108
1109 switch (loop) {
1110 case 0:
1111 /* host */
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001112 expr_str = &fwd->p_host.expr_s;
1113 expr = &fwd->p_host.expr;
1114 smp = (fwd->p_host.mode == HTTP_7239_HOST_SMP);
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001115 break;
1116 case 1:
1117 /* by->node */
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001118 expr_str = &fwd->p_by.nn_expr_s;
1119 expr = &fwd->p_by.nn_expr;
1120 smp = (fwd->p_by.nn_mode == HTTP_7239_FORBY_SMP);
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001121 break;
1122 case 2:
1123 /* by->nodeport */
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001124 expr_str = &fwd->p_by.np_expr_s;
1125 expr = &fwd->p_by.np_expr;
1126 smp = (fwd->p_by.np_mode == HTTP_7239_FORBY_SMP);
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001127 break;
1128 case 3:
1129 /* for->node */
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001130 expr_str = &fwd->p_for.nn_expr_s;
1131 expr = &fwd->p_for.nn_expr;
1132 smp = (fwd->p_for.nn_mode == HTTP_7239_FORBY_SMP);
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001133 break;
1134 case 4:
1135 /* for->nodeport */
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001136 expr_str = &fwd->p_for.np_expr_s;
1137 expr = &fwd->p_for.np_expr;
1138 smp = (fwd->p_for.np_mode == HTTP_7239_FORBY_SMP);
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001139 break;
1140 }
Aurelien DARRAGON9ded8342023-01-06 12:16:28 +01001141 if (!smp)
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001142 continue; /* no expr */
Aurelien DARRAGON9ded8342023-01-06 12:16:28 +01001143
1144 /* expr and expr_str cannot be NULL past this point */
1145 BUG_ON(!expr || !expr_str);
1146
1147 if (!*expr_str) {
1148 /* should not happen unless system memory exhaustion */
1149 ha_alert("%s '%s' [%s:%d]: failed to parse 'option forwarded' expression : %s.\n",
1150 proxy_type_str(curproxy), curproxy->id,
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001151 fwd->c_file, fwd->c_line,
Aurelien DARRAGON9ded8342023-01-06 12:16:28 +01001152 "memory error");
Aurelien DARRAGONc07001b2023-01-24 17:55:09 +01001153 err |= ERR_ALERT | ERR_FATAL;
Aurelien DARRAGON9ded8342023-01-06 12:16:28 +01001154 continue;
1155 }
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001156
Aurelien DARRAGON9ded8342023-01-06 12:16:28 +01001157 cur_expr =
1158 sample_parse_expr((char*[]){*expr_str, NULL}, &idx,
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001159 fwd->c_file,
1160 fwd->c_line,
Aurelien DARRAGONc07001b2023-01-24 17:55:09 +01001161 &err_str, &curproxy->conf.args, NULL);
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001162
Aurelien DARRAGON9ded8342023-01-06 12:16:28 +01001163 if (!cur_expr) {
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001164 ha_alert("%s '%s' [%s:%d]: failed to parse 'option forwarded' expression '%s' in : %s.\n",
1165 proxy_type_str(curproxy), curproxy->id,
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001166 fwd->c_file, fwd->c_line,
Aurelien DARRAGONc07001b2023-01-24 17:55:09 +01001167 *expr_str, err_str);
1168 ha_free(&err_str);
1169 err |= ERR_ALERT | ERR_FATAL;
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001170 }
Aurelien DARRAGONd745a3f2023-01-06 15:06:49 +01001171 else if (!(cur_expr->fetch->val & SMP_VAL_BE_HRQ_HDR)) {
1172 /* fetch not available in this context: sample expr is resolved
1173 * within backend right after headers are processed.
1174 * (in http_process_request())
1175 * -> we simply warn the user about the misuse
1176 */
1177 ha_warning("%s '%s' [%s:%d]: in 'option forwarded' sample expression '%s' : "
1178 "some args extract information from '%s', "
1179 "none of which is available here.\n",
1180 proxy_type_str(curproxy), curproxy->id,
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001181 fwd->c_file, fwd->c_line,
Aurelien DARRAGONd745a3f2023-01-06 15:06:49 +01001182 *expr_str, sample_ckp_names(cur_expr->fetch->use));
Aurelien DARRAGONc07001b2023-01-24 17:55:09 +01001183 err |= ERR_WARN;
Aurelien DARRAGONd745a3f2023-01-06 15:06:49 +01001184 }
Aurelien DARRAGON9ded8342023-01-06 12:16:28 +01001185 /* post parsing individual expr cleanup */
1186 ha_free(expr_str);
1187
1188 /* expr assignment */
1189 *expr = cur_expr;
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001190 }
1191 curproxy->conf.args.file = NULL;
1192 curproxy->conf.args.line = 0;
1193
Aurelien DARRAGON9ded8342023-01-06 12:16:28 +01001194 /* post parsing general cleanup */
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001195 ha_free(&fwd->c_file);
1196 fwd->c_line = 0;
Aurelien DARRAGON9ded8342023-01-06 12:16:28 +01001197
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001198 fwd->c_mode = 1; /* parsing completed */
Aurelien DARRAGON9ded8342023-01-06 12:16:28 +01001199
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001200 out:
Aurelien DARRAGONc07001b2023-01-24 17:55:09 +01001201 return err;
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001202}
1203
Aurelien DARRAGON39254ca2023-03-03 13:11:36 +01001204/* x-forwarded-for: tries to parse "option forwardfor" config keyword
1205 * Returns a composition of ERR_NONE, ERR_FATAL, ERR_ALERT
1206 */
Aurelien DARRAGON730b9832022-12-28 18:53:05 +01001207int proxy_http_parse_xff(char **args, int cur_arg,
1208 struct proxy *curproxy, const struct proxy *defpx,
1209 const char *file, int linenum)
1210{
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001211 struct http_ext_xff *xff;
Aurelien DARRAGON730b9832022-12-28 18:53:05 +01001212 int err_code = 0;
1213
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001214 if (!http_ext_xff_prepare(curproxy))
1215 return proxy_http_parse_oom(file, linenum);
1216
1217 xff = curproxy->http_ext->xff;
1218
Aurelien DARRAGON730b9832022-12-28 18:53:05 +01001219 /* insert x-forwarded-for field, but not for the IP address listed as an except.
1220 * set default options (ie: bitfield, header name, etc)
1221 */
1222
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001223 xff->mode = HTTP_XFF_ALWAYS;
Aurelien DARRAGON730b9832022-12-28 18:53:05 +01001224
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001225 istfree(&xff->hdr_name);
1226 xff->hdr_name = istdup(ist(DEF_XFORWARDFOR_HDR));
1227 if (!isttest(xff->hdr_name))
Aurelien DARRAGON730b9832022-12-28 18:53:05 +01001228 return proxy_http_parse_oom(file, linenum);
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001229 xff->except_net.family = AF_UNSPEC;
Aurelien DARRAGON730b9832022-12-28 18:53:05 +01001230
1231 /* loop to go through arguments - start at 2, since 0+1 = "option" "forwardfor" */
1232 cur_arg = 2;
1233 while (*(args[cur_arg])) {
1234 if (strcmp(args[cur_arg], "except") == 0) {
1235 unsigned char mask;
1236 int i;
1237
1238 /* suboption except - needs additional argument for it */
1239 if (*(args[cur_arg+1]) &&
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001240 str2net(args[cur_arg+1], 1, &xff->except_net.addr.v4.ip, &xff->except_net.addr.v4.mask)) {
1241 xff->except_net.family = AF_INET;
1242 xff->except_net.addr.v4.ip.s_addr &= xff->except_net.addr.v4.mask.s_addr;
Aurelien DARRAGON730b9832022-12-28 18:53:05 +01001243 }
1244 else if (*(args[cur_arg+1]) &&
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001245 str62net(args[cur_arg+1], &xff->except_net.addr.v6.ip, &mask)) {
1246 xff->except_net.family = AF_INET6;
1247 len2mask6(mask, &xff->except_net.addr.v6.mask);
Aurelien DARRAGON730b9832022-12-28 18:53:05 +01001248 for (i = 0; i < 16; i++)
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001249 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 +01001250 }
1251 else {
1252 ha_alert("parsing [%s:%d] : '%s %s %s' expects <address>[/mask] as argument.\n",
1253 file, linenum, args[0], args[1], args[cur_arg]);
1254 err_code |= ERR_ALERT | ERR_FATAL;
1255 goto out;
1256 }
1257 /* flush useless bits */
1258 cur_arg += 2;
1259 } else if (strcmp(args[cur_arg], "header") == 0) {
1260 /* suboption header - needs additional argument for it */
1261 if (*(args[cur_arg+1]) == 0) {
1262 ha_alert("parsing [%s:%d] : '%s %s %s' expects <header_name> as argument.\n",
1263 file, linenum, args[0], args[1], args[cur_arg]);
1264 err_code |= ERR_ALERT | ERR_FATAL;
1265 goto out;
1266 }
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001267 istfree(&xff->hdr_name);
1268 xff->hdr_name = istdup(ist(args[cur_arg+1]));
1269 if (!isttest(xff->hdr_name))
Aurelien DARRAGON730b9832022-12-28 18:53:05 +01001270 return proxy_http_parse_oom(file, linenum);
1271 cur_arg += 2;
1272 } else if (strcmp(args[cur_arg], "if-none") == 0) {
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001273 xff->mode = HTTP_XFF_IFNONE;
Aurelien DARRAGON730b9832022-12-28 18:53:05 +01001274 cur_arg += 1;
1275 } else {
1276 /* unknown suboption - catchall */
1277 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'except', 'header' and 'if-none'.\n",
1278 file, linenum, args[0], args[1]);
1279 err_code |= ERR_ALERT | ERR_FATAL;
1280 goto out;
1281 }
1282 } /* end while loop */
1283 out:
1284 return err_code;
1285}
1286
Aurelien DARRAGON39254ca2023-03-03 13:11:36 +01001287/* x-original-to: tries to parse "option originalto" config keyword
1288 * Returns a composition of ERR_NONE, ERR_FATAL, ERR_ALERT
1289 */
Aurelien DARRAGONf9583412022-12-29 15:45:41 +01001290int proxy_http_parse_xot(char **args, int cur_arg,
1291 struct proxy *curproxy, const struct proxy *defpx,
1292 const char *file, int linenum)
1293{
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001294 struct http_ext_xot *xot;
Aurelien DARRAGONf9583412022-12-29 15:45:41 +01001295 int err_code = 0;
1296
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001297 if (!http_ext_xot_prepare(curproxy))
1298 return proxy_http_parse_oom(file, linenum);
1299
1300 xot = curproxy->http_ext->xot;
1301
Aurelien DARRAGONf9583412022-12-29 15:45:41 +01001302 /* insert x-original-to field, but not for the IP address listed as an except.
1303 * set default options (ie: bitfield, header name, etc)
1304 */
1305
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001306 istfree(&xot->hdr_name);
1307 xot->hdr_name = istdup(ist(DEF_XORIGINALTO_HDR));
1308 if (!isttest(xot->hdr_name))
Aurelien DARRAGONf9583412022-12-29 15:45:41 +01001309 return proxy_http_parse_oom(file, linenum);
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001310 xot->except_net.family = AF_UNSPEC;
Aurelien DARRAGONf9583412022-12-29 15:45:41 +01001311
1312 /* loop to go through arguments - start at 2, since 0+1 = "option" "originalto" */
1313 cur_arg = 2;
1314 while (*(args[cur_arg])) {
1315 if (strcmp(args[cur_arg], "except") == 0) {
1316 unsigned char mask;
1317 int i;
1318
1319 /* suboption except - needs additional argument for it */
1320 if (*(args[cur_arg+1]) &&
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001321 str2net(args[cur_arg+1], 1, &xot->except_net.addr.v4.ip, &xot->except_net.addr.v4.mask)) {
1322 xot->except_net.family = AF_INET;
1323 xot->except_net.addr.v4.ip.s_addr &= xot->except_net.addr.v4.mask.s_addr;
Aurelien DARRAGONf9583412022-12-29 15:45:41 +01001324 }
1325 else if (*(args[cur_arg+1]) &&
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001326 str62net(args[cur_arg+1], &xot->except_net.addr.v6.ip, &mask)) {
1327 xot->except_net.family = AF_INET6;
1328 len2mask6(mask, &xot->except_net.addr.v6.mask);
Aurelien DARRAGONf9583412022-12-29 15:45:41 +01001329 for (i = 0; i < 16; i++)
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001330 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 +01001331 }
1332 else {
1333 ha_alert("parsing [%s:%d] : '%s %s %s' expects <address>[/mask] as argument.\n",
1334 file, linenum, args[0], args[1], args[cur_arg]);
1335 err_code |= ERR_ALERT | ERR_FATAL;
1336 goto out;
1337 }
1338 cur_arg += 2;
1339 } else if (strcmp(args[cur_arg], "header") == 0) {
1340 /* suboption header - needs additional argument for it */
1341 if (*(args[cur_arg+1]) == 0) {
1342 ha_alert("parsing [%s:%d] : '%s %s %s' expects <header_name> as argument.\n",
1343 file, linenum, args[0], args[1], args[cur_arg]);
1344 err_code |= ERR_ALERT | ERR_FATAL;
1345 goto out;
1346 }
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001347 istfree(&xot->hdr_name);
1348 xot->hdr_name = istdup(ist(args[cur_arg+1]));
1349 if (!isttest(xot->hdr_name))
Aurelien DARRAGONf9583412022-12-29 15:45:41 +01001350 return proxy_http_parse_oom(file, linenum);
1351 cur_arg += 2;
1352 } else {
1353 /* unknown suboption - catchall */
1354 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'except' and 'header'.\n",
1355 file, linenum, args[0], args[1]);
1356 err_code |= ERR_ALERT | ERR_FATAL;
1357 goto out;
1358 }
1359 } /* end while loop */
1360
1361 out:
1362 return err_code;
1363}
1364
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001365/*
1366 * =========== MGMT ===========
1367 * below are helpers to manage http ext options
1368 */
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001369
1370/* Ensure http_ext->fwd is properly allocated and
1371 * initialized for <curproxy>.
1372 * The function will leverage http_ext_prepare() to make
1373 * sure http_ext is properly allocated and initialized as well.
1374 * Returns 1 for success and 0 for failure (memory error)
1375 */
1376int http_ext_7239_prepare(struct proxy *curproxy)
1377{
1378 struct http_ext_7239 *fwd;
1379
1380 if (!http_ext_prepare(curproxy))
1381 return 0;
1382 if (curproxy->http_ext->fwd)
1383 return 1; /* nothing to do */
1384
1385 fwd = malloc(sizeof(*fwd));
1386 if (!fwd)
1387 return 0;
1388 /* initialize fwd mandatory fields */
1389 fwd->c_mode = 0; /* pre-compile (parse) time */
1390 fwd->c_file = NULL;
1391 fwd->p_host.expr_s = NULL;
1392 fwd->p_by.nn_expr_s = NULL;
1393 fwd->p_by.np_expr_s = NULL;
1394 fwd->p_for.nn_expr_s = NULL;
1395 fwd->p_for.np_expr_s = NULL;
1396 /* assign */
1397 curproxy->http_ext->fwd = fwd;
1398 return 1;
1399}
1400
1401/* Ensure http_ext->xff is properly allocated and
1402 * initialized for <curproxy>.
1403 * The function will leverage http_ext_prepare() to make
1404 * sure http_ext is properly allocated and initialized as well.
1405 * Returns 1 for success and 0 for failure (memory error)
1406 */
1407int http_ext_xff_prepare(struct proxy *curproxy)
1408{
1409 struct http_ext_xff *xff;
1410
1411 if (!http_ext_prepare(curproxy))
1412 return 0;
1413 if (curproxy->http_ext->xff)
1414 return 1; /* nothing to do */
1415
1416 xff = malloc(sizeof(*xff));
1417 if (!xff)
1418 return 0;
1419 /* initialize xff mandatory fields */
1420 xff->hdr_name = IST_NULL;
1421 /* assign */
1422 curproxy->http_ext->xff = xff;
1423 return 1;
1424}
1425
1426/* Ensure http_ext->xot is properly allocated and
1427 * initialized for <curproxy>.
1428 * The function will leverage http_ext_prepare() to make
1429 * sure http_ext is properly allocated and initialized as well.
1430 * Returns 1 for success and 0 for failure (memory error)
1431 */
1432int http_ext_xot_prepare(struct proxy *curproxy)
1433{
1434 struct http_ext_xot *xot;
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001435
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001436 if (!http_ext_prepare(curproxy))
1437 return 0;
1438 if (curproxy->http_ext->xot)
1439 return 1; /* nothing to do */
1440
1441 xot = malloc(sizeof(*xot));
1442 if (!xot)
1443 return 0;
1444 /* initialize xot mandatory fields */
1445 xot->hdr_name = IST_NULL;
1446 /* assign */
1447 curproxy->http_ext->xot = xot;
1448 return 1;
1449}
1450
1451/* deep clean http_ext->fwd parameter for <curproxy>
1452 * http_ext->fwd will be freed
1453 * clean behavior will differ depending on http_ext->fwd
1454 * state. If fwd is in 'parsed' state, parsing hints will be
1455 * cleaned. Else, it means fwd is in 'compiled' state, in this
1456 * case we're cleaning compiled results.
1457 * This is because parse and compile memory areas are shared in
1458 * a single union to optimize struct http_ext_7239 size.
1459 */
1460void http_ext_7239_clean(struct proxy *curproxy)
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001461{
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001462 struct http_ext_7239 *clean;
1463
1464 if (!curproxy->http_ext)
1465 return;
1466 clean = curproxy->http_ext->fwd;
1467 if (!clean)
1468 return; /* nothing to do */
Aurelien DARRAGON9ded8342023-01-06 12:16:28 +01001469 if (!clean->c_mode) {
1470 /* parsed */
1471 ha_free(&clean->c_file);
1472 ha_free(&clean->p_host.expr_s);
1473 ha_free(&clean->p_by.nn_expr_s);
1474 ha_free(&clean->p_by.np_expr_s);
1475 ha_free(&clean->p_for.nn_expr_s);
1476 ha_free(&clean->p_for.np_expr_s);
1477 }
1478 else {
1479 /* compiled */
1480 release_sample_expr(clean->p_host.expr);
1481 clean->p_host.expr = NULL;
1482 release_sample_expr(clean->p_by.nn_expr);
1483 clean->p_by.nn_expr = NULL;
1484 release_sample_expr(clean->p_by.np_expr);
1485 clean->p_by.np_expr = NULL;
1486 release_sample_expr(clean->p_for.nn_expr);
1487 clean->p_for.nn_expr = NULL;
1488 release_sample_expr(clean->p_for.np_expr);
1489 clean->p_for.np_expr = NULL;
1490 }
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001491 /* free fwd */
1492 ha_free(&curproxy->http_ext->fwd);
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001493}
1494
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001495/* deep clean http_ext->xff parameter for <curproxy>
1496 * http_ext->xff will be freed
1497 */
1498void http_ext_xff_clean(struct proxy *curproxy)
Aurelien DARRAGON730b9832022-12-28 18:53:05 +01001499{
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001500 struct http_ext_xff *clean;
1501
1502 if (!curproxy->http_ext)
1503 return;
1504 clean = curproxy->http_ext->xff;
1505 if (!clean)
1506 return; /* nothing to do */
Aurelien DARRAGON730b9832022-12-28 18:53:05 +01001507 istfree(&clean->hdr_name);
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001508 /* free xff */
1509 ha_free(&curproxy->http_ext->xff);
Aurelien DARRAGON730b9832022-12-28 18:53:05 +01001510}
1511
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001512/* deep clean http_ext->xot parameter for <curproxy>
1513 * http_ext->xot will be freed
1514 */
1515void http_ext_xot_clean(struct proxy *curproxy)
Aurelien DARRAGONf9583412022-12-29 15:45:41 +01001516{
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001517 struct http_ext_xot *clean;
1518
1519 if (!curproxy->http_ext)
1520 return;
1521 clean = curproxy->http_ext->xot;
1522 if (!clean)
1523 return; /* nothing to do */
Aurelien DARRAGONf9583412022-12-29 15:45:41 +01001524 istfree(&clean->hdr_name);
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001525 /* free xot */
1526 ha_free(&curproxy->http_ext->xot);
Aurelien DARRAGONf9583412022-12-29 15:45:41 +01001527}
1528
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001529/* duplicate http_ext->fwd parameters from <def> to <cpy>
1530 * performs the required memory allocation and initialization
1531 */
1532void http_ext_7239_dup(const struct proxy *def, struct proxy *cpy)
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001533{
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001534 struct http_ext_7239 *dest = NULL;
1535 struct http_ext_7239 *orig = NULL;
1536
1537 /* feature requires backend cap */
1538 if (!(cpy->cap & PR_CAP_BE))
1539 return;
1540
1541 if (def->http_ext == NULL || def->http_ext->fwd == NULL)
1542 return;
1543
1544 orig = def->http_ext->fwd;
1545
Aurelien DARRAGON9ded8342023-01-06 12:16:28 +01001546 if (orig->c_mode)
1547 return; /* copy not supported once compiled */
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001548
1549 if (!http_ext_7239_prepare(cpy))
1550 return;
1551
1552 dest = cpy->http_ext->fwd;
1553
Aurelien DARRAGONb2bb9252022-12-28 15:37:57 +01001554 if (orig->c_file)
1555 dest->c_file = strdup(orig->c_file);
1556 dest->c_line = orig->c_line;
1557 /* proto */
1558 dest->p_proto = orig->p_proto;
1559 /* host */
1560 dest->p_host.mode = orig->p_host.mode;
1561 if (orig->p_host.expr_s)
1562 dest->p_host.expr_s = strdup(orig->p_host.expr_s);
1563 /* by - nodename */
1564 dest->p_by.nn_mode = orig->p_by.nn_mode;
1565 if (orig->p_by.nn_expr_s)
1566 dest->p_by.nn_expr_s = strdup(orig->p_by.nn_expr_s);
1567 /* by - nodeport */
1568 dest->p_by.np_mode = orig->p_by.np_mode;
1569 if (orig->p_by.np_expr_s)
1570 dest->p_by.np_expr_s = strdup(orig->p_by.np_expr_s);
1571 /* for - nodename */
1572 dest->p_for.nn_mode = orig->p_for.nn_mode;
1573 if (orig->p_for.nn_expr_s)
1574 dest->p_for.nn_expr_s = strdup(orig->p_for.nn_expr_s);
1575 /* for - nodeport */
1576 dest->p_for.np_mode = orig->p_for.np_mode;
1577 if (orig->p_for.np_expr_s)
1578 dest->p_for.np_expr_s = strdup(orig->p_for.np_expr_s);
1579}
Aurelien DARRAGON730b9832022-12-28 18:53:05 +01001580
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001581/* duplicate http_ext->xff parameters from <def> to <cpy>
1582 * performs the required memory allocation and initialization
1583 */
1584void http_ext_xff_dup(const struct proxy *def, struct proxy *cpy)
Aurelien DARRAGON730b9832022-12-28 18:53:05 +01001585{
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001586 struct http_ext_xff *dest = NULL;
1587 struct http_ext_xff *orig = NULL;
1588
1589 if (def->http_ext == NULL || def->http_ext->xff == NULL ||
1590 !http_ext_xff_prepare(cpy))
1591 return;
1592
1593 orig = def->http_ext->xff;
1594 dest = cpy->http_ext->xff;
1595
Aurelien DARRAGON730b9832022-12-28 18:53:05 +01001596 if (isttest(orig->hdr_name))
1597 dest->hdr_name = istdup(orig->hdr_name);
1598 dest->mode = orig->mode;
1599 dest->except_net = orig->except_net;
1600}
Aurelien DARRAGONf9583412022-12-29 15:45:41 +01001601
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001602/* duplicate http_ext->xot parameters from <def> to <cpy>
1603 * performs the required memory allocation and initialization
1604 */
1605void http_ext_xot_dup(const struct proxy *def, struct proxy *cpy)
Aurelien DARRAGONf9583412022-12-29 15:45:41 +01001606{
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001607 struct http_ext_xot *dest = NULL;
1608 struct http_ext_xot *orig = NULL;
1609
1610 if (def->http_ext == NULL || def->http_ext->xot == NULL ||
1611 !http_ext_xot_prepare(cpy))
1612 return;
1613
1614 orig = def->http_ext->xot;
1615 dest = cpy->http_ext->xot;
1616
Aurelien DARRAGONf9583412022-12-29 15:45:41 +01001617 if (isttest(orig->hdr_name))
1618 dest->hdr_name = istdup(orig->hdr_name);
1619 dest->except_net = orig->except_net;
1620}
Aurelien DARRAGON82faad12022-12-29 18:32:19 +01001621
Aurelien DARRAGONb2e2ec52023-01-09 11:09:03 +01001622/* Allocate new http_ext and initialize it
1623 * if needed
1624 * Returns 1 for success and 0 for failure
1625 */
1626int http_ext_prepare(struct proxy *curproxy)
1627{
1628 if (curproxy->http_ext)
1629 return 1; /* nothing to do */
1630
1631 curproxy->http_ext = malloc(sizeof(*curproxy->http_ext));
1632 if (!curproxy->http_ext)
1633 return 0; /* failure */
1634 /* first init, set supported ext to NULL */
1635 curproxy->http_ext->fwd = NULL;
1636 curproxy->http_ext->xff = NULL;
1637 curproxy->http_ext->xot = NULL;
1638 return 1;
1639}
1640
1641/* duplicate existing http_ext from <defproxy> to <curproxy>
1642 */
1643void http_ext_dup(const struct proxy *defproxy, struct proxy *curproxy)
1644{
1645 /* copy defproxy.http_ext members */
1646 http_ext_7239_dup(defproxy, curproxy);
1647 http_ext_xff_dup(defproxy, curproxy);
1648 http_ext_xot_dup(defproxy, curproxy);
1649}
1650
1651/* deep clean http_ext for <curproxy> (if previously allocated)
1652 */
1653void http_ext_clean(struct proxy *curproxy)
1654{
1655 if (!curproxy->http_ext)
1656 return; /* nothing to do */
1657 /* first, free supported ext */
1658 http_ext_7239_clean(curproxy);
1659 http_ext_xff_clean(curproxy);
1660 http_ext_xot_clean(curproxy);
1661
1662 /* then, free http_ext */
1663 ha_free(&curproxy->http_ext);
1664}
1665
1666/* soft clean (only clean http_ext if no more options are used) */
1667void http_ext_softclean(struct proxy *curproxy)
1668{
1669 if (!curproxy->http_ext)
1670 return; /* nothing to do */
1671 if (!curproxy->http_ext->fwd &&
1672 !curproxy->http_ext->xff &&
1673 !curproxy->http_ext->xot) {
1674 /* no more use for http_ext, all options are disabled */
1675 http_ext_clean(curproxy);
1676 }
1677}
1678
Ilya Shipitsin07be66d2023-04-01 12:26:42 +02001679/* Perform some consistency checks on px.http_ext after parsing
Aurelien DARRAGONc07001b2023-01-24 17:55:09 +01001680 * is completed.
1681 * We make sure to perform a softclean in case some options were
1682 * to be disabled in this check. This way we can release some memory.
1683 * Returns a composition of ERR_NONE, ERR_ALERT, ERR_FATAL, ERR_WARN
1684 */
1685static int check_http_ext_postconf(struct proxy *px) {
1686 int err = ERR_NONE;
1687
1688 if (px->http_ext) {
1689 /* consistency check for http_ext */
1690 if (px->mode != PR_MODE_HTTP && !(px->options & PR_O_HTTP_UPG)) {
1691 /* http is disabled on px, yet it is required by http_ext */
1692 if (px->http_ext->fwd) {
1693 ha_warning("'option %s' ignored for %s '%s' as it requires HTTP mode.\n",
1694 "forwarded", proxy_type_str(px), px->id);
1695 err |= ERR_WARN;
1696 http_ext_7239_clean(px);
1697 }
1698 if (px->http_ext->xff) {
1699 ha_warning("'option %s' ignored for %s '%s' as it requires HTTP mode.\n",
1700 "forwardfor", proxy_type_str(px), px->id);
1701 err |= ERR_WARN;
1702 http_ext_xff_clean(px);
1703 }
1704 if (px->http_ext->xot) {
1705 ha_warning("'option %s' ignored for %s '%s' as it requires HTTP mode.\n",
1706 "originalto", proxy_type_str(px), px->id);
1707 err |= ERR_WARN;
1708 http_ext_xot_clean(px);
1709 }
1710 } else if (px->http_ext->fwd) {
1711 /* option "forwarded" may need to compile its expressions */
1712 err |= proxy_http_compile_7239(px);
1713 }
1714 /* http_ext post init early cleanup */
1715 http_ext_softclean(px);
1716
1717 }
1718 return err;
1719}
1720
1721REGISTER_POST_PROXY_CHECK(check_http_ext_postconf);
Aurelien DARRAGON82faad12022-12-29 18:32:19 +01001722/*
1723 * =========== CONV ===========
1724 * related converters
1725 */
1726
Aurelien DARRAGON5c6f86f2022-12-30 16:23:08 +01001727/* input: string representing 7239 forwarded header single value
1728 * does not take arguments
1729 * output: 1 if header is RFC compliant, 0 otherwise
1730 */
1731static int sample_conv_7239_valid(const struct arg *args, struct sample *smp, void *private)
1732{
1733 struct ist input = ist2(smp->data.u.str.area, smp->data.u.str.data);
1734
1735 smp->data.type = SMP_T_BOOL;
1736 smp->data.u.sint = !!http_validate_7239_header(input, FORWARDED_HEADER_ALL, NULL);
1737 return 1;
1738}
1739
Aurelien DARRAGON6fb58b82022-12-30 16:37:03 +01001740/* input: string representing 7239 forwarded header single value
1741 * argument: parameter name to look for in the header
1742 * output: header parameter raw value, as a string
1743 */
1744static int sample_conv_7239_field(const struct arg *args, struct sample *smp, void *private)
1745{
1746 struct ist input = ist2(smp->data.u.str.area, smp->data.u.str.data);
1747 struct buffer *output;
1748 struct forwarded_header_ctx ctx;
1749 int validate;
1750 int field = 0;
1751
1752 if (strcmp(args->data.str.area, "proto") == 0)
1753 field = FORWARDED_HEADER_PROTO;
1754 else if (strcmp(args->data.str.area, "host") == 0)
1755 field = FORWARDED_HEADER_HOST;
1756 else if (strcmp(args->data.str.area, "for") == 0)
1757 field = FORWARDED_HEADER_FOR;
1758 else if (strcmp(args->data.str.area, "by") == 0)
1759 field = FORWARDED_HEADER_BY;
1760
1761 validate = http_validate_7239_header(input, FORWARDED_HEADER_ALL, &ctx);
1762 if (!(validate & field))
1763 return 0; /* invalid header or header does not contain field */
1764 output = get_trash_chunk();
1765 switch (field) {
1766 case FORWARDED_HEADER_PROTO:
1767 if (ctx.proto == FORWARDED_HEADER_HTTP)
1768 chunk_appendf(output, "http");
1769 else if (ctx.proto == FORWARDED_HEADER_HTTPS)
1770 chunk_appendf(output, "https");
1771 break;
1772 case FORWARDED_HEADER_HOST:
1773 chunk_istcat(output, ctx.host);
1774 break;
1775 case FORWARDED_HEADER_FOR:
1776 chunk_istcat(output, ctx.nfor.raw);
1777 break;
1778 case FORWARDED_HEADER_BY:
1779 chunk_istcat(output, ctx.nby.raw);
1780 break;
1781 default:
1782 break;
1783 }
1784 smp->flags &= ~SMP_F_CONST;
1785 smp->data.type = SMP_T_STR;
1786 smp->data.u.str = *output;
1787 return 1;
1788}
1789
Aurelien DARRAGON07d67532022-12-30 16:45:42 +01001790/* input: substring representing 7239 forwarded header node
1791 * output: forwarded header nodename translated to either
1792 * ipv4 address, ipv6 address or str
1793 * ('_' prefix if obfuscated, or "unknown" if unknown)
1794 */
1795static int sample_conv_7239_n2nn(const struct arg *args, struct sample *smp, void *private)
1796{
1797 struct ist input = ist2(smp->data.u.str.area, smp->data.u.str.data);
1798 struct forwarded_header_node ctx;
1799 struct buffer *output;
1800
1801 if (http_7239_extract_node(&input, &ctx, 1) == 0)
1802 return 0; /* could not extract node */
1803 switch (ctx.nodename.type) {
1804 case FORWARDED_HEADER_UNK:
1805 output = get_trash_chunk();
1806 chunk_appendf(output, "unknown");
1807 smp->flags &= ~SMP_F_CONST;
1808 smp->data.type = SMP_T_STR;
1809 smp->data.u.str = *output;
1810 break;
1811 case FORWARDED_HEADER_OBFS:
1812 output = get_trash_chunk();
1813 chunk_appendf(output, "_"); /* append obfs prefix */
1814 chunk_istcat(output, ctx.nodename.obfs);
1815 smp->flags &= ~SMP_F_CONST;
1816 smp->data.type = SMP_T_STR;
1817 smp->data.u.str = *output;
1818 break;
1819 case FORWARDED_HEADER_IP:
1820 if (ctx.nodename.ip.ss_family == AF_INET) {
1821 smp->data.type = SMP_T_IPV4;
1822 smp->data.u.ipv4 = ((struct sockaddr_in *)&ctx.nodename.ip)->sin_addr;
1823 }
1824 else if (ctx.nodename.ip.ss_family == AF_INET6) {
1825 smp->data.type = SMP_T_IPV6;
1826 smp->data.u.ipv6 = ((struct sockaddr_in6 *)&ctx.nodename.ip)->sin6_addr;
1827 }
1828 else
1829 return 0; /* unsupported */
1830 break;
1831 default:
1832 return 0; /* unsupported */
1833 }
1834 return 1;
1835}
1836
Aurelien DARRAGON9a273b42022-12-30 16:56:08 +01001837/* input: substring representing 7239 forwarded header node
1838 * output: forwarded header nodeport translated to either
1839 * integer or str for obfuscated ('_' prefix)
1840 */
1841static int sample_conv_7239_n2np(const struct arg *args, struct sample *smp, void *private)
1842{
1843 struct ist input = ist2(smp->data.u.str.area, smp->data.u.str.data);
1844 struct forwarded_header_node ctx;
1845 struct buffer *output;
1846
1847 if (http_7239_extract_node(&input, &ctx, 1) == 0)
1848 return 0; /* could not extract node */
1849
1850 switch (ctx.nodeport.type) {
1851 case FORWARDED_HEADER_UNK:
1852 return 0; /* not provided */
1853 case FORWARDED_HEADER_OBFS:
1854 output = get_trash_chunk();
1855 chunk_appendf(output, "_"); /* append obfs prefix */
1856 chunk_istcat(output, ctx.nodeport.obfs);
1857 smp->flags &= ~SMP_F_CONST;
1858 smp->data.type = SMP_T_STR;
1859 smp->data.u.str = *output;
1860 break;
1861 case FORWARDED_HEADER_PORT:
1862 smp->data.type = SMP_T_SINT;
1863 smp->data.u.sint = ctx.nodeport.port;
1864 break;
1865 default:
1866 return 0; /* unsupported */
1867 }
1868
1869 return 1;
1870}
1871
Aurelien DARRAGON82faad12022-12-29 18:32:19 +01001872/* Note: must not be declared <const> as its list will be overwritten */
1873static struct sample_conv_kw_list sample_conv_kws = {ILH, {
Aurelien DARRAGON5c6f86f2022-12-30 16:23:08 +01001874 { "rfc7239_is_valid", sample_conv_7239_valid, 0, NULL, SMP_T_STR, SMP_T_BOOL},
Aurelien DARRAGON6fb58b82022-12-30 16:37:03 +01001875 { "rfc7239_field", sample_conv_7239_field, ARG1(1,STR), NULL, SMP_T_STR, SMP_T_STR},
Aurelien DARRAGON07d67532022-12-30 16:45:42 +01001876 { "rfc7239_n2nn", sample_conv_7239_n2nn, 0, NULL, SMP_T_STR, SMP_T_ANY},
Aurelien DARRAGON9a273b42022-12-30 16:56:08 +01001877 { "rfc7239_n2np", sample_conv_7239_n2np, 0, NULL, SMP_T_STR, SMP_T_ANY},
Aurelien DARRAGON82faad12022-12-29 18:32:19 +01001878 { NULL, NULL, 0, 0, 0 },
1879}};
1880
1881INITCALL1(STG_REGISTER, sample_register_convs, &sample_conv_kws);