Coverage Report

Created: 2023-11-19 07:16

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