]> git.ipfire.org Git - thirdparty/haproxy.git/blob
fcb5a07bc
[thirdparty/haproxy.git] /
1 /*
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>
24 #include <haproxy/tools.h>
25
26 /* check if char is a valid obfuscated identifier char
27 * (according to 7239 RFC)
28 * Returns non zero value for valid char
29 */
30 static int http_7239_valid_obfsc(char c)
31 {
32 return (isalnum((unsigned char)c) ||
33 (c == '.' || c == '-' || c == '_'));
34 }
35
36 /*
37 * =========== ANALYZE ===========
38 * below are http process/ana helpers
39 */
40
41 /* checks if <input> contains rfc7239 compliant port
42 * Returns 1 for success and 0 for failure
43 * if <port> is not NULL, it will be set to the extracted value contained
44 * in <input>
45 * <input> will be consumed accordingly (parsed/extracted characters are
46 * removed from <input>)
47 */
48 static inline int http_7239_extract_port(struct ist *input, uint16_t *port)
49 {
50 char *start = istptr(*input);
51 uint32_t port_cast = 0;
52 int it = 0;
53
54 /* strtol does not support non-null terminated str,
55 * we extract port ourselves
56 */
57 while (it < istlen(*input) &&
58 isdigit((unsigned char)start[it])) {
59 port_cast = (port_cast * 10) + (start[it] - '0');
60 if (port_cast > 65535)
61 return 0; /* invalid port */
62 it += 1;
63 }
64 if (!port_cast)
65 return 0; /* invalid port */
66 /* ok */
67 if (port)
68 *port = (uint16_t)port_cast;
69 *input = istadv(*input, it);
70 return 1;
71 }
72
73 /* 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 */
80 static 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 */
105 static 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 */
137 static 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
169 * differ wheteher the input is quoted or not according to the rfc.
170 * 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 */
176 static 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
216 * differ wheteher the input is quoted or not according to the rfc.
217 * 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 */
223 static 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
282 * differ wheteher the input is quoted or not according to the rfc.
283 * 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 */
289 static 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
320 * differ wheteher the input is quoted or not according to the rfc.
321 * 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 */
327 static 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
356 static 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
361 static 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 */
381 int 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
486 static inline void http_build_7239_header_nodename(struct buffer *out,
487 struct stream *s, struct proxy *curproxy,
488 const struct sockaddr_storage *addr,
489 struct http_ext_7239_forby *forby)
490 {
491 struct in6_addr *ip6_addr;
492
493 if (forby->nn_mode == HTTP_7239_FORBY_ORIG) {
494 if (addr && addr->ss_family == AF_INET) {
495 unsigned char *pn = (unsigned char *)&((struct sockaddr_in *)addr)->sin_addr;
496
497 chunk_appendf(out, "%d.%d.%d.%d", pn[0], pn[1], pn[2], pn[3]);
498 }
499 else if (addr && addr->ss_family == AF_INET6) {
500 ip6_addr = &((struct sockaddr_in6 *)addr)->sin6_addr;
501 print_ip6:
502 {
503 char pn[INET6_ADDRSTRLEN];
504
505 inet_ntop(AF_INET6,
506 ip6_addr,
507 pn, sizeof(pn));
508 if (!forby->np_mode)
509 chunk_appendf(out, "\""); /* explicit quoting required for ipv6 */
510 chunk_appendf(out, "[%s]", pn);
511 }
512 }
513 /* else: not supported */
514 }
515 else if (forby->nn_mode == HTTP_7239_FORBY_SMP && forby->nn_expr) {
516 struct sample *smp;
517
518 smp = sample_process(curproxy, s->sess, s,
519 SMP_OPT_DIR_REQ | SMP_OPT_FINAL, forby->nn_expr, NULL);
520
521 if (smp) {
522 if (smp->data.type == SMP_T_IPV6) {
523 /* smp is valid IP6, print with RFC compliant output */
524 ip6_addr = &smp->data.u.ipv6;
525 goto print_ip6;
526 }
527 if (sample_casts[smp->data.type][SMP_T_STR] &&
528 sample_casts[smp->data.type][SMP_T_STR](smp)) {
529 struct ist validate_n = ist2(smp->data.u.str.area, smp->data.u.str.data);
530 struct ist validate_o = ist2(smp->data.u.str.area, smp->data.u.str.data);
531 struct forwarded_header_nodename nodename;
532
533 /* validate nodename */
534 if (http_7239_extract_nodename(&validate_n, &nodename, 1) &&
535 !istlen(validate_n)) {
536 if (nodename.type == FORWARDED_HEADER_IP &&
537 nodename.ip.ss_family == AF_INET6) {
538 /* special care needed for valid ip6 nodename (quoting) */
539 ip6_addr = &((struct sockaddr_in6 *)&nodename.ip)->sin6_addr;
540 goto print_ip6;
541 }
542 /* no special care needed, input is already rfc compliant,
543 * just print as regular non quoted string
544 */
545 chunk_cat(out, &smp->data.u.str);
546 }
547 else if (http_7239_extract_obfs(&validate_o, NULL) &&
548 !istlen(validate_o)) {
549 /* raw user input that should be printed as 7239 obfs */
550 chunk_appendf(out, "_%.*s", (int)smp->data.u.str.data, smp->data.u.str.area);
551 }
552 /* else: not compliant */
553 }
554 /* else: cannot be casted to str */
555 }
556 /* else: smp error */
557 }
558 }
559
560 static inline void http_build_7239_header_nodeport(struct buffer *out,
561 struct stream *s, struct proxy *curproxy,
562 const struct sockaddr_storage *addr,
563 struct http_ext_7239_forby *forby)
564 {
565 if (forby->np_mode == HTTP_7239_FORBY_ORIG) {
566 if (addr && addr->ss_family == AF_INET)
567 chunk_appendf(out, "%d", ntohs(((struct sockaddr_in *)addr)->sin_port));
568 else if (addr && addr->ss_family == AF_INET6)
569 chunk_appendf(out, "%d", ntohs(((struct sockaddr_in6 *)addr)->sin6_port));
570 /* else: not supported */
571 }
572 else if (forby->np_mode == HTTP_7239_FORBY_SMP && forby->np_expr) {
573 struct sample *smp;
574
575 smp = sample_fetch_as_type(curproxy, s->sess, s,
576 SMP_OPT_DIR_REQ | SMP_OPT_FINAL, forby->np_expr, SMP_T_STR);
577 if (smp) {
578 struct ist validate_n = ist2(smp->data.u.str.area, smp->data.u.str.data);
579 struct ist validate_o = ist2(smp->data.u.str.area, smp->data.u.str.data);
580
581 /* validate nodeport */
582 if (http_7239_extract_nodeport(&validate_n, NULL) &&
583 !istlen(validate_n)) {
584 /* no special care needed, input is already rfc compliant,
585 * just print as regular non quoted string
586 */
587 chunk_cat(out, &smp->data.u.str);
588 }
589 else if (http_7239_extract_obfs(&validate_o, NULL) &&
590 !istlen(validate_o)) {
591 /* raw user input that should be printed as 7239 obfs */
592 chunk_appendf(out, "_%.*s", (int)smp->data.u.str.data, smp->data.u.str.area);
593 }
594 /* else: not compliant */
595 }
596 /* else: smp error */
597 }
598 }
599
600 static inline void http_build_7239_header_node(struct buffer *out,
601 struct stream *s, struct proxy *curproxy,
602 const struct sockaddr_storage *addr,
603 struct http_ext_7239_forby *forby)
604 {
605 size_t offset_start;
606 size_t offset_save;
607
608 offset_start = out->data;
609 if (forby->np_mode)
610 chunk_appendf(out, "\"");
611 offset_save = out->data;
612 http_build_7239_header_node(out, s, curproxy, addr, &curproxy->http.fwd.p_by);
613 if (offset_save == out->data) {
614 /* could not build nodename, either because some
615 * data is not available or user is providing bad input
616 */
617 chunk_appendf(out, "unknown");
618 }
619 if (forby->np_mode) {
620 chunk_appendf(out, ":");
621 offset_save = out->data;
622 http_build_7239_header_nodeport(out, s, curproxy, addr, &curproxy->http.fwd.p_by);
623 if (offset_save == out->data) {
624 /* could not build nodeport, either because some data is
625 * not available or user is providing bad input
626 */
627 out->data = offset_save - 1;
628 }
629 }
630 if (out->data != offset_start && out->area[offset_start] == '"')
631 chunk_appendf(out, "\""); /* add matching end quote */
632 }
633
634 static inline void http_build_7239_header_host(struct buffer *out,
635 struct stream *s, struct proxy *curproxy,
636 struct htx *htx, struct http_ext_7239_host *host)
637 {
638 struct http_hdr_ctx ctx = { .blk = NULL };
639 char *str = NULL;
640 int str_len = 0;
641
642 if (host->mode == HTTP_7239_HOST_ORIG &&
643 http_find_header(htx, ist("host"), &ctx, 0)) {
644 str = ctx.value.ptr;
645 str_len = ctx.value.len;
646 print_host:
647 {
648 struct ist validate = ist2(str, str_len);
649 /* host check, to ensure rfc compliant output
650 * (assumming host is quoted/escaped)
651 */
652 if (http_7239_extract_host(&validate, NULL, 1) && !istlen(validate))
653 chunk_memcat(out, str, str_len);
654 /* else: not compliant or partially compliant */
655 }
656
657 }
658 else if (host->mode == HTTP_7239_HOST_SMP && host->expr) {
659 struct sample *smp;
660
661 smp = sample_fetch_as_type(curproxy, s->sess, s,
662 SMP_OPT_DIR_REQ | SMP_OPT_FINAL, host->expr, SMP_T_STR);
663 if (smp) {
664 str = smp->data.u.str.area;
665 str_len = smp->data.u.str.data;
666 goto print_host;
667 }
668 /* else: smp error */
669 }
670 }
671
672 /* Tries build 7239 header according to <curproxy> parameters and <s> context
673 * It both depends on <curproxy>->http_ext->fwd for config and <s> for request
674 * context data.
675 * The function will write output to <out> buffer
676 * Returns 1 for success and 0 for error (ie: not enough space in buffer)
677 */
678 static int http_build_7239_header(struct buffer *out,
679 struct stream *s, struct proxy *curproxy, struct htx *htx)
680 {
681 struct connection *cli_conn = objt_conn(strm_sess(s)->origin);
682
683 if (curproxy->http.fwd.p_proto) {
684 chunk_appendf(out, "%sproto=%s", ((out->data) ? ";" : ""),
685 ((conn_is_ssl(cli_conn)) ? "https" : "http"));
686 }
687 if (curproxy->http.fwd.p_host.mode) {
688 /* always add quotes for host parameter to make output compliancy checks simpler */
689 chunk_appendf(out, "%shost=\"", ((out->data) ? ";" : ""));
690 /* ignore return value for now, but could be useful some day */
691 http_build_7239_header_host(out, s, curproxy, htx,
692 &curproxy->http.fwd.p_host);
693 chunk_appendf(out, "\"");
694 }
695
696 if (curproxy->http.fwd.p_by.nn_mode) {
697 const struct sockaddr_storage *dst = sc_dst(s->scf);
698
699 chunk_appendf(out, "%sby=", ((out->data) ? ";" : ""));
700 http_build_7239_header_node(out, s, curproxy, dst, &curproxy->http.fwd.p_by);
701 }
702
703 if (curproxy->http.fwd.p_for.nn_mode) {
704 const struct sockaddr_storage *src = sc_src(s->scf);
705
706 chunk_appendf(out, "%sfor=", ((out->data) ? ";" : ""));
707 http_build_7239_header_node(out, s, curproxy, src, &curproxy->http.fwd.p_for);
708 }
709 if (unlikely(out->data == out->size)) {
710 /* not enough space in buffer, error */
711 return 0;
712 }
713 return 1;
714 }
715
716 /* This function will try to inject 7239 forwarded header
717 * Returns 1 for success and 0 for failure
718 */
719 int http_handle_7239_header(struct stream *s, struct channel *req)
720 {
721 struct htx *htx = htxbuf(&req->buf);
722 struct proxy *curproxy = s->be; /* ignore frontend */
723 int validate = 1;
724 struct http_hdr_ctx find = { .blk = NULL };
725 struct http_hdr_ctx last = { .blk = NULL};
726 struct ist hdr = ist("forwarded");
727
728 BUG_ON(!(curproxy->options & PR_O_HTTP_7239)); /* should not happen */
729
730 /* ok, let's build forwarded header */
731 chunk_reset(&trash);
732 if (unlikely(!http_build_7239_header(&trash, s, curproxy, htx)))
733 return 0; /* error when building header (bad user conf or memory error) */
734
735 /* validate existing forwarded header (including multiple values),
736 * hard stop if error is encountered
737 */
738 while (http_find_header(htx, hdr, &find, 0)) {
739 /* validate current header chunk */
740 if (!http_validate_7239_header(find.value, FORWARDED_HEADER_ALL, NULL)) {
741 /* at least one error, existing forwarded header not OK, add our own
742 * forwarded header, so that it can be trusted
743 */
744 validate = 0;
745 break;
746 }
747 last = find;
748 }
749 /* no errors, append our data at the end of existing header */
750 if (last.blk && validate) {
751 if (unlikely(!http_append_header_value(htx, &last, ist2(trash.area, trash.data))))
752 return 0; /* htx error */
753 }
754 else {
755 if (unlikely(!http_add_header(htx, hdr, ist2(trash.area, trash.data))))
756 return 0; /* htx error */
757 }
758 return 1;
759 }
760
761 /*
762 * =========== CONFIG ===========
763 * below are helpers to parse http ext options from the config
764 */
765 static int proxy_http_parse_oom(const char *file, int linenum)
766 {
767 int err_code = 0;
768
769 ha_alert("parsing [%s:%d]: out of memory.\n", file, linenum);
770 err_code |= ERR_ALERT | ERR_ABORT;
771 return err_code;
772 }
773
774 static inline int _proxy_http_parse_7239_expr(char **args, int *cur_arg,
775 const char *file, int linenum,
776 char **expr_s)
777 {
778 int err_code = 0;
779
780 if (!*args[*cur_arg + 1]) {
781 ha_alert("parsing [%s:%d]: '%s' expects <expr> as argument.\n",
782 file, linenum, args[*cur_arg]);
783 err_code |= ERR_ALERT | ERR_FATAL;
784 goto out;
785 }
786 *cur_arg += 1;
787 ha_free(expr_s);
788 *expr_s = strdup(args[*cur_arg]);
789 if (!*expr_s)
790 return proxy_http_parse_oom(file, linenum);
791 *cur_arg += 1;
792 out:
793 return err_code;
794 }
795
796 int proxy_http_parse_7239(char **args, int cur_arg,
797 struct proxy *curproxy, const struct proxy *defpx,
798 const char *file, int linenum)
799 {
800 int err_code = 0;
801
802 if (warnifnotcap(curproxy, PR_CAP_BE, file, linenum, "option forwarded", NULL)) {
803 /* option is ignored for frontends */
804 err_code |= ERR_WARN;
805 goto out;
806 }
807
808 curproxy->options |= PR_O_HTTP_7239;
809 curproxy->http.fwd.p_proto = 0;
810 curproxy->http.fwd.p_host.mode = 0;
811 curproxy->http.fwd.p_for.nn_mode = 0;
812 curproxy->http.fwd.p_for.np_mode = 0;
813 curproxy->http.fwd.p_by.nn_mode = 0;
814 curproxy->http.fwd.p_by.np_mode = 0;
815 ha_free(&curproxy->http.fwd.c_file);
816 curproxy->http.fwd.c_file = strdup(file);
817 curproxy->http.fwd.c_line = linenum;
818
819 /* start at 2, since 0+1 = "option" "forwarded" */
820 cur_arg = 2;
821 if (!*(args[cur_arg])) {
822 /* no optional argument provided, use default settings */
823 curproxy->http.fwd.p_for.nn_mode = HTTP_7239_FORBY_ORIG; /* enable for and mimic xff */
824 curproxy->http.fwd.p_proto = 1; /* enable proto */
825 goto out;
826 }
827 /* loop to go through optional arguments */
828 while (*(args[cur_arg])) {
829 if (strcmp(args[cur_arg], "proto") == 0) {
830 curproxy->http.fwd.p_proto = 1;
831 cur_arg += 1;
832 } else if (strcmp(args[cur_arg], "host") == 0) {
833 curproxy->http.fwd.p_host.mode = HTTP_7239_HOST_ORIG;
834 cur_arg += 1;
835 } else if (strcmp(args[cur_arg], "host-expr") == 0) {
836 curproxy->http.fwd.p_host.mode = HTTP_7239_HOST_SMP;
837 err_code |= _proxy_http_parse_7239_expr(args, &cur_arg, file, linenum,
838 &curproxy->http.fwd.p_host.expr_s);
839 if (err_code & ERR_FATAL)
840 goto out;
841 } else if (strcmp(args[cur_arg], "by") == 0) {
842 curproxy->http.fwd.p_by.nn_mode = HTTP_7239_FORBY_ORIG;
843 cur_arg += 1;
844 } else if (strcmp(args[cur_arg], "by-expr") == 0) {
845 curproxy->http.fwd.p_by.nn_mode = HTTP_7239_FORBY_SMP;
846 err_code |= _proxy_http_parse_7239_expr(args, &cur_arg, file, linenum,
847 &curproxy->http.fwd.p_by.nn_expr_s);
848 if (err_code & ERR_FATAL)
849 goto out;
850 } else if (strcmp(args[cur_arg], "for") == 0) {
851 curproxy->http.fwd.p_for.nn_mode = HTTP_7239_FORBY_ORIG;
852 cur_arg += 1;
853 } else if (strcmp(args[cur_arg], "for-expr") == 0) {
854 curproxy->http.fwd.p_for.nn_mode = HTTP_7239_FORBY_SMP;
855 err_code |= _proxy_http_parse_7239_expr(args, &cur_arg, file, linenum,
856 &curproxy->http.fwd.p_for.nn_expr_s);
857 if (err_code & ERR_FATAL)
858 goto out;
859 } else if (strcmp(args[cur_arg], "by_port") == 0) {
860 curproxy->http.fwd.p_by.np_mode = HTTP_7239_FORBY_ORIG;
861 cur_arg += 1;
862 } else if (strcmp(args[cur_arg], "by_port-expr") == 0) {
863 curproxy->http.fwd.p_by.np_mode = HTTP_7239_FORBY_SMP;
864 err_code |= _proxy_http_parse_7239_expr(args, &cur_arg, file, linenum,
865 &curproxy->http.fwd.p_by.np_expr_s);
866 if (err_code & ERR_FATAL)
867 goto out;
868 } else if (strcmp(args[cur_arg], "for_port") == 0) {
869 curproxy->http.fwd.p_for.np_mode = HTTP_7239_FORBY_ORIG;
870 cur_arg += 1;
871 } else if (strcmp(args[cur_arg], "for_port-expr") == 0) {
872 curproxy->http.fwd.p_for.np_mode = HTTP_7239_FORBY_SMP;
873 err_code |= _proxy_http_parse_7239_expr(args, &cur_arg, file, linenum,
874 &curproxy->http.fwd.p_for.np_expr_s);
875 if (err_code & ERR_FATAL)
876 goto out;
877 } else {
878 /* unknown suboption - catchall */
879 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'proto', 'host', "
880 "'host-expr', 'by', 'by-expr', 'by_port', 'by_port-expr', "
881 "'for', 'for-expr', 'for_port' and 'for_port-expr'.\n",
882 file, linenum, args[0], args[1]);
883 err_code |= ERR_ALERT | ERR_FATAL;
884 goto out;
885 }
886 } /* end while loop */
887
888 /* consistency check */
889 if (curproxy->http.fwd.p_by.np_mode &&
890 !curproxy->http.fwd.p_by.nn_mode) {
891 curproxy->http.fwd.p_by.np_mode = 0;
892 ha_free(&curproxy->http.fwd.p_by.np_expr_s);
893 ha_warning("parsing [%s:%d] : '%s %s' : '%s' will be ignored because both 'by' "
894 "and 'by-expr' are unset\n",
895 file, linenum, args[0], args[1],
896 ((curproxy->http.fwd.p_by.np_mode == HTTP_7239_FORBY_ORIG) ? "by_port" : "by_port-expr"));
897 err_code |= ERR_WARN;
898 }
899 if (curproxy->http.fwd.p_for.np_mode &&
900 !curproxy->http.fwd.p_for.nn_mode) {
901 curproxy->http.fwd.p_for.np_mode = 0;
902 ha_free(&curproxy->http.fwd.p_for.np_expr_s);
903 ha_warning("parsing [%s:%d] : '%s %s' : '%s' will be ignored because both 'for' "
904 "and 'for-expr' are unset\n",
905 file, linenum, args[0], args[1],
906 ((curproxy->http.fwd.p_for.np_mode == HTTP_7239_FORBY_ORIG) ? "for_port" : "for_port-expr"));
907 err_code |= ERR_WARN;
908 }
909
910 out:
911 return err_code;
912 }
913
914 /* returns 0 for success and positive value
915 * (equals to number of errors) in case of error
916 */
917 int proxy_http_compile_7239(struct proxy *curproxy)
918 {
919 char *err = NULL;
920 int cfgerr = 0;
921 int loop;
922
923 BUG_ON(!(curproxy->options & PR_O_HTTP_7239)); /* should not happen */
924 if (!(curproxy->cap & PR_CAP_BE)) {
925 /* no backend cap: not supported (ie: frontend)
926 * Moreover, 7239 settings are only inherited from default
927 * if proxy is backend capable.. going further would result in
928 * undefined behavior */
929 goto out;
930 }
931
932 curproxy->conf.args.ctx = ARGC_OPT; /* option */
933 curproxy->conf.args.file = curproxy->http.fwd.c_file;
934 curproxy->conf.args.line = curproxy->http.fwd.c_line;
935
936 for (loop = 0; loop < 5; loop++) {
937 char *expr_str = NULL;
938 struct sample_expr **expr = NULL;
939 int smp = 0;
940 int idx = 0;
941
942 switch (loop) {
943 case 0:
944 /* host */
945 expr_str = curproxy->http.fwd.p_host.expr_s;
946 expr = &curproxy->http.fwd.p_host.expr;
947 smp = (curproxy->http.fwd.p_host.mode ==
948 HTTP_7239_HOST_SMP);
949 break;
950 case 1:
951 /* by->node */
952 expr_str = curproxy->http.fwd.p_by.nn_expr_s;
953 expr = &curproxy->http.fwd.p_by.nn_expr;
954 smp = (curproxy->http.fwd.p_by.nn_mode ==
955 HTTP_7239_FORBY_SMP);
956 break;
957 case 2:
958 /* by->nodeport */
959 expr_str = curproxy->http.fwd.p_by.np_expr_s;
960 expr = &curproxy->http.fwd.p_by.np_expr;
961 smp = (curproxy->http.fwd.p_by.np_mode ==
962 HTTP_7239_FORBY_SMP);
963 break;
964 case 3:
965 /* for->node */
966 expr_str = curproxy->http.fwd.p_for.nn_expr_s;
967 expr = &curproxy->http.fwd.p_for.nn_expr;
968 smp = (curproxy->http.fwd.p_for.nn_mode ==
969 HTTP_7239_FORBY_SMP);
970 break;
971 case 4:
972 /* for->nodeport */
973 expr_str = curproxy->http.fwd.p_for.np_expr_s;
974 expr = &curproxy->http.fwd.p_for.np_expr;
975 smp = (curproxy->http.fwd.p_for.np_mode ==
976 HTTP_7239_FORBY_SMP);
977 break;
978 }
979 if (!smp || !expr_str)
980 continue; /* no expr */
981 /* expr cannot be NULL past this point */
982 ALREADY_CHECKED(expr);
983
984 *expr =
985 sample_parse_expr((char*[]){expr_str, NULL}, &idx,
986 curproxy->http.fwd.c_file,
987 curproxy->http.fwd.c_line,
988 &err, &curproxy->conf.args, NULL);
989
990 if (!*expr) {
991 ha_alert("%s '%s' [%s:%d]: failed to parse 'option forwarded' expression '%s' in : %s.\n",
992 proxy_type_str(curproxy), curproxy->id,
993 curproxy->http.fwd.c_file, curproxy->http.fwd.c_line,
994 expr_str, err);
995 ha_free(&err);
996 cfgerr++;
997 }
998 }
999 curproxy->conf.args.file = NULL;
1000 curproxy->conf.args.line = 0;
1001
1002 out:
1003 return cfgerr;
1004 }
1005
1006 /*
1007 * =========== MGMT ===========
1008 * below are helpers to manage http ext options
1009 */
1010
1011 void http_ext_7239_clean(struct http_ext_7239 *clean)
1012 {
1013 ha_free(&clean->c_file);
1014 ha_free(&clean->p_host.expr_s);
1015 ha_free(&clean->p_by.nn_expr_s);
1016 ha_free(&clean->p_by.np_expr_s);
1017 ha_free(&clean->p_for.nn_expr_s);
1018 ha_free(&clean->p_for.np_expr_s);
1019
1020 release_sample_expr(clean->p_host.expr);
1021 clean->p_host.expr = NULL;
1022 release_sample_expr(clean->p_by.nn_expr);
1023 clean->p_by.nn_expr = NULL;
1024 release_sample_expr(clean->p_by.np_expr);
1025 clean->p_by.np_expr = NULL;
1026 release_sample_expr(clean->p_for.nn_expr);
1027 clean->p_for.nn_expr = NULL;
1028 release_sample_expr(clean->p_for.np_expr);
1029 clean->p_for.np_expr = NULL;
1030 }
1031
1032 void http_ext_7239_copy(struct http_ext_7239 *dest, const struct http_ext_7239 *orig)
1033 {
1034 if (orig->c_file)
1035 dest->c_file = strdup(orig->c_file);
1036 dest->c_line = orig->c_line;
1037 /* proto */
1038 dest->p_proto = orig->p_proto;
1039 /* host */
1040 dest->p_host.mode = orig->p_host.mode;
1041 if (orig->p_host.expr_s)
1042 dest->p_host.expr_s = strdup(orig->p_host.expr_s);
1043 /* by - nodename */
1044 dest->p_by.nn_mode = orig->p_by.nn_mode;
1045 if (orig->p_by.nn_expr_s)
1046 dest->p_by.nn_expr_s = strdup(orig->p_by.nn_expr_s);
1047 /* by - nodeport */
1048 dest->p_by.np_mode = orig->p_by.np_mode;
1049 if (orig->p_by.np_expr_s)
1050 dest->p_by.np_expr_s = strdup(orig->p_by.np_expr_s);
1051 /* for - nodename */
1052 dest->p_for.nn_mode = orig->p_for.nn_mode;
1053 if (orig->p_for.nn_expr_s)
1054 dest->p_for.nn_expr_s = strdup(orig->p_for.nn_expr_s);
1055 /* for - nodeport */
1056 dest->p_for.np_mode = orig->p_for.np_mode;
1057 if (orig->p_for.np_expr_s)
1058 dest->p_for.np_expr_s = strdup(orig->p_for.np_expr_s);
1059 }