Coverage Report

Created: 2025-10-10 07:08

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/haproxy/src/http_ext.c
Line
Count
Source
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 modify
7
 * it under the terms of the GNU General Public License as published by
8
 * the Free Software Foundation; either version 2 of the License, or
9
 * (at your option) any later version.
10
 *
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU General Public License
17
 * along with this program; if not, write to the Free Software
18
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
19
 */
20
21
#include <haproxy/sample.h>
22
#include <haproxy/http_htx.h>
23
#include <haproxy/http_ext.h>
24
#include <haproxy/chunk.h>
25
#include <haproxy/stream.h>
26
#include <haproxy/proxy.h>
27
#include <haproxy/sc_strm.h>
28
#include <haproxy/obj_type.h>
29
#include <haproxy/cfgparse.h>
30
#include <haproxy/arg.h>
31
#include <haproxy/initcall.h>
32
#include <haproxy/tools.h>
33
34
/*
35
 * =========== ANALYZE ===========
36
 * below are http process/ana helpers
37
 */
38
39
/* checks if <input> contains rfc7239 compliant port
40
 * Returns 1 for success and 0 for failure
41
 * if <port> is not NULL, it will be set to the extracted value contained
42
 * in <input>
43
 * <input> will be consumed accordingly (parsed/extracted characters are
44
 * removed from <input>)
45
 */
46
static inline int http_7239_extract_port(struct ist *input, uint16_t *port)
47
0
{
48
0
  char *start = istptr(*input);
49
0
  uint32_t port_cast = 0;
50
0
  int it = 0;
51
52
  /* strtol does not support non-null terminated str,
53
   * we extract port ourselves
54
   */
55
0
  while (it < istlen(*input) &&
56
0
         isdigit((unsigned char)start[it])) {
57
0
    port_cast = (port_cast * 10) + (start[it] - '0');
58
0
    if (port_cast > 65535)
59
0
      return 0; /* invalid port */
60
0
    it += 1;
61
0
  }
62
0
  if (!port_cast)
63
0
    return 0; /* invalid port */
64
  /* ok */
65
0
  if (port)
66
0
    *port = (uint16_t)port_cast;
67
0
  *input = istadv(*input, it);
68
0
  return 1;
69
0
}
70
71
/* check if char is a valid obfuscated identifier char
72
 * (according to 7239 RFC)
73
 * Returns non zero value for valid char
74
 */
75
static inline int http_7239_valid_obfsc(char c)
76
0
{
77
0
  return (isalnum((unsigned char)c) ||
78
0
                (c == '.' || c == '-' || c == '_'));
79
0
}
80
81
/* checks if <input> contains rfc7239 compliant obfuscated identifier
82
 * Returns 1 for success and 0 for failure
83
 * if <obfs> is not NULL, it will be set to the extracted value contained
84
 * in <input>
85
 * <input> will be consumed accordingly (parsed/extracted characters are
86
 * removed from <input>)
87
 */
88
static inline int http_7239_extract_obfs(struct ist *input, struct ist *obfs)
89
0
{
90
0
  int it = 0;
91
92
0
  if (obfs)
93
0
    obfs->ptr = input->ptr;
94
95
0
  while (it < istlen(*input) && istptr(*input)[it] != ';') {
96
0
    if (!http_7239_valid_obfsc(istptr(*input)[it]))
97
0
      break; /* end of obfs token */
98
0
    it += 1;
99
0
  }
100
0
  if (obfs)
101
0
    obfs->len = it;
102
0
  *input = istadv(*input, it);
103
0
  return !!it;
104
0
}
105
106
/* checks if <input> contains rfc7239 compliant IPV4 address
107
 * Returns 1 for success and 0 for failure
108
 * if <ip> is not NULL, it will be set to the extracted value contained
109
 * in <input>
110
 * <input> will be consumed accordingly (parsed/extracted characters are
111
 * removed from <input>)
112
 */
113
static inline int http_7239_extract_ipv4(struct ist *input, struct in_addr *ip)
114
0
{
115
0
  char ip4[INET_ADDRSTRLEN];
116
0
  unsigned char buf[sizeof(struct in_addr)];
117
0
  void *dst = buf;
118
0
  int it = 0;
119
120
0
  if (ip)
121
0
    dst = ip;
122
123
  /* extract ipv4 addr */
124
0
  while (it < istlen(*input) && it < (sizeof(ip4) - 1)) {
125
0
    if (!isdigit((unsigned char)istptr(*input)[it]) &&
126
0
        istptr(*input)[it] != '.')
127
0
      break; /* no more ip4 char */
128
0
    ip4[it] = istptr(*input)[it];
129
0
    it += 1;
130
0
  }
131
0
  ip4[it] = 0;
132
0
  if (inet_pton(AF_INET, ip4, dst) != 1)
133
0
    return 0; /* invalid ip4 addr */
134
  /* ok */
135
0
  *input = istadv(*input, it);
136
0
  return 1;
137
0
}
138
139
/* checks if <input> contains rfc7239 compliant IPV6 address
140
 *    assuming input.len >= 1 and first char is '['
141
 * Returns 1 for success and 0 for failure
142
 * if <ip> is not NULL, it will be set to the extracted value contained
143
 * in <input>
144
 * <input> will be consumed accordingly (parsed/extracted characters are
145
 * removed from <input>)
146
 */
147
static inline int http_7239_extract_ipv6(struct ist *input, struct in6_addr *ip)
148
0
{
149
0
  char ip6[INET6_ADDRSTRLEN];
150
0
  unsigned char buf[sizeof(struct in6_addr)];
151
0
  void *dst = buf;
152
0
  int it = 0;
153
154
0
  if (ip)
155
0
    dst = ip;
156
157
0
  *input = istnext(*input); /* skip '[' leading char */
158
  /* extract ipv6 addr */
159
0
  while (it < istlen(*input) &&
160
0
               it < (sizeof(ip6) - 1)) {
161
0
    if (!isalnum((unsigned char)istptr(*input)[it]) &&
162
0
        istptr(*input)[it] != ':')
163
0
      break; /* no more ip6 char */
164
0
    ip6[it] = istptr(*input)[it];
165
0
    it += 1;
166
0
  }
167
0
  ip6[it] = 0;
168
0
  if ((istlen(*input)-it) < 1 || istptr(*input)[it] != ']')
169
0
    return 0; /* missing ending "]" char */
170
0
  it += 1;
171
0
  if (inet_pton(AF_INET6, ip6, dst) != 1)
172
0
    return 0; /* invalid ip6 addr */
173
  /* ok */
174
0
  *input = istadv(*input, it);
175
0
  return 1;
176
0
}
177
178
/* checks if <input> contains rfc7239 compliant host
179
 * <quoted> is used to determine if the current input is being extracted
180
 * from a quoted (non zero) or unquoted (zero) token, as the parsing rules
181
 * differ whether the input is quoted or not according to the rfc.
182
 * Returns 1 for success and 0 for failure
183
 * if <host> is not NULL, it will be set to the extracted value contained
184
 * in <input>
185
 * <input> will be consumed accordingly (parsed/extracted characters are
186
 * removed from <input>)
187
 */
188
static inline int http_7239_extract_host(struct ist *input, struct ist *host, int quoted)
189
0
{
190
0
  if (istlen(*input) < 1)
191
0
    return 0; /* invalid input */
192
193
0
  if (host)
194
0
    host->ptr = input->ptr;
195
196
0
  if (quoted && *istptr(*input) == '[') {
197
    /* raw ipv6 address */
198
0
    if (!http_7239_extract_ipv6(input, NULL))
199
0
      return 0; /* invalid addr */
200
0
  }
201
0
  else {
202
    /* ipv4 or dns */
203
0
    while (istlen(*input)) {
204
0
      if (!isalnum((unsigned char)*istptr(*input)) &&
205
0
          *istptr(*input) != '.')
206
0
        break; /* end of hostname token */
207
0
      *input = istnext(*input);
208
0
    }
209
0
  }
210
0
  if (istlen(*input) < 1 || *istptr(*input) != ':') {
211
0
    goto out; /* no optional port provided */
212
0
  }
213
0
  if (!quoted)
214
0
    return 0; /* not supported */
215
0
  *input = istnext(*input); /* skip ':' */
216
  /* validate port */
217
0
  if (!http_7239_extract_port(input, NULL))
218
0
    return 0; /* invalid port */
219
0
 out:
220
0
  if (host)
221
0
    host->len = (input->ptr - host->ptr);
222
0
  return 1;
223
0
}
224
225
/* checks if <input> contains rfc7239 compliant nodename
226
 * <quoted> is used to determine if the current input is being extracted
227
 * from a quoted (non zero) or unquoted (zero) token, as the parsing rules
228
 * differ whether the input is quoted or not according to the rfc.
229
 * Returns 1 for success and 0 for failure
230
 * if <nodename> is not NULL, it will be set to the extracted value contained
231
 * in <input>
232
 * <input> will be consumed accordingly (parsed/extracted characters are
233
 * removed from <input>)
234
 */
235
static inline int http_7239_extract_nodename(struct ist *input, struct forwarded_header_nodename *nodename, int quoted)
236
0
{
237
0
  if (istlen(*input) < 1)
238
0
    return 0; /* invalid input */
239
0
  if (*istptr(*input) == '_') {
240
0
    struct ist *obfs = NULL;
241
242
    /* obfuscated nodename */
243
0
    *input = istnext(*input); /* skip '_' */
244
0
    if (nodename) {
245
0
      nodename->type = FORWARDED_HEADER_OBFS;
246
0
      obfs = &nodename->obfs;
247
0
    }
248
0
    if (!http_7239_extract_obfs(input, obfs))
249
0
      return 0; /* invalid obfs */
250
0
  } else if (*istptr(*input) == 'u') {
251
    /* "unknown" nodename? */
252
0
    if (istlen(*input) < 7 ||
253
0
        strncmp("unknown", istptr(*input), 7))
254
0
      return 0; /* syntax error */
255
0
    *input = istadv(*input, 7); /* skip "unknown" */
256
0
    if (nodename)
257
0
      nodename->type = FORWARDED_HEADER_UNK;
258
0
  } else if (quoted && *istptr(*input) == '[') {
259
0
    struct in6_addr *ip6 = NULL;
260
261
    /* ipv6 address */
262
0
    if (nodename) {
263
0
      struct sockaddr_in6 *addr = (void *)&nodename->ip;
264
265
0
      ip6 = &addr->sin6_addr;
266
0
      addr->sin6_family = AF_INET6;
267
0
      nodename->type = FORWARDED_HEADER_IP;
268
0
    }
269
0
    if (!http_7239_extract_ipv6(input, ip6))
270
0
      return 0; /* invalid ip6 */
271
0
  } else if (*istptr(*input)) {
272
0
    struct in_addr *ip = NULL;
273
274
    /* ipv4 address */
275
0
    if (nodename) {
276
0
      struct sockaddr_in *addr = (void *)&nodename->ip;
277
278
0
      ip = &addr->sin_addr;
279
0
      addr->sin_family = AF_INET;
280
0
      nodename->type = FORWARDED_HEADER_IP;
281
0
    }
282
0
    if (!http_7239_extract_ipv4(input, ip))
283
0
      return 0; /* invalid ip */
284
0
  } else
285
0
    return 0; /* unexpected char */
286
287
  /* ok */
288
0
  return 1;
289
0
}
290
291
/* checks if <input> contains rfc7239 compliant nodeport
292
 * <quoted> is used to determine if the current input is being extracted
293
 * from a quoted (non zero) or unquoted (zero) token, as the parsing rules
294
 * differ whether the input is quoted or not according to the rfc.
295
 * Returns 1 for success and 0 for failure
296
 * if <nodeport> is not NULL, it will be set to the extracted value contained
297
 * in <input>
298
 * <input> will be consumed accordingly (parsed/extracted characters are
299
 * removed from <input>)
300
 */
301
static inline int http_7239_extract_nodeport(struct ist *input, struct forwarded_header_nodeport *nodeport)
302
0
{
303
0
  if (*istptr(*input) == '_') {
304
0
    struct ist *obfs = NULL;
305
306
    /* obfuscated nodeport */
307
0
    *input = istnext(*input); /* skip '_' */
308
0
    if (nodeport) {
309
0
      nodeport->type = FORWARDED_HEADER_OBFS;
310
0
      obfs = &nodeport->obfs;
311
0
    }
312
0
    if (!http_7239_extract_obfs(input, obfs))
313
0
      return 0; /* invalid obfs */
314
0
  } else {
315
0
    uint16_t *port = NULL;
316
317
    /* normal port */
318
0
    if (nodeport) {
319
0
      nodeport->type = FORWARDED_HEADER_PORT;
320
0
      port = &nodeport->port;
321
0
    }
322
0
    if (!http_7239_extract_port(input, port))
323
0
      return 0; /* invalid port */
324
0
  }
325
  /* ok */
326
0
  return 1;
327
0
}
328
329
/* checks if <input> contains rfc7239 compliant node (nodename:nodeport token)
330
 * <quoted> is used to determine if the current input is being extracted
331
 * from a quoted (non zero) or unquoted (zero) token, as the parsing rules
332
 * differ whether the input is quoted or not according to the rfc.
333
 * Returns 1 for success and 0 for failure
334
 * if <node> is not NULL, it will be set to the extracted value contained
335
 * in <input>
336
 * <input> will be consumed accordingly (parsed/extracted characters are
337
 * removed from <input>)
338
 */
339
static inline int http_7239_extract_node(struct ist *input, struct forwarded_header_node *node, int quoted)
340
0
{
341
0
  struct forwarded_header_nodename *nodename = NULL;
342
0
  struct forwarded_header_nodeport *nodeport = NULL;
343
344
0
  if (node) {
345
0
    nodename = &node->nodename;
346
0
    nodeport = &node->nodeport;
347
0
    node->raw.ptr = input->ptr;
348
0
  }
349
0
  if (!http_7239_extract_nodename(input, nodename, quoted))
350
0
    return 0; /* invalid nodename */
351
0
  if (istlen(*input) < 1 || *istptr(*input) != ':') {
352
0
    if (node)
353
0
      node->nodeport.type = FORWARDED_HEADER_UNK;
354
0
    goto out; /* no optional port provided */
355
0
  }
356
0
  if (!quoted)
357
0
    return 0; /* not supported */
358
0
  *input = istnext(*input);
359
0
  if (!http_7239_extract_nodeport(input, nodeport))
360
0
    return 0; /* invalid nodeport */
361
0
 out:
362
  /* ok */
363
0
  if (node)
364
0
    node->raw.len = input->ptr - node->raw.ptr;
365
0
  return 1;
366
0
}
367
368
static inline int _forwarded_header_save_ctx(struct forwarded_header_ctx *ctx, int current_step, int required_steps)
369
0
{
370
0
  return (ctx && (current_step & required_steps));
371
0
}
372
373
static inline void _forwarded_header_quote_expected(struct ist *hdr, uint8_t *quoted)
374
0
{
375
0
  if (istlen(*hdr) > 0 && *istptr(*hdr) == '"') {
376
0
    *quoted = 1;
377
    /* node is quoted, we must find corresponding
378
     * ending quote at the end of the token
379
     */
380
0
    *hdr = istnext(*hdr); /* skip quote */
381
0
  }
382
0
}
383
384
/* checks if current header <hdr> is RFC 7239 compliant and can be "trusted".
385
 * function will stop parsing as soon as every <required_steps> have
386
 * been validated or error is encountered.
387
 * Provide FORWARDED_HEADER_ALL for a full header validating spectrum.
388
 * You may provide limited scope to perform quick searches on specific attributes
389
 * If <ctx> is provided (not NULL), parsed attributes will be stored according to
390
 * their types, allowing you to extract some useful information from the header.
391
 * Returns 0 on failure and <validated_steps> bitfield on success.
392
 */
393
int http_validate_7239_header(struct ist hdr, int required_steps, struct forwarded_header_ctx *ctx)
394
0
{
395
0
  int validated_steps = 0;
396
0
  int current_step = 0;
397
0
  uint8_t first = 1;
398
0
  uint8_t quoted = 0;
399
400
0
  while (istlen(hdr) && (required_steps & ~validated_steps)) {
401
0
    if (!first) {
402
0
      if (*istptr(hdr) == ';')
403
0
        hdr = istnext(hdr); /* skip ';' */
404
0
      else
405
0
        goto not_ok; /* unexpected char */
406
0
    }
407
0
    else
408
0
      first = 0;
409
410
0
    if (!(validated_steps & FORWARDED_HEADER_FOR) && istlen(hdr) > 4 &&
411
0
                    strncmp("for=", istptr(hdr), 4) == 0) {
412
0
      struct forwarded_header_node *node = NULL;
413
414
      /* for parameter */
415
0
      current_step = FORWARDED_HEADER_FOR;
416
0
      hdr = istadv(hdr, 4); /* skip "for=" */
417
0
      _forwarded_header_quote_expected(&hdr, &quoted);
418
0
      if (_forwarded_header_save_ctx(ctx, current_step, required_steps))
419
0
        node = &ctx->nfor;
420
      /* validate node */
421
0
      if (!http_7239_extract_node(&hdr, node, quoted))
422
0
        goto not_ok; /* invalid node */
423
0
    }
424
0
    else if (!(validated_steps & FORWARDED_HEADER_BY) && istlen(hdr) > 3 &&
425
0
                         strncmp("by=", istptr(hdr), 3) == 0) {
426
0
      struct forwarded_header_node *node = NULL;
427
428
      /* by parameter */
429
0
      current_step = FORWARDED_HEADER_BY;
430
0
      hdr = istadv(hdr, 3); /* skip "by=" */
431
0
      _forwarded_header_quote_expected(&hdr, &quoted);
432
0
      if (_forwarded_header_save_ctx(ctx, current_step, required_steps))
433
0
        node = &ctx->nby;
434
      /* validate node */
435
0
      if (!http_7239_extract_node(&hdr, node, quoted))
436
0
        goto not_ok; /* invalid node */
437
0
    }
438
0
    else if (!(validated_steps & FORWARDED_HEADER_HOST) && istlen(hdr) > 5 &&
439
0
                         strncmp("host=", istptr(hdr), 5) == 0) {
440
0
      struct ist *host = NULL;
441
442
      /* host parameter */
443
0
      current_step = FORWARDED_HEADER_HOST;
444
0
      hdr = istadv(hdr, 5); /* skip "host=" */
445
0
      _forwarded_header_quote_expected(&hdr, &quoted);
446
0
      if (_forwarded_header_save_ctx(ctx, current_step, required_steps))
447
0
        host = &ctx->host;
448
      /* validate host */
449
0
      if (!http_7239_extract_host(&hdr, host, quoted))
450
0
        goto not_ok; /* invalid host */
451
0
    }
452
0
    else if (!(validated_steps & FORWARDED_HEADER_PROTO) && istlen(hdr) > 6 &&
453
0
                         strncmp("proto=", istptr(hdr), 6) == 0) {
454
      /* proto parameter */
455
0
      current_step = FORWARDED_HEADER_PROTO;
456
0
      hdr = istadv(hdr, 6); /* skip "proto=" */
457
      /* validate proto (only common used http|https are supported for now) */
458
0
      if (istlen(hdr) < 4 || strncmp("http", istptr(hdr), 4))
459
0
        goto not_ok;
460
0
      hdr = istadv(hdr, 4); /* skip "http" */
461
0
      if (istlen(hdr) && *istptr(hdr) == 's') {
462
0
        hdr = istnext(hdr);
463
0
        if (_forwarded_header_save_ctx(ctx, current_step, required_steps))
464
0
          ctx->proto = FORWARDED_HEADER_HTTPS;
465
0
      } else if (_forwarded_header_save_ctx(ctx, current_step, required_steps))
466
0
        ctx->proto = FORWARDED_HEADER_HTTP;
467
      /* rfc allows for potential proto quoting, but we don't support
468
       * it: it is not common usage
469
       */
470
0
    }
471
0
    else {
472
      /* not supported
473
       * rfc allows for upcoming extensions
474
       * but obviously, we can't trust them
475
       * as they are not yet standardized
476
       */
477
478
0
      goto not_ok;
479
0
    }
480
    /* quote check */
481
0
    if (quoted) {
482
0
      if (istlen(hdr) < 1 || *istptr(hdr) != '"') {
483
        /* matching ending quote not found */
484
0
        goto not_ok;
485
0
      }
486
0
      hdr = istnext(hdr); /* skip ending quote */
487
0
      quoted = 0; /* reset */
488
0
    }
489
0
    validated_steps |= current_step;
490
0
  }
491
492
0
  return validated_steps;
493
494
0
 not_ok:
495
0
  return 0;
496
0
}
497
498
static inline void _7239_print_ip6(struct buffer *out, struct in6_addr *ip6_addr, int quoted)
499
0
{
500
0
  char pn[INET6_ADDRSTRLEN];
501
502
0
  inet_ntop(AF_INET6,
503
0
      ip6_addr,
504
0
      pn, sizeof(pn));
505
0
  if (!quoted)
506
0
    chunk_appendf(out, "\""); /* explicit quoting required for ipv6 */
507
0
  chunk_appendf(out, "[%s]", pn);
508
0
}
509
510
static inline void http_build_7239_header_nodename(struct buffer *out,
511
                                                   struct stream *s, struct proxy *curproxy,
512
                                                   const struct sockaddr_storage *addr,
513
                                                   struct http_ext_7239_forby *forby)
514
0
{
515
0
  struct in6_addr *ip6_addr;
516
0
  int quoted = !!forby->np_mode;
517
518
0
  if (forby->nn_mode == HTTP_7239_FORBY_ORIG) {
519
0
    if (addr && addr->ss_family == AF_INET) {
520
0
      unsigned char *pn = (unsigned char *)&((struct sockaddr_in *)addr)->sin_addr;
521
522
0
      chunk_appendf(out, "%d.%d.%d.%d", pn[0], pn[1], pn[2], pn[3]);
523
0
    }
524
0
    else if (addr && addr->ss_family == AF_INET6) {
525
0
      ip6_addr = &((struct sockaddr_in6 *)addr)->sin6_addr;
526
0
      _7239_print_ip6(out, ip6_addr, quoted);
527
0
    }
528
    /* else: not supported */
529
0
  }
530
0
  else if (forby->nn_mode == HTTP_7239_FORBY_SMP && forby->nn_expr) {
531
0
    struct sample *smp;
532
533
0
    smp = sample_process(curproxy, s->sess, s,
534
0
        SMP_OPT_DIR_REQ | SMP_OPT_FINAL, forby->nn_expr, NULL);
535
536
0
    if (smp) {
537
0
      if (smp->data.type == SMP_T_IPV6) {
538
        /* smp is valid IP6, print with RFC compliant output */
539
0
        ip6_addr = &smp->data.u.ipv6;
540
0
        _7239_print_ip6(out, ip6_addr, quoted);
541
0
      }
542
0
      else if (sample_casts[smp->data.type][SMP_T_STR] &&
543
0
         sample_casts[smp->data.type][SMP_T_STR](smp)) {
544
0
        struct ist validate_n = ist2(smp->data.u.str.area, smp->data.u.str.data);
545
0
        struct ist validate_o = ist2(smp->data.u.str.area, smp->data.u.str.data);
546
0
        struct forwarded_header_nodename nodename;
547
548
        /* validate nodename */
549
0
        if (http_7239_extract_nodename(&validate_n, &nodename, 1) &&
550
0
            !istlen(validate_n)) {
551
0
          if (nodename.type == FORWARDED_HEADER_IP &&
552
0
              nodename.ip.ss_family == AF_INET6) {
553
            /* special care needed for valid ip6 nodename (quoting) */
554
0
            ip6_addr = &((struct sockaddr_in6 *)&nodename.ip)->sin6_addr;
555
0
            _7239_print_ip6(out, ip6_addr, quoted);
556
0
          } else {
557
            /* no special care needed, input is already rfc compliant,
558
             * just print as regular non quoted string
559
             */
560
0
            chunk_cat(out, &smp->data.u.str);
561
0
          }
562
0
        }
563
0
        else if (http_7239_extract_obfs(&validate_o, NULL) &&
564
0
           !istlen(validate_o)) {
565
          /* raw user input that should be printed as 7239 obfs */
566
0
          chunk_appendf(out, "_%.*s", (int)smp->data.u.str.data, smp->data.u.str.area);
567
0
        }
568
        /* else: not compliant */
569
0
      }
570
      /* else: cannot be casted to str */
571
0
    }
572
    /* else: smp error */
573
0
  }
574
0
}
575
576
static inline void http_build_7239_header_nodeport(struct buffer *out,
577
                                                   struct stream *s, struct proxy *curproxy,
578
                                                   const struct sockaddr_storage *addr,
579
                                                   struct http_ext_7239_forby *forby)
580
0
{
581
0
  if (forby->np_mode == HTTP_7239_FORBY_ORIG) {
582
0
    if (addr && addr->ss_family == AF_INET)
583
0
      chunk_appendf(out, "%d", ntohs(((struct sockaddr_in *)addr)->sin_port));
584
0
    else if (addr && addr->ss_family == AF_INET6)
585
0
      chunk_appendf(out, "%d", ntohs(((struct sockaddr_in6 *)addr)->sin6_port));
586
    /* else: not supported */
587
0
  }
588
0
  else if (forby->np_mode == HTTP_7239_FORBY_SMP && forby->np_expr) {
589
0
    struct sample *smp;
590
591
0
    smp = sample_fetch_as_type(curproxy, s->sess, s,
592
0
        SMP_OPT_DIR_REQ | SMP_OPT_FINAL, forby->np_expr, SMP_T_STR);
593
0
    if (smp) {
594
0
      struct ist validate_n = ist2(smp->data.u.str.area, smp->data.u.str.data);
595
0
      struct ist validate_o = ist2(smp->data.u.str.area, smp->data.u.str.data);
596
597
      /* validate nodeport */
598
0
      if (http_7239_extract_nodeport(&validate_n, NULL) &&
599
0
          !istlen(validate_n)) {
600
        /* no special care needed, input is already rfc compliant,
601
         * just print as regular non quoted string
602
         */
603
0
        chunk_cat(out, &smp->data.u.str);
604
0
      }
605
0
      else if (http_7239_extract_obfs(&validate_o, NULL) &&
606
0
         !istlen(validate_o)) {
607
        /* raw user input that should be printed as 7239 obfs */
608
0
        chunk_appendf(out, "_%.*s", (int)smp->data.u.str.data, smp->data.u.str.area);
609
0
      }
610
      /* else: not compliant */
611
0
    }
612
    /* else: smp error */
613
0
  }
614
0
}
615
616
static inline void http_build_7239_header_node(struct buffer *out,
617
                                               struct stream *s, struct proxy *curproxy,
618
                                               const struct sockaddr_storage *addr,
619
                                               struct http_ext_7239_forby *forby)
620
0
{
621
0
  size_t offset_start;
622
0
  size_t offset_save;
623
624
0
  offset_start = out->data;
625
0
  if (forby->np_mode)
626
0
    chunk_appendf(out, "\"");
627
0
  offset_save = out->data;
628
0
  http_build_7239_header_nodename(out, s, curproxy, addr, forby);
629
0
  if (offset_save == out->data) {
630
    /* could not build nodename, either because some
631
     * data is not available or user is providing bad input
632
     */
633
0
    chunk_appendf(out, "unknown");
634
0
  }
635
0
  if (forby->np_mode) {
636
0
    chunk_appendf(out, ":");
637
0
    offset_save = out->data;
638
0
    http_build_7239_header_nodeport(out, s, curproxy, addr, forby);
639
0
    if (offset_save == out->data) {
640
      /* could not build nodeport, either because some data is
641
       * not available or user is providing bad input
642
       */
643
0
      out->data = offset_save - 1;
644
0
    }
645
0
  }
646
0
  if (out->data != offset_start && out->area[offset_start] == '"')
647
0
    chunk_appendf(out, "\""); /* add matching end quote */
648
0
}
649
650
static inline void http_build_7239_header_host(struct buffer *out,
651
                                               struct stream *s, struct proxy *curproxy,
652
                                               struct htx *htx, struct http_ext_7239_host *host)
653
0
{
654
0
  struct http_hdr_ctx ctx = { .blk = NULL };
655
0
  char *str = NULL;
656
0
  int str_len = 0;
657
658
0
  if (host->mode == HTTP_7239_HOST_ORIG &&
659
0
      http_find_header(htx, ist("host"), &ctx, 0)) {
660
0
    str = ctx.value.ptr;
661
0
    str_len = ctx.value.len;
662
0
 print_host:
663
0
    {
664
0
      struct ist validate = ist2(str, str_len);
665
      /* host check, to ensure rfc compliant output
666
       * (assuming host is quoted/escaped)
667
       */
668
0
      if (http_7239_extract_host(&validate, NULL, 1) && !istlen(validate))
669
0
        chunk_memcat(out, str, str_len);
670
      /* else: not compliant or partially compliant */
671
0
    }
672
673
0
  }
674
0
  else if (host->mode == HTTP_7239_HOST_SMP && host->expr) {
675
0
    struct sample *smp;
676
677
0
    smp = sample_fetch_as_type(curproxy, s->sess, s,
678
0
        SMP_OPT_DIR_REQ | SMP_OPT_FINAL, host->expr, SMP_T_STR);
679
0
    if (smp) {
680
0
      str = smp->data.u.str.area;
681
0
      str_len = smp->data.u.str.data;
682
0
      goto print_host;
683
0
    }
684
    /* else: smp error */
685
0
  }
686
0
}
687
688
/* Tries build 7239 header according to <curproxy> parameters and <s> context
689
 * It both depends on <curproxy>->http_ext->fwd for config and <s> for request
690
 * context data.
691
 * The function will write output to <out> buffer
692
 * Returns 1 for success and 0 for error (ie: not enough space in buffer)
693
 */
694
static int http_build_7239_header(struct buffer *out,
695
                                  struct stream *s, struct proxy *curproxy, struct htx *htx)
696
0
{
697
0
  struct connection *cli_conn = objt_conn(strm_sess(s)->origin);
698
699
0
  if (curproxy->http_ext->fwd->p_proto) {
700
0
    chunk_appendf(out, "%sproto=%s", ((out->data) ? ";" : ""),
701
0
      ((conn_is_ssl(cli_conn)) ? "https" : "http"));
702
0
  }
703
0
  if (curproxy->http_ext->fwd->p_host.mode) {
704
    /* always add quotes for host parameter to make output compliance checks simpler */
705
0
    chunk_appendf(out, "%shost=\"", ((out->data) ? ";" : ""));
706
    /* ignore return value for now, but could be useful some day */
707
0
    http_build_7239_header_host(out, s, curproxy, htx, &curproxy->http_ext->fwd->p_host);
708
0
    chunk_appendf(out, "\"");
709
0
  }
710
711
0
  if (curproxy->http_ext->fwd->p_by.nn_mode) {
712
0
    const struct sockaddr_storage *dst = sc_dst(s->scf);
713
714
0
    chunk_appendf(out, "%sby=", ((out->data) ? ";" : ""));
715
0
    http_build_7239_header_node(out, s, curproxy, dst, &curproxy->http_ext->fwd->p_by);
716
0
  }
717
718
0
  if (curproxy->http_ext->fwd->p_for.nn_mode) {
719
0
    const struct sockaddr_storage *src = sc_src(s->scf);
720
721
0
    chunk_appendf(out, "%sfor=", ((out->data) ? ";" : ""));
722
0
    http_build_7239_header_node(out, s, curproxy, src, &curproxy->http_ext->fwd->p_for);
723
0
  }
724
0
  if (unlikely(out->data == out->size)) {
725
    /* not enough space in buffer, error */
726
0
    return 0;
727
0
  }
728
0
  return 1;
729
0
}
730
731
/* This function will try to inject RFC 7239 forwarded header if
732
 * configured on the backend (ignored for frontends).
733
 * Will do nothing if the option is not enabled on the proxy.
734
 * Returns 1 for success and 0 for failure
735
 */
736
int http_handle_7239_header(struct stream *s, struct channel *req)
737
0
{
738
0
  struct proxy *curproxy = s->be; /* ignore frontend */
739
740
0
  if (curproxy->http_ext && curproxy->http_ext->fwd) {
741
0
    struct htx *htx = htxbuf(&req->buf);
742
0
    int validate = 1;
743
0
    struct http_hdr_ctx find = { .blk = NULL };
744
0
    struct http_hdr_ctx last = { .blk = NULL};
745
0
    struct ist hdr = ist("forwarded");
746
747
    /* ok, let's build forwarded header */
748
0
    chunk_reset(&trash);
749
0
    if (unlikely(!http_build_7239_header(&trash, s, curproxy, htx)))
750
0
      return 0; /* error when building header (bad user conf or memory error) */
751
752
    /* validate existing forwarded header (including multiple values),
753
     * hard stop if error is encountered
754
     */
755
0
    while (http_find_header(htx, hdr, &find, 0)) {
756
      /* validate current header chunk */
757
0
      if (!http_validate_7239_header(find.value, FORWARDED_HEADER_ALL, NULL)) {
758
        /* at least one error, existing forwarded header not OK, add our own
759
         * forwarded header, so that it can be trusted
760
         */
761
0
        validate = 0;
762
0
        break;
763
0
      }
764
0
      last = find;
765
0
    }
766
    /* no errors, append our data at the end of existing header */
767
0
    if (last.blk && validate) {
768
0
      if (unlikely(!http_append_header_value(htx, &last, ist2(trash.area, trash.data))))
769
0
        return 0; /* htx error */
770
0
    }
771
0
    else {
772
0
      if (unlikely(!http_add_header(htx, hdr, ist2(trash.area, trash.data))))
773
0
        return 0; /* htx error */
774
0
    }
775
0
  }
776
0
  return 1;
777
0
}
778
779
/*
780
 * add X-Forwarded-For if either the frontend or the backend
781
 * asks for it.
782
 * Returns 1 for success and 0 for failure
783
 */
784
int http_handle_xff_header(struct stream *s, struct channel *req)
785
0
{
786
0
  struct session *sess = s->sess;
787
0
  struct http_ext_xff *f_xff = NULL;
788
0
  struct http_ext_xff *b_xff = NULL;
789
790
0
  if (sess->fe->http_ext && sess->fe->http_ext->xff) {
791
    /* frontend */
792
0
    f_xff = sess->fe->http_ext->xff;
793
0
  }
794
0
  if (s->be->http_ext && s->be->http_ext->xff) {
795
    /* backend */
796
0
    b_xff = s->be->http_ext->xff;
797
0
  }
798
799
0
  if (f_xff || b_xff) {
800
0
    struct htx *htx = htxbuf(&req->buf);
801
0
    const struct sockaddr_storage *src = sc_src(s->scf);
802
0
    struct http_hdr_ctx ctx = { .blk = NULL };
803
0
    struct ist hdr = ((b_xff) ? b_xff->hdr_name : f_xff->hdr_name);
804
805
0
    if ((!f_xff || f_xff->mode == HTTP_XFF_IFNONE) &&
806
0
        (!b_xff || b_xff->mode == HTTP_XFF_IFNONE) &&
807
0
        http_find_header(htx, hdr, &ctx, 0)) {
808
      /* The header is set to be added only if none is present
809
       * and we found it, so don't do anything.
810
       */
811
0
    }
812
0
    else if (src && src->ss_family == AF_INET) {
813
      /* Add an X-Forwarded-For header unless the source IP is
814
       * in the 'except' network range.
815
       */
816
0
      if ((!f_xff || ipcmp2net(src, &f_xff->except_net)) &&
817
0
          (!b_xff || ipcmp2net(src, &b_xff->except_net))) {
818
0
        unsigned char *pn = (unsigned char *)&((struct sockaddr_in *)src)->sin_addr;
819
820
        /* Note: we rely on the backend to get the header name to be used for
821
         * x-forwarded-for, because the header is really meant for the backends.
822
         * However, if the backend did not specify any option, we have to rely
823
         * on the frontend's header name.
824
         */
825
0
        chunk_printf(&trash, "%d.%d.%d.%d", pn[0], pn[1], pn[2], pn[3]);
826
0
        if (unlikely(!http_add_header(htx, hdr, ist2(trash.area, trash.data))))
827
0
          return 0;
828
0
      }
829
0
    }
830
0
    else if (src && src->ss_family == AF_INET6) {
831
      /* Add an X-Forwarded-For header unless the source IP is
832
       * in the 'except' network range.
833
       */
834
0
      if ((!f_xff || ipcmp2net(src, &f_xff->except_net)) &&
835
0
          (!b_xff || ipcmp2net(src, &b_xff->except_net))) {
836
0
        char pn[INET6_ADDRSTRLEN];
837
838
0
        inet_ntop(AF_INET6,
839
0
            (const void *)&((struct sockaddr_in6 *)(src))->sin6_addr,
840
0
            pn, sizeof(pn));
841
842
        /* Note: we rely on the backend to get the header name to be used for
843
         * x-forwarded-for, because the header is really meant for the backends.
844
         * However, if the backend did not specify any option, we have to rely
845
         * on the frontend's header name.
846
         */
847
0
        chunk_printf(&trash, "%s", pn);
848
0
        if (unlikely(!http_add_header(htx, hdr, ist2(trash.area, trash.data))))
849
0
          return 0;
850
0
      }
851
0
    }
852
0
  }
853
0
  return 1;
854
0
}
855
856
/*
857
 * add X-Original-To if either the frontend or the backend
858
 * asks for it.
859
 * Returns 1 for success and 0 for failure
860
 */
861
int http_handle_xot_header(struct stream *s, struct channel *req)
862
0
{
863
0
  struct session *sess = s->sess;
864
0
  struct http_ext_xot *f_xot = NULL;
865
0
  struct http_ext_xot *b_xot = NULL;
866
867
0
  if (sess->fe->http_ext && sess->fe->http_ext->xot) {
868
    /* frontend */
869
0
    f_xot = sess->fe->http_ext->xot;
870
0
  }
871
0
  if (s->be->http_ext && s->be->http_ext->xot) {
872
    /* backend */
873
0
    b_xot = s->be->http_ext->xot;
874
0
  }
875
876
0
  if (f_xot || b_xot) {
877
0
    struct htx *htx = htxbuf(&req->buf);
878
0
    const struct sockaddr_storage *dst = sc_dst(s->scf);
879
0
    struct ist hdr = ((b_xot) ? b_xot->hdr_name : f_xot->hdr_name);
880
881
0
    if (dst && dst->ss_family == AF_INET) {
882
      /* Add an X-Original-To header unless the destination IP is
883
       * in the 'except' network range.
884
       */
885
0
      if ((!f_xot || ipcmp2net(dst, &f_xot->except_net)) &&
886
0
          (!b_xot || ipcmp2net(dst, &b_xot->except_net))) {
887
0
        unsigned char *pn = (unsigned char *)&((struct sockaddr_in *)dst)->sin_addr;
888
889
        /* Note: we rely on the backend to get the header name to be used for
890
         * x-original-to, because the header is really meant for the backends.
891
         * However, if the backend did not specify any option, we have to rely
892
         * on the frontend's header name.
893
         */
894
0
        chunk_printf(&trash, "%d.%d.%d.%d", pn[0], pn[1], pn[2], pn[3]);
895
0
        if (unlikely(!http_add_header(htx, hdr, ist2(trash.area, trash.data))))
896
0
          return 0;
897
0
      }
898
0
    }
899
0
    else if (dst && dst->ss_family == AF_INET6) {
900
      /* Add an X-Original-To header unless the source IP is
901
       * in the 'except' network range.
902
       */
903
0
      if ((!f_xot || ipcmp2net(dst, &f_xot->except_net)) &&
904
0
          (!b_xot || ipcmp2net(dst, &b_xot->except_net))) {
905
0
        char pn[INET6_ADDRSTRLEN];
906
907
0
        inet_ntop(AF_INET6,
908
0
            (const void *)&((struct sockaddr_in6 *)dst)->sin6_addr,
909
0
            pn, sizeof(pn));
910
911
        /* Note: we rely on the backend to get the header name to be used for
912
         * x-forwarded-for, because the header is really meant for the backends.
913
         * However, if the backend did not specify any option, we have to rely
914
         * on the frontend's header name.
915
         */
916
0
        chunk_printf(&trash, "%s", pn);
917
0
        if (unlikely(!http_add_header(htx, hdr, ist2(trash.area, trash.data))))
918
0
          return 0;
919
0
      }
920
0
    }
921
0
  }
922
0
  return 1;
923
0
}
924
925
/*
926
 * =========== CONFIG ===========
927
 * below are helpers to parse http ext options from the config
928
 */
929
static int proxy_http_parse_oom(const char *file, int linenum)
930
0
{
931
0
  int err_code = 0;
932
933
0
  ha_alert("parsing [%s:%d]: out of memory.\n", file, linenum);
934
0
  err_code |= ERR_ALERT | ERR_ABORT;
935
0
  return err_code;
936
0
}
937
938
static inline int _proxy_http_parse_7239_expr(char **args, int *cur_arg,
939
                                              const char *file, int linenum,
940
                                              char **expr_s)
941
0
{
942
0
  int err_code = 0;
943
944
0
  if (!*args[*cur_arg + 1]) {
945
0
    ha_alert("parsing [%s:%d]: '%s' expects <expr> as argument.\n",
946
0
       file, linenum, args[*cur_arg]);
947
0
    err_code |= ERR_ALERT | ERR_FATAL;
948
0
    goto out;
949
0
  }
950
0
  *cur_arg += 1;
951
0
  ha_free(expr_s);
952
0
  *expr_s = strdup(args[*cur_arg]);
953
0
  if (!*expr_s)
954
0
    return proxy_http_parse_oom(file, linenum);
955
0
  *cur_arg += 1;
956
0
 out:
957
0
  return err_code;
958
0
}
959
960
/* forwarded/7239 RFC: tries to parse "option forwarded" config keyword
961
 * Returns a composition of ERR_ABORT, ERR_ALERT, ERR_FATAL, ERR_WARN
962
 */
963
int proxy_http_parse_7239(char **args, int cur_arg,
964
                          struct proxy *curproxy, const struct proxy *defpx,
965
                          const char *file, int linenum)
966
0
{
967
0
  struct http_ext_7239 *fwd;
968
0
  int err_code = 0;
969
970
0
  if (warnifnotcap(curproxy, PR_CAP_BE, file, linenum, "option forwarded", NULL)) {
971
    /* option is ignored for frontends */
972
0
    err_code |= ERR_WARN;
973
0
    goto out;
974
0
  }
975
976
0
  if (!http_ext_7239_prepare(curproxy))
977
0
    return proxy_http_parse_oom(file, linenum);
978
979
0
  fwd = curproxy->http_ext->fwd;
980
981
0
  fwd->p_proto = 0;
982
0
  fwd->p_host.mode = 0;
983
0
  fwd->p_for.nn_mode = 0;
984
0
  fwd->p_for.np_mode = 0;
985
0
  fwd->p_by.nn_mode = 0;
986
0
  fwd->p_by.np_mode = 0;
987
0
  ha_free(&fwd->c_file);
988
0
  fwd->c_file = strdup(file);
989
0
  fwd->c_line = linenum;
990
991
  /* start at 2, since 0+1 = "option" "forwarded" */
992
0
  cur_arg = 2;
993
0
  if (!*(args[cur_arg])) {
994
    /* no optional argument provided, use default settings */
995
0
    fwd->p_for.nn_mode = HTTP_7239_FORBY_ORIG; /* enable for and mimic xff */
996
0
    fwd->p_proto = 1; /* enable proto */
997
0
    goto out;
998
0
  }
999
  /* loop to go through optional arguments */
1000
0
  while (*(args[cur_arg])) {
1001
0
    if (strcmp(args[cur_arg], "proto") == 0) {
1002
0
      fwd->p_proto = 1;
1003
0
      cur_arg += 1;
1004
0
    } else if (strcmp(args[cur_arg], "host") == 0) {
1005
0
      fwd->p_host.mode = HTTP_7239_HOST_ORIG;
1006
0
      cur_arg += 1;
1007
0
    } else if (strcmp(args[cur_arg], "host-expr") == 0) {
1008
0
      fwd->p_host.mode = HTTP_7239_HOST_SMP;
1009
0
      err_code |= _proxy_http_parse_7239_expr(args, &cur_arg, file, linenum,
1010
0
                &fwd->p_host.expr_s);
1011
0
      if (err_code & ERR_CODE)
1012
0
        goto out;
1013
0
    } else if (strcmp(args[cur_arg], "by") == 0) {
1014
0
      fwd->p_by.nn_mode = HTTP_7239_FORBY_ORIG;
1015
0
      cur_arg += 1;
1016
0
    } else if (strcmp(args[cur_arg], "by-expr") == 0) {
1017
0
      fwd->p_by.nn_mode = HTTP_7239_FORBY_SMP;
1018
0
      err_code |= _proxy_http_parse_7239_expr(args, &cur_arg, file, linenum,
1019
0
                &fwd->p_by.nn_expr_s);
1020
0
      if (err_code & ERR_CODE)
1021
0
        goto out;
1022
0
    } else if (strcmp(args[cur_arg], "for") == 0) {
1023
0
      fwd->p_for.nn_mode = HTTP_7239_FORBY_ORIG;
1024
0
      cur_arg += 1;
1025
0
    } else if (strcmp(args[cur_arg], "for-expr") == 0) {
1026
0
      fwd->p_for.nn_mode = HTTP_7239_FORBY_SMP;
1027
0
      err_code |= _proxy_http_parse_7239_expr(args, &cur_arg, file, linenum,
1028
0
                &fwd->p_for.nn_expr_s);
1029
0
      if (err_code & ERR_CODE)
1030
0
        goto out;
1031
0
    } else if (strcmp(args[cur_arg], "by_port") == 0) {
1032
0
      fwd->p_by.np_mode = HTTP_7239_FORBY_ORIG;
1033
0
      cur_arg += 1;
1034
0
    } else if (strcmp(args[cur_arg], "by_port-expr") == 0) {
1035
0
      fwd->p_by.np_mode = HTTP_7239_FORBY_SMP;
1036
0
      err_code |= _proxy_http_parse_7239_expr(args, &cur_arg, file, linenum,
1037
0
                &fwd->p_by.np_expr_s);
1038
0
      if (err_code & ERR_CODE)
1039
0
        goto out;
1040
0
    } else if (strcmp(args[cur_arg], "for_port") == 0) {
1041
0
      fwd->p_for.np_mode = HTTP_7239_FORBY_ORIG;
1042
0
      cur_arg += 1;
1043
0
    } else if (strcmp(args[cur_arg], "for_port-expr") == 0) {
1044
0
      fwd->p_for.np_mode = HTTP_7239_FORBY_SMP;
1045
0
      err_code |= _proxy_http_parse_7239_expr(args, &cur_arg, file, linenum,
1046
0
                &fwd->p_for.np_expr_s);
1047
0
      if (err_code & ERR_CODE)
1048
0
        goto out;
1049
0
    } else {
1050
      /* unknown suboption - catchall */
1051
0
      ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'proto', 'host', "
1052
0
         "'host-expr', 'by', 'by-expr', 'by_port', 'by_port-expr', "
1053
0
         "'for', 'for-expr', 'for_port' and 'for_port-expr'.\n",
1054
0
         file, linenum, args[0], args[1]);
1055
0
      err_code |= ERR_ALERT | ERR_FATAL;
1056
0
      goto out;
1057
0
    }
1058
0
  } /* end while loop */
1059
1060
  /* consistency check */
1061
0
  if (fwd->p_by.np_mode &&
1062
0
      !fwd->p_by.nn_mode) {
1063
0
    fwd->p_by.np_mode = 0;
1064
0
    ha_free(&fwd->p_by.np_expr_s);
1065
0
    ha_warning("parsing [%s:%d] : '%s %s' : '%s' will be ignored because both 'by' "
1066
0
         "and 'by-expr' are unset\n",
1067
0
         file, linenum, args[0], args[1],
1068
0
         ((fwd->p_by.np_mode == HTTP_7239_FORBY_ORIG) ? "by_port" : "by_port-expr"));
1069
0
    err_code |= ERR_WARN;
1070
0
  }
1071
0
  if (fwd->p_for.np_mode &&
1072
0
      !fwd->p_for.nn_mode) {
1073
0
    fwd->p_for.np_mode = 0;
1074
0
    ha_free(&fwd->p_for.np_expr_s);
1075
0
    ha_warning("parsing [%s:%d] : '%s %s' : '%s' will be ignored because both 'for' "
1076
0
         "and 'for-expr' are unset\n",
1077
0
         file, linenum, args[0], args[1],
1078
0
         ((fwd->p_for.np_mode == HTTP_7239_FORBY_ORIG) ? "for_port" : "for_port-expr"));
1079
0
    err_code |= ERR_WARN;
1080
0
  }
1081
1082
0
 out:
1083
0
  return err_code;
1084
0
}
1085
1086
/* rfc7239 forwarded option needs a postparsing step
1087
 * to convert parsing hints into runtime usable sample expressions
1088
 * Returns a composition of ERR_NONE, ERR_FATAL, ERR_ALERT, ERR_WARN
1089
 */
1090
int proxy_http_compile_7239(struct proxy *curproxy)
1091
0
{
1092
0
  struct http_ext_7239 *fwd;
1093
0
  int err = ERR_NONE;
1094
0
  int loop;
1095
1096
0
  if (!(curproxy->cap & PR_CAP_BE)) {
1097
    /* no backend cap: not supported (ie: frontend) */
1098
0
    goto out;
1099
0
  }
1100
1101
  /* should not happen (test should be performed after BE cap test) */
1102
0
  BUG_ON(!curproxy->http_ext || !curproxy->http_ext->fwd);
1103
1104
0
  curproxy->conf.args.ctx = ARGC_OPT; /* option */
1105
0
  curproxy->conf.args.file = curproxy->http_ext->fwd->c_file;
1106
0
  curproxy->conf.args.line = curproxy->http_ext->fwd->c_line;
1107
0
  fwd = curproxy->http_ext->fwd;
1108
1109
  /* it is important that we keep iterating on error to make sure
1110
   * all fwd config fields are in the same state (post-parsing state)
1111
   */
1112
0
  for (loop = 0; loop < 5; loop++) {
1113
0
    char **expr_str = NULL;
1114
0
    struct sample_expr **expr = NULL;
1115
0
    struct sample_expr *cur_expr;
1116
0
    char *err_str = NULL;
1117
0
    int smp = 0;
1118
0
    int idx = 0;
1119
1120
0
    switch (loop) {
1121
0
      case 0:
1122
        /* host */
1123
0
        expr_str = &fwd->p_host.expr_s;
1124
0
        expr = &fwd->p_host.expr;
1125
0
        smp = (fwd->p_host.mode == HTTP_7239_HOST_SMP);
1126
0
        break;
1127
0
      case 1:
1128
        /* by->node */
1129
0
        expr_str = &fwd->p_by.nn_expr_s;
1130
0
        expr = &fwd->p_by.nn_expr;
1131
0
        smp = (fwd->p_by.nn_mode == HTTP_7239_FORBY_SMP);
1132
0
        break;
1133
0
      case 2:
1134
        /* by->nodeport */
1135
0
        expr_str = &fwd->p_by.np_expr_s;
1136
0
        expr = &fwd->p_by.np_expr;
1137
0
        smp = (fwd->p_by.np_mode == HTTP_7239_FORBY_SMP);
1138
0
        break;
1139
0
      case 3:
1140
        /* for->node */
1141
0
        expr_str = &fwd->p_for.nn_expr_s;
1142
0
        expr = &fwd->p_for.nn_expr;
1143
0
        smp = (fwd->p_for.nn_mode == HTTP_7239_FORBY_SMP);
1144
0
        break;
1145
0
      case 4:
1146
        /* for->nodeport */
1147
0
        expr_str = &fwd->p_for.np_expr_s;
1148
0
        expr = &fwd->p_for.np_expr;
1149
0
        smp = (fwd->p_for.np_mode == HTTP_7239_FORBY_SMP);
1150
0
        break;
1151
0
    }
1152
0
    if (!smp)
1153
0
      continue; /* no expr */
1154
1155
    /* expr and expr_str cannot be NULL past this point */
1156
0
    BUG_ON(!expr || !expr_str);
1157
1158
0
    if (!*expr_str) {
1159
      /* should not happen unless system memory exhaustion */
1160
0
      ha_alert("%s '%s' [%s:%d]: failed to parse 'option forwarded' expression : %s.\n",
1161
0
         proxy_type_str(curproxy), curproxy->id,
1162
0
         fwd->c_file, fwd->c_line,
1163
0
         "memory error");
1164
0
      err |= ERR_ALERT | ERR_FATAL;
1165
0
      continue;
1166
0
    }
1167
1168
0
    cur_expr =
1169
0
      sample_parse_expr((char*[]){*expr_str, NULL}, &idx,
1170
0
            fwd->c_file,
1171
0
            fwd->c_line,
1172
0
            &err_str, &curproxy->conf.args, NULL);
1173
1174
0
    if (!cur_expr) {
1175
0
      ha_alert("%s '%s' [%s:%d]: failed to parse 'option forwarded' expression '%s' in : %s.\n",
1176
0
         proxy_type_str(curproxy), curproxy->id,
1177
0
         fwd->c_file, fwd->c_line,
1178
0
         *expr_str, err_str);
1179
0
      ha_free(&err_str);
1180
0
      err |= ERR_ALERT | ERR_FATAL;
1181
0
    }
1182
0
    else if (!(cur_expr->fetch->val & SMP_VAL_BE_HRQ_HDR)) {
1183
      /* fetch not available in this context: sample expr is resolved
1184
       * within backend right after headers are processed.
1185
       * (in http_process_request())
1186
       * -> we simply warn the user about the misuse
1187
       */
1188
0
      ha_warning("%s '%s' [%s:%d]: in 'option forwarded' sample expression '%s' : "
1189
0
           "some args extract information from '%s', "
1190
0
           "none of which is available here.\n",
1191
0
           proxy_type_str(curproxy), curproxy->id,
1192
0
           fwd->c_file, fwd->c_line,
1193
0
           *expr_str, sample_ckp_names(cur_expr->fetch->use));
1194
0
      err |= ERR_WARN;
1195
0
    }
1196
    /* post parsing individual expr cleanup */
1197
0
    ha_free(expr_str);
1198
1199
    /* expr assignment */
1200
0
    *expr = cur_expr;
1201
0
  }
1202
0
  curproxy->conf.args.file = NULL;
1203
0
  curproxy->conf.args.line = 0;
1204
1205
  /* post parsing general cleanup */
1206
0
  ha_free(&fwd->c_file);
1207
0
  fwd->c_line = 0;
1208
1209
0
  fwd->c_mode = 1; /* parsing completed */
1210
1211
0
 out:
1212
0
  return err;
1213
0
}
1214
1215
/* x-forwarded-for: tries to parse "option forwardfor" config keyword
1216
 * Returns a composition of ERR_NONE, ERR_FATAL, ERR_ALERT
1217
 */
1218
int proxy_http_parse_xff(char **args, int cur_arg,
1219
                         struct proxy *curproxy, const struct proxy *defpx,
1220
                         const char *file, int linenum)
1221
0
{
1222
0
  struct http_ext_xff *xff;
1223
0
  int err_code = 0;
1224
1225
0
  if (!http_ext_xff_prepare(curproxy))
1226
0
    return proxy_http_parse_oom(file, linenum);
1227
1228
0
  xff = curproxy->http_ext->xff;
1229
1230
  /* insert x-forwarded-for field, but not for the IP address listed as an except.
1231
   * set default options (ie: bitfield, header name, etc)
1232
   */
1233
1234
0
  xff->mode = HTTP_XFF_ALWAYS;
1235
1236
0
  istfree(&xff->hdr_name);
1237
0
  xff->hdr_name = istdup(ist(DEF_XFORWARDFOR_HDR));
1238
0
  if (!isttest(xff->hdr_name))
1239
0
    return proxy_http_parse_oom(file, linenum);
1240
0
  xff->except_net.family = AF_UNSPEC;
1241
1242
  /* loop to go through arguments - start at 2, since 0+1 = "option" "forwardfor" */
1243
0
  cur_arg = 2;
1244
0
  while (*(args[cur_arg])) {
1245
0
    if (strcmp(args[cur_arg], "except") == 0) {
1246
0
      unsigned char mask;
1247
0
      int i;
1248
1249
      /* suboption except - needs additional argument for it */
1250
0
      if (*(args[cur_arg+1]) &&
1251
0
          str2net(args[cur_arg+1], 1, &xff->except_net.addr.v4.ip, &xff->except_net.addr.v4.mask)) {
1252
0
        xff->except_net.family = AF_INET;
1253
0
        xff->except_net.addr.v4.ip.s_addr &= xff->except_net.addr.v4.mask.s_addr;
1254
0
      }
1255
0
      else if (*(args[cur_arg+1]) &&
1256
0
         str62net(args[cur_arg+1], &xff->except_net.addr.v6.ip, &mask)) {
1257
0
        xff->except_net.family = AF_INET6;
1258
0
        len2mask6(mask, &xff->except_net.addr.v6.mask);
1259
0
        for (i = 0; i < 16; i++)
1260
0
          xff->except_net.addr.v6.ip.s6_addr[i] &= xff->except_net.addr.v6.mask.s6_addr[i];
1261
0
      }
1262
0
      else {
1263
0
        ha_alert("parsing [%s:%d] : '%s %s %s' expects <address>[/mask] as argument.\n",
1264
0
           file, linenum, args[0], args[1], args[cur_arg]);
1265
0
        err_code |= ERR_ALERT | ERR_FATAL;
1266
0
        goto out;
1267
0
      }
1268
      /* flush useless bits */
1269
0
      cur_arg += 2;
1270
0
    } else if (strcmp(args[cur_arg], "header") == 0) {
1271
      /* suboption header - needs additional argument for it */
1272
0
      if (*(args[cur_arg+1]) == 0) {
1273
0
        ha_alert("parsing [%s:%d] : '%s %s %s' expects <header_name> as argument.\n",
1274
0
           file, linenum, args[0], args[1], args[cur_arg]);
1275
0
        err_code |= ERR_ALERT | ERR_FATAL;
1276
0
        goto out;
1277
0
      }
1278
0
      istfree(&xff->hdr_name);
1279
0
      xff->hdr_name = istdup(ist(args[cur_arg+1]));
1280
0
      if (!isttest(xff->hdr_name))
1281
0
        return proxy_http_parse_oom(file, linenum);
1282
0
      cur_arg += 2;
1283
0
    } else if (strcmp(args[cur_arg], "if-none") == 0) {
1284
0
      xff->mode = HTTP_XFF_IFNONE;
1285
0
      cur_arg += 1;
1286
0
    } else {
1287
      /* unknown suboption - catchall */
1288
0
      ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'except', 'header' and 'if-none'.\n",
1289
0
         file, linenum, args[0], args[1]);
1290
0
      err_code |= ERR_ALERT | ERR_FATAL;
1291
0
      goto out;
1292
0
    }
1293
0
  } /* end while loop */
1294
0
 out:
1295
0
  return err_code;
1296
0
}
1297
1298
/* x-original-to: tries to parse "option originalto" config keyword
1299
 * Returns a composition of ERR_NONE, ERR_FATAL, ERR_ALERT
1300
 */
1301
int proxy_http_parse_xot(char **args, int cur_arg,
1302
                         struct proxy *curproxy, const struct proxy *defpx,
1303
                         const char *file, int linenum)
1304
0
{
1305
0
  struct http_ext_xot *xot;
1306
0
  int err_code = 0;
1307
1308
0
  if (!http_ext_xot_prepare(curproxy))
1309
0
    return proxy_http_parse_oom(file, linenum);
1310
1311
0
  xot = curproxy->http_ext->xot;
1312
1313
  /* insert x-original-to field, but not for the IP address listed as an except.
1314
   * set default options (ie: bitfield, header name, etc)
1315
   */
1316
1317
0
  istfree(&xot->hdr_name);
1318
0
  xot->hdr_name = istdup(ist(DEF_XORIGINALTO_HDR));
1319
0
  if (!isttest(xot->hdr_name))
1320
0
    return proxy_http_parse_oom(file, linenum);
1321
0
  xot->except_net.family = AF_UNSPEC;
1322
1323
  /* loop to go through arguments - start at 2, since 0+1 = "option" "originalto" */
1324
0
  cur_arg = 2;
1325
0
  while (*(args[cur_arg])) {
1326
0
    if (strcmp(args[cur_arg], "except") == 0) {
1327
0
      unsigned char mask;
1328
0
      int i;
1329
1330
      /* suboption except - needs additional argument for it */
1331
0
      if (*(args[cur_arg+1]) &&
1332
0
          str2net(args[cur_arg+1], 1, &xot->except_net.addr.v4.ip, &xot->except_net.addr.v4.mask)) {
1333
0
        xot->except_net.family = AF_INET;
1334
0
        xot->except_net.addr.v4.ip.s_addr &= xot->except_net.addr.v4.mask.s_addr;
1335
0
      }
1336
0
      else if (*(args[cur_arg+1]) &&
1337
0
         str62net(args[cur_arg+1], &xot->except_net.addr.v6.ip, &mask)) {
1338
0
        xot->except_net.family = AF_INET6;
1339
0
        len2mask6(mask, &xot->except_net.addr.v6.mask);
1340
0
        for (i = 0; i < 16; i++)
1341
0
          xot->except_net.addr.v6.ip.s6_addr[i] &= xot->except_net.addr.v6.mask.s6_addr[i];
1342
0
      }
1343
0
      else {
1344
0
        ha_alert("parsing [%s:%d] : '%s %s %s' expects <address>[/mask] as argument.\n",
1345
0
           file, linenum, args[0], args[1], args[cur_arg]);
1346
0
        err_code |= ERR_ALERT | ERR_FATAL;
1347
0
        goto out;
1348
0
      }
1349
0
      cur_arg += 2;
1350
0
    } else if (strcmp(args[cur_arg], "header") == 0) {
1351
      /* suboption header - needs additional argument for it */
1352
0
      if (*(args[cur_arg+1]) == 0) {
1353
0
        ha_alert("parsing [%s:%d] : '%s %s %s' expects <header_name> as argument.\n",
1354
0
           file, linenum, args[0], args[1], args[cur_arg]);
1355
0
        err_code |= ERR_ALERT | ERR_FATAL;
1356
0
        goto out;
1357
0
      }
1358
0
      istfree(&xot->hdr_name);
1359
0
      xot->hdr_name = istdup(ist(args[cur_arg+1]));
1360
0
      if (!isttest(xot->hdr_name))
1361
0
        return proxy_http_parse_oom(file, linenum);
1362
0
      cur_arg += 2;
1363
0
    } else {
1364
      /* unknown suboption - catchall */
1365
0
      ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'except' and 'header'.\n",
1366
0
         file, linenum, args[0], args[1]);
1367
0
      err_code |= ERR_ALERT | ERR_FATAL;
1368
0
      goto out;
1369
0
    }
1370
0
  } /* end while loop */
1371
1372
0
 out:
1373
0
  return err_code;
1374
0
}
1375
1376
/*
1377
 * =========== MGMT ===========
1378
 * below are helpers to manage http ext options
1379
 */
1380
1381
/* Ensure http_ext->fwd is properly allocated and
1382
 * initialized for <curproxy>.
1383
 * The function will leverage http_ext_prepare() to make
1384
 * sure http_ext is properly allocated and initialized as well.
1385
 * Returns 1 for success and 0 for failure (memory error)
1386
 */
1387
int http_ext_7239_prepare(struct proxy *curproxy)
1388
0
{
1389
0
  struct http_ext_7239 *fwd;
1390
1391
0
  if (!http_ext_prepare(curproxy))
1392
0
    return 0;
1393
0
  if (curproxy->http_ext->fwd)
1394
0
    return 1; /* nothing to do */
1395
1396
0
  fwd = malloc(sizeof(*fwd));
1397
0
  if (!fwd)
1398
0
    return 0;
1399
  /* initialize fwd mandatory fields */
1400
0
  fwd->c_mode = 0; /* pre-compile (parse) time */
1401
0
  fwd->c_file = NULL;
1402
0
  fwd->p_host.expr_s = NULL;
1403
0
  fwd->p_by.nn_expr_s = NULL;
1404
0
  fwd->p_by.np_expr_s = NULL;
1405
0
  fwd->p_for.nn_expr_s = NULL;
1406
0
  fwd->p_for.np_expr_s = NULL;
1407
  /* assign */
1408
0
  curproxy->http_ext->fwd = fwd;
1409
0
  return 1;
1410
0
}
1411
1412
/* Ensure http_ext->xff is properly allocated and
1413
 * initialized for <curproxy>.
1414
 * The function will leverage http_ext_prepare() to make
1415
 * sure http_ext is properly allocated and initialized as well.
1416
 * Returns 1 for success and 0 for failure (memory error)
1417
 */
1418
int http_ext_xff_prepare(struct proxy *curproxy)
1419
0
{
1420
0
  struct http_ext_xff *xff;
1421
1422
0
  if (!http_ext_prepare(curproxy))
1423
0
    return 0;
1424
0
  if (curproxy->http_ext->xff)
1425
0
    return 1; /* nothing to do */
1426
1427
0
  xff = malloc(sizeof(*xff));
1428
0
  if (!xff)
1429
0
    return 0;
1430
  /* initialize xff mandatory fields */
1431
0
  xff->hdr_name = IST_NULL;
1432
  /* assign */
1433
0
  curproxy->http_ext->xff = xff;
1434
0
  return 1;
1435
0
}
1436
1437
/* Ensure http_ext->xot is properly allocated and
1438
 * initialized for <curproxy>.
1439
 * The function will leverage http_ext_prepare() to make
1440
 * sure http_ext is properly allocated and initialized as well.
1441
 * Returns 1 for success and 0 for failure (memory error)
1442
 */
1443
int http_ext_xot_prepare(struct proxy *curproxy)
1444
0
{
1445
0
  struct http_ext_xot *xot;
1446
1447
0
  if (!http_ext_prepare(curproxy))
1448
0
    return 0;
1449
0
  if (curproxy->http_ext->xot)
1450
0
    return 1; /* nothing to do */
1451
1452
0
  xot = malloc(sizeof(*xot));
1453
0
  if (!xot)
1454
0
    return 0;
1455
  /* initialize xot mandatory fields */
1456
0
  xot->hdr_name = IST_NULL;
1457
  /* assign */
1458
0
  curproxy->http_ext->xot = xot;
1459
0
  return 1;
1460
0
}
1461
1462
/* deep clean http_ext->fwd parameter for <curproxy>
1463
 * http_ext->fwd will be freed
1464
 * clean behavior will differ depending on http_ext->fwd
1465
 * state. If fwd is in 'parsed' state, parsing hints will be
1466
 * cleaned. Else, it means fwd is in 'compiled' state, in this
1467
 * case we're cleaning compiled results.
1468
 * This is because parse and compile memory areas are shared in
1469
 * a single union to optimize struct http_ext_7239 size.
1470
 */
1471
void http_ext_7239_clean(struct proxy *curproxy)
1472
0
{
1473
0
  struct http_ext_7239 *clean;
1474
1475
0
  if (!curproxy->http_ext)
1476
0
    return;
1477
0
  clean = curproxy->http_ext->fwd;
1478
0
  if (!clean)
1479
0
    return; /* nothing to do */
1480
0
  if (!clean->c_mode) {
1481
    /* parsed */
1482
0
    ha_free(&clean->c_file);
1483
0
    ha_free(&clean->p_host.expr_s);
1484
0
    ha_free(&clean->p_by.nn_expr_s);
1485
0
    ha_free(&clean->p_by.np_expr_s);
1486
0
    ha_free(&clean->p_for.nn_expr_s);
1487
0
    ha_free(&clean->p_for.np_expr_s);
1488
0
  }
1489
0
  else {
1490
    /* compiled */
1491
0
    release_sample_expr(clean->p_host.expr);
1492
0
    clean->p_host.expr = NULL;
1493
0
    release_sample_expr(clean->p_by.nn_expr);
1494
0
    clean->p_by.nn_expr = NULL;
1495
0
    release_sample_expr(clean->p_by.np_expr);
1496
0
    clean->p_by.np_expr = NULL;
1497
0
    release_sample_expr(clean->p_for.nn_expr);
1498
0
    clean->p_for.nn_expr = NULL;
1499
0
    release_sample_expr(clean->p_for.np_expr);
1500
0
    clean->p_for.np_expr = NULL;
1501
0
  }
1502
  /* free fwd */
1503
0
  ha_free(&curproxy->http_ext->fwd);
1504
0
}
1505
1506
/* deep clean http_ext->xff parameter for <curproxy>
1507
 * http_ext->xff will be freed
1508
 */
1509
void http_ext_xff_clean(struct proxy *curproxy)
1510
0
{
1511
0
  struct http_ext_xff *clean;
1512
1513
0
  if (!curproxy->http_ext)
1514
0
    return;
1515
0
  clean = curproxy->http_ext->xff;
1516
0
  if (!clean)
1517
0
    return; /* nothing to do */
1518
0
  istfree(&clean->hdr_name);
1519
  /* free xff */
1520
0
  ha_free(&curproxy->http_ext->xff);
1521
0
}
1522
1523
/* deep clean http_ext->xot parameter for <curproxy>
1524
 * http_ext->xot will be freed
1525
 */
1526
void http_ext_xot_clean(struct proxy *curproxy)
1527
0
{
1528
0
  struct http_ext_xot *clean;
1529
1530
0
  if (!curproxy->http_ext)
1531
0
    return;
1532
0
  clean = curproxy->http_ext->xot;
1533
0
  if (!clean)
1534
0
    return; /* nothing to do */
1535
0
  istfree(&clean->hdr_name);
1536
  /* free xot */
1537
0
  ha_free(&curproxy->http_ext->xot);
1538
0
}
1539
1540
/* duplicate http_ext->fwd parameters from <def> to <cpy>
1541
 * performs the required memory allocation and initialization
1542
 */
1543
void http_ext_7239_dup(const struct proxy *def, struct proxy *cpy)
1544
0
{
1545
0
  struct http_ext_7239 *dest = NULL;
1546
0
  struct http_ext_7239 *orig = NULL;
1547
1548
  /* feature requires backend cap */
1549
0
  if (!(cpy->cap & PR_CAP_BE))
1550
0
    return;
1551
1552
0
  if (def->http_ext == NULL || def->http_ext->fwd == NULL)
1553
0
    return;
1554
1555
0
  orig = def->http_ext->fwd;
1556
1557
0
  if (orig->c_mode)
1558
0
    return; /* copy not supported once compiled */
1559
1560
0
  if (!http_ext_7239_prepare(cpy))
1561
0
    return;
1562
1563
0
  dest = cpy->http_ext->fwd;
1564
1565
0
  if (orig->c_file)
1566
0
    dest->c_file = strdup(orig->c_file);
1567
0
  dest->c_line = orig->c_line;
1568
  /* proto */
1569
0
  dest->p_proto = orig->p_proto;
1570
  /* host */
1571
0
  dest->p_host.mode = orig->p_host.mode;
1572
0
  if (orig->p_host.expr_s)
1573
0
    dest->p_host.expr_s = strdup(orig->p_host.expr_s);
1574
  /* by - nodename */
1575
0
  dest->p_by.nn_mode = orig->p_by.nn_mode;
1576
0
  if (orig->p_by.nn_expr_s)
1577
0
    dest->p_by.nn_expr_s = strdup(orig->p_by.nn_expr_s);
1578
  /* by - nodeport */
1579
0
  dest->p_by.np_mode = orig->p_by.np_mode;
1580
0
  if (orig->p_by.np_expr_s)
1581
0
    dest->p_by.np_expr_s = strdup(orig->p_by.np_expr_s);
1582
  /* for - nodename */
1583
0
  dest->p_for.nn_mode = orig->p_for.nn_mode;
1584
0
  if (orig->p_for.nn_expr_s)
1585
0
    dest->p_for.nn_expr_s = strdup(orig->p_for.nn_expr_s);
1586
  /* for - nodeport */
1587
0
  dest->p_for.np_mode = orig->p_for.np_mode;
1588
0
  if (orig->p_for.np_expr_s)
1589
0
    dest->p_for.np_expr_s = strdup(orig->p_for.np_expr_s);
1590
0
}
1591
1592
/* duplicate http_ext->xff parameters from <def> to <cpy>
1593
 * performs the required memory allocation and initialization
1594
 */
1595
void http_ext_xff_dup(const struct proxy *def, struct proxy *cpy)
1596
0
{
1597
0
  struct http_ext_xff *dest = NULL;
1598
0
  struct http_ext_xff *orig = NULL;
1599
1600
0
  if (def->http_ext == NULL || def->http_ext->xff == NULL ||
1601
0
      !http_ext_xff_prepare(cpy))
1602
0
    return;
1603
1604
0
  orig = def->http_ext->xff;
1605
0
  dest = cpy->http_ext->xff;
1606
1607
0
  if (isttest(orig->hdr_name))
1608
0
    dest->hdr_name = istdup(orig->hdr_name);
1609
0
  dest->mode = orig->mode;
1610
0
  dest->except_net = orig->except_net;
1611
0
}
1612
1613
/* duplicate http_ext->xot parameters from <def> to <cpy>
1614
 * performs the required memory allocation and initialization
1615
 */
1616
void http_ext_xot_dup(const struct proxy *def, struct proxy *cpy)
1617
0
{
1618
0
  struct http_ext_xot *dest = NULL;
1619
0
  struct http_ext_xot *orig = NULL;
1620
1621
0
  if (def->http_ext == NULL || def->http_ext->xot == NULL ||
1622
0
      !http_ext_xot_prepare(cpy))
1623
0
    return;
1624
1625
0
  orig = def->http_ext->xot;
1626
0
  dest = cpy->http_ext->xot;
1627
1628
0
  if (isttest(orig->hdr_name))
1629
0
    dest->hdr_name = istdup(orig->hdr_name);
1630
0
  dest->except_net = orig->except_net;
1631
0
}
1632
1633
/* Allocate new http_ext and initialize it
1634
 * if needed
1635
 * Returns 1 for success and 0 for failure
1636
 */
1637
int http_ext_prepare(struct proxy *curproxy)
1638
0
{
1639
0
  if (curproxy->http_ext)
1640
0
    return 1; /* nothing to do */
1641
1642
0
  curproxy->http_ext = malloc(sizeof(*curproxy->http_ext));
1643
0
  if (!curproxy->http_ext)
1644
0
    return 0; /* failure */
1645
  /* first init, set supported ext to NULL */
1646
0
  curproxy->http_ext->fwd = NULL;
1647
0
  curproxy->http_ext->xff = NULL;
1648
0
  curproxy->http_ext->xot = NULL;
1649
0
  return 1;
1650
0
}
1651
1652
/* duplicate existing http_ext from <defproxy> to <curproxy>
1653
 */
1654
void http_ext_dup(const struct proxy *defproxy, struct proxy *curproxy)
1655
0
{
1656
  /* copy defproxy.http_ext members */
1657
0
  http_ext_7239_dup(defproxy, curproxy);
1658
0
  http_ext_xff_dup(defproxy, curproxy);
1659
0
  http_ext_xot_dup(defproxy, curproxy);
1660
0
}
1661
1662
/* deep clean http_ext for <curproxy> (if previously allocated)
1663
 */
1664
void http_ext_clean(struct proxy *curproxy)
1665
0
{
1666
0
  if (!curproxy->http_ext)
1667
0
    return; /* nothing to do */
1668
  /* first, free supported ext */
1669
0
  http_ext_7239_clean(curproxy);
1670
0
  http_ext_xff_clean(curproxy);
1671
0
  http_ext_xot_clean(curproxy);
1672
1673
  /* then, free http_ext */
1674
0
  ha_free(&curproxy->http_ext);
1675
0
}
1676
1677
/* soft clean (only clean http_ext if no more options are used) */
1678
void http_ext_softclean(struct proxy *curproxy)
1679
0
{
1680
0
  if (!curproxy->http_ext)
1681
0
    return; /* nothing to do */
1682
0
  if (!curproxy->http_ext->fwd &&
1683
0
      !curproxy->http_ext->xff &&
1684
0
      !curproxy->http_ext->xot) {
1685
    /* no more use for http_ext, all options are disabled */
1686
0
    http_ext_clean(curproxy);
1687
0
  }
1688
0
}
1689
1690
/* Perform some consistency checks on px.http_ext after parsing
1691
 * is completed.
1692
 * We make sure to perform a softclean in case some options were
1693
 * to be disabled in this check. This way we can release some memory.
1694
 * Returns a composition of ERR_NONE, ERR_ALERT, ERR_FATAL, ERR_WARN
1695
 */
1696
0
static int check_http_ext_postconf(struct proxy *px) {
1697
0
  int err = ERR_NONE;
1698
1699
0
  if (px->http_ext) {
1700
    /* consistency check for http_ext */
1701
0
    if (px->mode != PR_MODE_HTTP && !(px->options & PR_O_HTTP_UPG)) {
1702
      /* http is disabled on px, yet it is required by http_ext */
1703
0
      if (px->http_ext->fwd) {
1704
0
        ha_warning("'option %s' ignored for %s '%s' as it requires HTTP mode.\n",
1705
0
             "forwarded", proxy_type_str(px), px->id);
1706
0
        err |= ERR_WARN;
1707
0
        http_ext_7239_clean(px);
1708
0
      }
1709
0
      if (px->http_ext->xff) {
1710
0
        ha_warning("'option %s' ignored for %s '%s' as it requires HTTP mode.\n",
1711
0
             "forwardfor", proxy_type_str(px), px->id);
1712
0
        err |= ERR_WARN;
1713
0
        http_ext_xff_clean(px);
1714
0
      }
1715
0
      if (px->http_ext->xot) {
1716
0
        ha_warning("'option %s' ignored for %s '%s' as it requires HTTP mode.\n",
1717
0
             "originalto", proxy_type_str(px), px->id);
1718
0
        err |= ERR_WARN;
1719
0
        http_ext_xot_clean(px);
1720
0
      }
1721
0
    } else if (px->http_ext->fwd) {
1722
      /* option "forwarded" may need to compile its expressions */
1723
0
      err |= proxy_http_compile_7239(px);
1724
0
    }
1725
    /* http_ext post init early cleanup */
1726
0
    http_ext_softclean(px);
1727
1728
0
  }
1729
0
  return err;
1730
0
}
1731
1732
REGISTER_POST_PROXY_CHECK(check_http_ext_postconf);
1733
/*
1734
 * =========== CONV ===========
1735
 * related converters
1736
 */
1737
1738
/* input: string representing 7239 forwarded header single value
1739
 * does not take arguments
1740
 * output: 1 if header is RFC compliant, 0 otherwise
1741
 */
1742
static int sample_conv_7239_valid(const struct arg *args, struct sample *smp, void *private)
1743
0
{
1744
0
  struct ist input = ist2(smp->data.u.str.area, smp->data.u.str.data);
1745
1746
0
  smp->data.type = SMP_T_BOOL;
1747
0
  smp->data.u.sint = !!http_validate_7239_header(input, FORWARDED_HEADER_ALL, NULL);
1748
0
  return 1;
1749
0
}
1750
1751
/* input: string representing 7239 forwarded header single value
1752
 * argument: parameter name to look for in the header
1753
 * output: header parameter raw value, as a string
1754
 */
1755
static int sample_conv_7239_field(const struct arg *args, struct sample *smp, void *private)
1756
0
{
1757
0
  struct ist input = ist2(smp->data.u.str.area, smp->data.u.str.data);
1758
0
  struct buffer *output;
1759
0
  struct forwarded_header_ctx ctx;
1760
0
  int validate;
1761
0
  int field = 0;
1762
1763
0
  if (strcmp(args->data.str.area, "proto") == 0)
1764
0
    field = FORWARDED_HEADER_PROTO;
1765
0
  else if (strcmp(args->data.str.area, "host") == 0)
1766
0
    field = FORWARDED_HEADER_HOST;
1767
0
  else if (strcmp(args->data.str.area, "for") == 0)
1768
0
    field = FORWARDED_HEADER_FOR;
1769
0
  else if (strcmp(args->data.str.area, "by") == 0)
1770
0
    field = FORWARDED_HEADER_BY;
1771
1772
0
  validate = http_validate_7239_header(input, FORWARDED_HEADER_ALL, &ctx);
1773
0
  if (!(validate & field))
1774
0
    return 0; /* invalid header or header does not contain field */
1775
0
  output = get_trash_chunk();
1776
0
  switch (field) {
1777
0
    case FORWARDED_HEADER_PROTO:
1778
0
      if (ctx.proto == FORWARDED_HEADER_HTTP)
1779
0
        chunk_appendf(output, "http");
1780
0
      else if (ctx.proto == FORWARDED_HEADER_HTTPS)
1781
0
        chunk_appendf(output, "https");
1782
0
      break;
1783
0
    case FORWARDED_HEADER_HOST:
1784
0
      chunk_istcat(output, ctx.host);
1785
0
      break;
1786
0
    case FORWARDED_HEADER_FOR:
1787
0
      chunk_istcat(output, ctx.nfor.raw);
1788
0
      break;
1789
0
    case FORWARDED_HEADER_BY:
1790
0
      chunk_istcat(output, ctx.nby.raw);
1791
0
      break;
1792
0
    default:
1793
0
      break;
1794
0
  }
1795
0
  smp->flags &= ~SMP_F_CONST;
1796
0
  smp->data.type = SMP_T_STR;
1797
0
  smp->data.u.str = *output;
1798
0
  return 1;
1799
0
}
1800
1801
/* input: substring representing 7239 forwarded header node
1802
 * output: forwarded header nodename translated to either
1803
 * ipv4 address, ipv6 address or str
1804
 * ('_' prefix if obfuscated, or "unknown" if unknown)
1805
 */
1806
static int sample_conv_7239_n2nn(const struct arg *args, struct sample *smp, void *private)
1807
0
{
1808
0
  struct ist input = ist2(smp->data.u.str.area, smp->data.u.str.data);
1809
0
  struct forwarded_header_node ctx;
1810
0
  struct buffer *output;
1811
1812
0
  if (http_7239_extract_node(&input, &ctx, 1) == 0)
1813
0
    return 0; /* could not extract node */
1814
0
  switch (ctx.nodename.type) {
1815
0
    case FORWARDED_HEADER_UNK:
1816
0
      output = get_trash_chunk();
1817
0
      chunk_appendf(output, "unknown");
1818
0
      smp->flags &= ~SMP_F_CONST;
1819
0
      smp->data.type = SMP_T_STR;
1820
0
      smp->data.u.str = *output;
1821
0
      break;
1822
0
    case FORWARDED_HEADER_OBFS:
1823
0
      output = get_trash_chunk();
1824
0
      chunk_appendf(output, "_"); /* append obfs prefix */
1825
0
      chunk_istcat(output, ctx.nodename.obfs);
1826
0
      smp->flags &= ~SMP_F_CONST;
1827
0
      smp->data.type = SMP_T_STR;
1828
0
      smp->data.u.str = *output;
1829
0
      break;
1830
0
    case FORWARDED_HEADER_IP:
1831
0
      if (ctx.nodename.ip.ss_family == AF_INET) {
1832
0
        smp->data.type = SMP_T_IPV4;
1833
0
        smp->data.u.ipv4 = ((struct sockaddr_in *)&ctx.nodename.ip)->sin_addr;
1834
0
      }
1835
0
      else if (ctx.nodename.ip.ss_family == AF_INET6) {
1836
0
        smp->data.type = SMP_T_IPV6;
1837
0
        smp->data.u.ipv6 = ((struct sockaddr_in6 *)&ctx.nodename.ip)->sin6_addr;
1838
0
      }
1839
0
      else
1840
0
        return 0; /* unsupported */
1841
0
      break;
1842
0
    default:
1843
0
      return 0; /* unsupported */
1844
0
  }
1845
0
  return 1;
1846
0
}
1847
1848
/* input: substring representing 7239 forwarded header node
1849
 * output: forwarded header nodeport translated to either
1850
 * integer or str for obfuscated ('_' prefix)
1851
 */
1852
static int sample_conv_7239_n2np(const struct arg *args, struct sample *smp, void *private)
1853
0
{
1854
0
  struct ist input = ist2(smp->data.u.str.area, smp->data.u.str.data);
1855
0
  struct forwarded_header_node ctx;
1856
0
  struct buffer *output;
1857
1858
0
  if (http_7239_extract_node(&input, &ctx, 1) == 0)
1859
0
    return 0; /* could not extract node */
1860
1861
0
  switch (ctx.nodeport.type) {
1862
0
    case FORWARDED_HEADER_UNK:
1863
0
      return 0; /* not provided */
1864
0
    case FORWARDED_HEADER_OBFS:
1865
0
      output = get_trash_chunk();
1866
0
      chunk_appendf(output, "_"); /* append obfs prefix */
1867
0
      chunk_istcat(output, ctx.nodeport.obfs);
1868
0
      smp->flags &= ~SMP_F_CONST;
1869
0
      smp->data.type = SMP_T_STR;
1870
0
      smp->data.u.str = *output;
1871
0
      break;
1872
0
    case FORWARDED_HEADER_PORT:
1873
0
      smp->data.type = SMP_T_SINT;
1874
0
      smp->data.u.sint = ctx.nodeport.port;
1875
0
      break;
1876
0
    default:
1877
0
      return 0; /* unsupported */
1878
0
  }
1879
1880
0
  return 1;
1881
0
}
1882
1883
/*
1884
 * input: ipv4 address, ipv6 address or str (empty string will result in
1885
 * "unknown" identifier, else string will be translated to _obfs
1886
 * identifier, prefixed by '_'. Must comply with RFC7239 charset)
1887
 *
1888
 * output: rfc7239-compliant forwarded header nodename
1889
 */
1890
static int sample_conv_7239_nn(const struct arg *args, struct sample *smp, void *private)
1891
0
{
1892
0
  struct buffer *trash = get_trash_chunk();
1893
1894
0
  switch (smp->data.type) {
1895
0
    case SMP_T_IPV4:
1896
0
    {
1897
0
      unsigned char *pn = (unsigned char *)&(smp->data.u.ipv4);
1898
1899
0
      chunk_printf(trash, "%d.%d.%d.%d", pn[0], pn[1], pn[2], pn[3]);
1900
0
      break;
1901
0
    }
1902
0
    case SMP_T_IPV6:
1903
0
      _7239_print_ip6(trash, &smp->data.u.ipv6, 1);
1904
0
      break;
1905
0
    case SMP_T_STR:
1906
0
 case_str:
1907
0
    {
1908
0
      struct ist validate_n = ist2(smp->data.u.str.area, smp->data.u.str.data);
1909
1910
0
      if (!istlen(validate_n)) {
1911
        // empty -> unknown
1912
0
        chunk_printf(trash, "unknown");
1913
0
        break;
1914
0
      }
1915
1916
0
      if (!(http_7239_extract_obfs(&validate_n, NULL) && !istlen(validate_n)))
1917
0
        return 0; /* invalid input */
1918
      // output with '_' prefix
1919
0
      chunk_printf(trash, "_%.*s", (int)smp->data.u.str.data, smp->data.u.str.area);
1920
0
      break;
1921
0
    }
1922
0
    default:
1923
0
    {
1924
0
      if (sample_casts[smp->data.type][SMP_T_STR] &&
1925
0
                            sample_casts[smp->data.type][SMP_T_STR](smp))
1926
0
        goto case_str;
1927
0
      return 0; /* unexpected */
1928
0
    }
1929
1930
0
  }
1931
1932
0
  smp->data.u.str = *trash;
1933
0
  smp->data.type = SMP_T_STR;
1934
0
  smp->flags &= ~SMP_F_CONST;
1935
1936
0
  return 1;
1937
0
}
1938
1939
/*
1940
 * input: unsigned integer or str (string will be translated to _obfs
1941
 * identifier, prefixed by '_'. Must comply with RFC7239 charset)
1942
 *
1943
 * output: rfc7239-compliant forwarded header nodeport
1944
 */
1945
static int sample_conv_7239_np(const struct arg *args, struct sample *smp, void *private)
1946
0
{
1947
0
  struct buffer *trash = get_trash_chunk();
1948
1949
0
  switch (smp->data.type) {
1950
0
    case SMP_T_SINT:
1951
0
    {
1952
0
      chunk_printf(trash, "%u", (unsigned int)smp->data.u.sint);
1953
0
      break;
1954
0
    }
1955
0
    case SMP_T_STR:
1956
0
 case_str:
1957
0
    {
1958
0
      struct ist validate_n = ist2(smp->data.u.str.area, smp->data.u.str.data);
1959
1960
0
      if (!istlen(validate_n))
1961
0
        return 0;
1962
1963
0
      if (!(http_7239_extract_obfs(&validate_n, NULL) && !istlen(validate_n)))
1964
0
        return 0; /* invalid input */
1965
      // output with '_' prefix
1966
0
      chunk_printf(trash, "_%.*s", (int)smp->data.u.str.data, smp->data.u.str.area);
1967
0
      break;
1968
0
    }
1969
0
    default:
1970
0
    {
1971
0
      if (sample_casts[smp->data.type][SMP_T_STR] &&
1972
0
                            sample_casts[smp->data.type][SMP_T_STR](smp))
1973
0
        goto case_str;
1974
0
      return 0; /* unexpected */
1975
0
    }
1976
1977
0
  }
1978
1979
0
  smp->data.u.str = *trash;
1980
0
  smp->data.type = SMP_T_STR;
1981
0
  smp->flags &= ~SMP_F_CONST;
1982
1983
0
  return 1;
1984
1985
0
}
1986
1987
/* Note: must not be declared <const> as its list will be overwritten */
1988
static struct sample_conv_kw_list sample_conv_kws = {ILH, {
1989
  { "rfc7239_is_valid",  sample_conv_7239_valid,   0,                NULL,   SMP_T_STR,  SMP_T_BOOL},
1990
  { "rfc7239_field",     sample_conv_7239_field,   ARG1(1,STR),      NULL,   SMP_T_STR,  SMP_T_STR},
1991
  { "rfc7239_n2nn",      sample_conv_7239_n2nn,    0,                NULL,   SMP_T_STR,  SMP_T_ANY},
1992
  { "rfc7239_n2np",      sample_conv_7239_n2np,    0,                NULL,   SMP_T_STR,  SMP_T_ANY},
1993
  { "rfc7239_nn",        sample_conv_7239_nn,      0,                NULL,   SMP_T_ANY,  SMP_T_STR},
1994
  { "rfc7239_np",        sample_conv_7239_np,      0,                NULL,   SMP_T_ANY,  SMP_T_STR},
1995
  { NULL, NULL, 0, 0, 0 },
1996
}};
1997
1998
INITCALL1(STG_REGISTER, sample_register_convs, &sample_conv_kws);