fcb5a07bc
[haproxy-3.0.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 }