| /* |
| * HTTP extensions logic and helpers |
| * |
| * Copyright 2022 HAProxy Technologies |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public License |
| * as published by the Free Software Foundation; either version |
| * 2.1 of the License, or (at your option) any later version. |
| * |
| */ |
| |
| /* forwarded header (7239 RFC) */ |
| |
| #include <haproxy/sample.h> |
| #include <haproxy/http_htx.h> |
| #include <haproxy/http_ext.h> |
| #include <haproxy/chunk.h> |
| #include <haproxy/stream.h> |
| #include <haproxy/proxy.h> |
| #include <haproxy/sc_strm.h> |
| #include <haproxy/obj_type.h> |
| #include <haproxy/cfgparse.h> |
| #include <haproxy/arg.h> |
| #include <haproxy/initcall.h> |
| #include <haproxy/tools.h> |
| |
| /* check if char is a valid obfuscated identifier char |
| * (according to 7239 RFC) |
| * Returns non zero value for valid char |
| */ |
| static int http_7239_valid_obfsc(char c) |
| { |
| return (isalnum((unsigned char)c) || |
| (c == '.' || c == '-' || c == '_')); |
| } |
| |
| /* |
| * =========== ANALYZE =========== |
| * below are http process/ana helpers |
| */ |
| |
| /* checks if <input> contains rfc7239 compliant port |
| * Returns 1 for success and 0 for failure |
| * if <port> is not NULL, it will be set to the extracted value contained |
| * in <input> |
| * <input> will be consumed accordingly (parsed/extracted characters are |
| * removed from <input>) |
| */ |
| static inline int http_7239_extract_port(struct ist *input, uint16_t *port) |
| { |
| char *start = istptr(*input); |
| uint32_t port_cast = 0; |
| int it = 0; |
| |
| /* strtol does not support non-null terminated str, |
| * we extract port ourselves |
| */ |
| while (it < istlen(*input) && |
| isdigit((unsigned char)start[it])) { |
| port_cast = (port_cast * 10) + (start[it] - '0'); |
| if (port_cast > 65535) |
| return 0; /* invalid port */ |
| it += 1; |
| } |
| if (!port_cast) |
| return 0; /* invalid port */ |
| /* ok */ |
| if (port) |
| *port = (uint16_t)port_cast; |
| *input = istadv(*input, it); |
| return 1; |
| } |
| |
| /* checks if <input> contains rfc7239 compliant obfuscated identifier |
| * Returns 1 for success and 0 for failure |
| * if <obfs> is not NULL, it will be set to the extracted value contained |
| * in <input> |
| * <input> will be consumed accordingly (parsed/extracted characters are |
| * removed from <input>) |
| */ |
| static inline int http_7239_extract_obfs(struct ist *input, struct ist *obfs) |
| { |
| int it = 0; |
| |
| if (obfs) |
| obfs->ptr = input->ptr; |
| |
| while (it < istlen(*input) && istptr(*input)[it] != ';') { |
| if (!http_7239_valid_obfsc(istptr(*input)[it])) |
| break; /* end of obfs token */ |
| it += 1; |
| } |
| if (obfs) |
| obfs->len = it; |
| *input = istadv(*input, it); |
| return !!it; |
| } |
| |
| /* checks if <input> contains rfc7239 compliant IPV4 address |
| * Returns 1 for success and 0 for failure |
| * if <ip> is not NULL, it will be set to the extracted value contained |
| * in <input> |
| * <input> will be consumed accordingly (parsed/extracted characters are |
| * removed from <input>) |
| */ |
| static inline int http_7239_extract_ipv4(struct ist *input, struct in_addr *ip) |
| { |
| char ip4[INET_ADDRSTRLEN]; |
| unsigned char buf[sizeof(struct in_addr)]; |
| int it = 0; |
| |
| /* extract ipv4 addr */ |
| while (it < istlen(*input) && it < (sizeof(ip4) - 1)) { |
| if (!isdigit((unsigned char)istptr(*input)[it]) && |
| istptr(*input)[it] != '.') |
| break; /* no more ip4 char */ |
| ip4[it] = istptr(*input)[it]; |
| it += 1; |
| } |
| ip4[it] = 0; |
| if (inet_pton(AF_INET, ip4, buf) != 1) |
| return 0; /* invalid ip4 addr */ |
| /* ok */ |
| if (ip) |
| memcpy(ip, buf, sizeof(buf)); |
| *input = istadv(*input, it); |
| return 1; |
| } |
| |
| /* checks if <input> contains rfc7239 compliant IPV6 address |
| * assuming input.len >= 1 and first char is '[' |
| * Returns 1 for success and 0 for failure |
| * if <ip> is not NULL, it will be set to the extracted value contained |
| * in <input> |
| * <input> will be consumed accordingly (parsed/extracted characters are |
| * removed from <input>) |
| */ |
| static inline int http_7239_extract_ipv6(struct ist *input, struct in6_addr *ip) |
| { |
| char ip6[INET6_ADDRSTRLEN]; |
| unsigned char buf[sizeof(struct in6_addr)]; |
| int it = 0; |
| |
| *input = istnext(*input); /* skip '[' leading char */ |
| /* extract ipv6 addr */ |
| while (it < istlen(*input) && |
| it < (sizeof(ip6) - 1)) { |
| if (!isalnum((unsigned char)istptr(*input)[it]) && |
| istptr(*input)[it] != ':') |
| break; /* no more ip6 char */ |
| ip6[it] = istptr(*input)[it]; |
| it += 1; |
| } |
| ip6[it] = 0; |
| if ((istlen(*input)-it) < 1 || istptr(*input)[it] != ']') |
| return 0; /* missing ending "]" char */ |
| it += 1; |
| if (inet_pton(AF_INET6, ip6, buf) != 1) |
| return 0; /* invalid ip6 addr */ |
| /* ok */ |
| if (ip) |
| memcpy(ip, buf, sizeof(buf)); |
| *input = istadv(*input, it); |
| return 1; |
| } |
| |
| /* checks if <input> contains rfc7239 compliant host |
| * <quoted> is used to determine if the current input is being extracted |
| * from a quoted (non zero) or unquoted (zero) token, as the parsing rules |
| * differ wheteher the input is quoted or not according to the rfc. |
| * Returns 1 for success and 0 for failure |
| * if <host> is not NULL, it will be set to the extracted value contained |
| * in <input> |
| * <input> will be consumed accordingly (parsed/extracted characters are |
| * removed from <input>) |
| */ |
| static inline int http_7239_extract_host(struct ist *input, struct ist *host, int quoted) |
| { |
| if (istlen(*input) < 1) |
| return 0; /* invalid input */ |
| |
| if (host) |
| host->ptr = input->ptr; |
| |
| if (quoted && *istptr(*input) == '[') { |
| /* raw ipv6 address */ |
| if (!http_7239_extract_ipv6(input, NULL)) |
| return 0; /* invalid addr */ |
| } |
| else { |
| /* ipv4 or dns */ |
| while (istlen(*input)) { |
| if (!isalnum((unsigned char)*istptr(*input)) && |
| *istptr(*input) != '.') |
| break; /* end of hostname token */ |
| *input = istnext(*input); |
| } |
| } |
| if (istlen(*input) < 1 || *istptr(*input) != ':') { |
| goto out; /* no optional port provided */ |
| } |
| if (!quoted) |
| return 0; /* not supported */ |
| *input = istnext(*input); /* skip ':' */ |
| /* validate port */ |
| if (!http_7239_extract_port(input, NULL)) |
| return 0; /* invalid port */ |
| out: |
| if (host) |
| host->len = (input->ptr - host->ptr); |
| return 1; |
| } |
| |
| /* checks if <input> contains rfc7239 compliant nodename |
| * <quoted> is used to determine if the current input is being extracted |
| * from a quoted (non zero) or unquoted (zero) token, as the parsing rules |
| * differ wheteher the input is quoted or not according to the rfc. |
| * Returns 1 for success and 0 for failure |
| * if <nodename> is not NULL, it will be set to the extracted value contained |
| * in <input> |
| * <input> will be consumed accordingly (parsed/extracted characters are |
| * removed from <input>) |
| */ |
| static inline int http_7239_extract_nodename(struct ist *input, struct forwarded_header_nodename *nodename, int quoted) |
| { |
| if (istlen(*input) < 1) |
| return 0; /* invalid input */ |
| if (*istptr(*input) == '_') { |
| struct ist *obfs = NULL; |
| |
| /* obfuscated nodename */ |
| *input = istnext(*input); /* skip '_' */ |
| if (nodename) { |
| nodename->type = FORWARDED_HEADER_OBFS; |
| obfs = &nodename->obfs; |
| } |
| if (!http_7239_extract_obfs(input, obfs)) |
| return 0; /* invalid obfs */ |
| } else if (*istptr(*input) == 'u') { |
| /* "unknown" nodename? */ |
| if (istlen(*input) < 7 || |
| strncmp("unknown", istptr(*input), 7)) |
| return 0; /* syntax error */ |
| *input = istadv(*input, 7); /* skip "unknown" */ |
| if (nodename) |
| nodename->type = FORWARDED_HEADER_UNK; |
| } else if (quoted && *istptr(*input) == '[') { |
| struct in6_addr *ip6 = NULL; |
| |
| /* ipv6 address */ |
| if (nodename) { |
| struct sockaddr_in6 *addr = (void *)&nodename->ip; |
| |
| ip6 = &addr->sin6_addr; |
| addr->sin6_family = AF_INET6; |
| nodename->type = FORWARDED_HEADER_IP; |
| } |
| if (!http_7239_extract_ipv6(input, ip6)) |
| return 0; /* invalid ip6 */ |
| } else if (*istptr(*input)) { |
| struct in_addr *ip = NULL; |
| |
| /* ipv4 address */ |
| if (nodename) { |
| struct sockaddr_in *addr = (void *)&nodename->ip; |
| |
| ip = &addr->sin_addr; |
| addr->sin_family = AF_INET; |
| nodename->type = FORWARDED_HEADER_IP; |
| } |
| if (!http_7239_extract_ipv4(input, ip)) |
| return 0; /* invalid ip */ |
| } else |
| return 0; /* unexpected char */ |
| |
| /* ok */ |
| return 1; |
| } |
| |
| /* checks if <input> contains rfc7239 compliant nodeport |
| * <quoted> is used to determine if the current input is being extracted |
| * from a quoted (non zero) or unquoted (zero) token, as the parsing rules |
| * differ wheteher the input is quoted or not according to the rfc. |
| * Returns 1 for success and 0 for failure |
| * if <nodeport> is not NULL, it will be set to the extracted value contained |
| * in <input> |
| * <input> will be consumed accordingly (parsed/extracted characters are |
| * removed from <input>) |
| */ |
| static inline int http_7239_extract_nodeport(struct ist *input, struct forwarded_header_nodeport *nodeport) |
| { |
| if (*istptr(*input) == '_') { |
| struct ist *obfs = NULL; |
| |
| /* obfuscated nodeport */ |
| *input = istnext(*input); /* skip '_' */ |
| if (nodeport) { |
| nodeport->type = FORWARDED_HEADER_OBFS; |
| obfs = &nodeport->obfs; |
| } |
| if (!http_7239_extract_obfs(input, obfs)) |
| return 0; /* invalid obfs */ |
| } else { |
| uint16_t *port = NULL; |
| |
| /* normal port */ |
| if (nodeport) { |
| nodeport->type = FORWARDED_HEADER_PORT; |
| port = &nodeport->port; |
| } |
| if (!http_7239_extract_port(input, port)) |
| return 0; /* invalid port */ |
| } |
| /* ok */ |
| return 1; |
| } |
| |
| /* checks if <input> contains rfc7239 compliant node (nodename:nodeport token) |
| * <quoted> is used to determine if the current input is being extracted |
| * from a quoted (non zero) or unquoted (zero) token, as the parsing rules |
| * differ wheteher the input is quoted or not according to the rfc. |
| * Returns 1 for success and 0 for failure |
| * if <node> is not NULL, it will be set to the extracted value contained |
| * in <input> |
| * <input> will be consumed accordingly (parsed/extracted characters are |
| * removed from <input>) |
| */ |
| static inline int http_7239_extract_node(struct ist *input, struct forwarded_header_node *node, int quoted) |
| { |
| struct forwarded_header_nodename *nodename = NULL; |
| struct forwarded_header_nodeport *nodeport = NULL; |
| |
| if (node) { |
| nodename = &node->nodename; |
| nodeport = &node->nodeport; |
| node->raw.ptr = input->ptr; |
| } |
| if (!http_7239_extract_nodename(input, nodename, quoted)) |
| return 0; /* invalid nodename */ |
| if (istlen(*input) < 1 || *istptr(*input) != ':') { |
| if (node) |
| node->nodeport.type = FORWARDED_HEADER_UNK; |
| goto out; /* no optional port provided */ |
| } |
| if (!quoted) |
| return 0; /* not supported */ |
| *input = istnext(*input); |
| if (!http_7239_extract_nodeport(input, nodeport)) |
| return 0; /* invalid nodeport */ |
| out: |
| /* ok */ |
| if (node) |
| node->raw.len = input->ptr - node->raw.ptr; |
| return 1; |
| } |
| |
| static inline int _forwarded_header_save_ctx(struct forwarded_header_ctx *ctx, int current_step, int required_steps) |
| { |
| return (ctx && (current_step & required_steps)); |
| } |
| |
| static inline void _forwarded_header_quote_expected(struct ist *hdr, uint8_t *quoted) |
| { |
| if (istlen(*hdr) > 0 && *istptr(*hdr) == '"') { |
| *quoted = 1; |
| /* node is quoted, we must find corresponding |
| * ending quote at the end of the token |
| */ |
| *hdr = istnext(*hdr); /* skip quote */ |
| } |
| } |
| |
| /* checks if current header <hdr> is RFC 7239 compliant and can be "trusted". |
| * function will stop parsing as soon as every <required_steps> have |
| * been validated or error is encountered. |
| * Provide FORWARDED_HEADER_ALL for a full header validating spectrum. |
| * You may provide limited scope to perform quick searches on specific attributes |
| * If <ctx> is provided (not NULL), parsed attributes will be stored according to |
| * their types, allowing you to extract some useful information from the header. |
| * Returns 0 on failure and <validated_steps> bitfield on success. |
| */ |
| int http_validate_7239_header(struct ist hdr, int required_steps, struct forwarded_header_ctx *ctx) |
| { |
| int validated_steps = 0; |
| int current_step = 0; |
| uint8_t first = 1; |
| uint8_t quoted = 0; |
| |
| while (istlen(hdr) && (required_steps & ~validated_steps)) { |
| if (!first) { |
| if (*istptr(hdr) == ';') |
| hdr = istnext(hdr); /* skip ';' */ |
| else |
| goto not_ok; /* unexpected char */ |
| } |
| else |
| first = 0; |
| |
| if (!(validated_steps & FORWARDED_HEADER_FOR) && istlen(hdr) > 4 && |
| strncmp("for=", istptr(hdr), 4) == 0) { |
| struct forwarded_header_node *node = NULL; |
| |
| /* for parameter */ |
| current_step = FORWARDED_HEADER_FOR; |
| hdr = istadv(hdr, 4); /* skip "for=" */ |
| _forwarded_header_quote_expected(&hdr, "ed); |
| if (_forwarded_header_save_ctx(ctx, current_step, required_steps)) |
| node = &ctx->nfor; |
| /* validate node */ |
| if (!http_7239_extract_node(&hdr, node, quoted)) |
| goto not_ok; /* invalid node */ |
| } |
| else if (!(validated_steps & FORWARDED_HEADER_BY) && istlen(hdr) > 3 && |
| strncmp("by=", istptr(hdr), 3) == 0) { |
| struct forwarded_header_node *node = NULL; |
| |
| /* by parameter */ |
| current_step = FORWARDED_HEADER_BY; |
| hdr = istadv(hdr, 3); /* skip "by=" */ |
| _forwarded_header_quote_expected(&hdr, "ed); |
| if (_forwarded_header_save_ctx(ctx, current_step, required_steps)) |
| node = &ctx->nby; |
| /* validate node */ |
| if (!http_7239_extract_node(&hdr, node, quoted)) |
| goto not_ok; /* invalid node */ |
| } |
| else if (!(validated_steps & FORWARDED_HEADER_HOST) && istlen(hdr) > 5 && |
| strncmp("host=", istptr(hdr), 5) == 0) { |
| struct ist *host = NULL; |
| |
| /* host parameter */ |
| current_step = FORWARDED_HEADER_HOST; |
| hdr = istadv(hdr, 5); /* skip "host=" */ |
| _forwarded_header_quote_expected(&hdr, "ed); |
| if (_forwarded_header_save_ctx(ctx, current_step, required_steps)) |
| host = &ctx->host; |
| /* validate host */ |
| if (!http_7239_extract_host(&hdr, host, quoted)) |
| goto not_ok; /* invalid host */ |
| } |
| else if (!(validated_steps & FORWARDED_HEADER_PROTO) && istlen(hdr) > 6 && |
| strncmp("proto=", istptr(hdr), 6) == 0) { |
| /* proto parameter */ |
| current_step = FORWARDED_HEADER_PROTO; |
| hdr = istadv(hdr, 6); /* skip "proto=" */ |
| /* validate proto (only common used http|https are supported for now) */ |
| if (istlen(hdr) < 4 || strncmp("http", istptr(hdr), 4)) |
| goto not_ok; |
| hdr = istadv(hdr, 4); /* skip "http" */ |
| if (istlen(hdr) && *istptr(hdr) == 's') { |
| hdr = istnext(hdr); |
| if (_forwarded_header_save_ctx(ctx, current_step, required_steps)) |
| ctx->proto = FORWARDED_HEADER_HTTPS; |
| } else if (_forwarded_header_save_ctx(ctx, current_step, required_steps)) |
| ctx->proto = FORWARDED_HEADER_HTTP; |
| /* rfc allows for potential proto quoting, but we don't support |
| * it: it is not common usage |
| */ |
| } |
| else { |
| /* not supported |
| * rfc allows for upcoming extensions |
| * but obviously, we can't trust them |
| * as they are not yet standardized |
| */ |
| |
| goto not_ok; |
| } |
| /* quote check */ |
| if (quoted) { |
| if (istlen(hdr) < 1 || *istptr(hdr) != '"') { |
| /* matching ending quote not found */ |
| goto not_ok; |
| } |
| hdr = istnext(hdr); /* skip ending quote */ |
| quoted = 0; /* reset */ |
| } |
| validated_steps |= current_step; |
| } |
| |
| return validated_steps; |
| |
| not_ok: |
| return 0; |
| } |
| |
| static inline void http_build_7239_header_nodename(struct buffer *out, |
| struct stream *s, struct proxy *curproxy, |
| const struct sockaddr_storage *addr, |
| struct http_ext_7239_forby *forby) |
| { |
| struct in6_addr *ip6_addr; |
| |
| if (forby->nn_mode == HTTP_7239_FORBY_ORIG) { |
| if (addr && addr->ss_family == AF_INET) { |
| unsigned char *pn = (unsigned char *)&((struct sockaddr_in *)addr)->sin_addr; |
| |
| chunk_appendf(out, "%d.%d.%d.%d", pn[0], pn[1], pn[2], pn[3]); |
| } |
| else if (addr && addr->ss_family == AF_INET6) { |
| ip6_addr = &((struct sockaddr_in6 *)addr)->sin6_addr; |
| print_ip6: |
| { |
| char pn[INET6_ADDRSTRLEN]; |
| |
| inet_ntop(AF_INET6, |
| ip6_addr, |
| pn, sizeof(pn)); |
| if (!forby->np_mode) |
| chunk_appendf(out, "\""); /* explicit quoting required for ipv6 */ |
| chunk_appendf(out, "[%s]", pn); |
| } |
| } |
| /* else: not supported */ |
| } |
| else if (forby->nn_mode == HTTP_7239_FORBY_SMP && forby->nn_expr) { |
| struct sample *smp; |
| |
| smp = sample_process(curproxy, s->sess, s, |
| SMP_OPT_DIR_REQ | SMP_OPT_FINAL, forby->nn_expr, NULL); |
| |
| if (smp) { |
| if (smp->data.type == SMP_T_IPV6) { |
| /* smp is valid IP6, print with RFC compliant output */ |
| ip6_addr = &smp->data.u.ipv6; |
| goto print_ip6; |
| } |
| if (sample_casts[smp->data.type][SMP_T_STR] && |
| sample_casts[smp->data.type][SMP_T_STR](smp)) { |
| struct ist validate_n = ist2(smp->data.u.str.area, smp->data.u.str.data); |
| struct ist validate_o = ist2(smp->data.u.str.area, smp->data.u.str.data); |
| struct forwarded_header_nodename nodename; |
| |
| /* validate nodename */ |
| if (http_7239_extract_nodename(&validate_n, &nodename, 1) && |
| !istlen(validate_n)) { |
| if (nodename.type == FORWARDED_HEADER_IP && |
| nodename.ip.ss_family == AF_INET6) { |
| /* special care needed for valid ip6 nodename (quoting) */ |
| ip6_addr = &((struct sockaddr_in6 *)&nodename.ip)->sin6_addr; |
| goto print_ip6; |
| } |
| /* no special care needed, input is already rfc compliant, |
| * just print as regular non quoted string |
| */ |
| chunk_cat(out, &smp->data.u.str); |
| } |
| else if (http_7239_extract_obfs(&validate_o, NULL) && |
| !istlen(validate_o)) { |
| /* raw user input that should be printed as 7239 obfs */ |
| chunk_appendf(out, "_%.*s", (int)smp->data.u.str.data, smp->data.u.str.area); |
| } |
| /* else: not compliant */ |
| } |
| /* else: cannot be casted to str */ |
| } |
| /* else: smp error */ |
| } |
| } |
| |
| static inline void http_build_7239_header_nodeport(struct buffer *out, |
| struct stream *s, struct proxy *curproxy, |
| const struct sockaddr_storage *addr, |
| struct http_ext_7239_forby *forby) |
| { |
| if (forby->np_mode == HTTP_7239_FORBY_ORIG) { |
| if (addr && addr->ss_family == AF_INET) |
| chunk_appendf(out, "%d", ntohs(((struct sockaddr_in *)addr)->sin_port)); |
| else if (addr && addr->ss_family == AF_INET6) |
| chunk_appendf(out, "%d", ntohs(((struct sockaddr_in6 *)addr)->sin6_port)); |
| /* else: not supported */ |
| } |
| else if (forby->np_mode == HTTP_7239_FORBY_SMP && forby->np_expr) { |
| struct sample *smp; |
| |
| smp = sample_fetch_as_type(curproxy, s->sess, s, |
| SMP_OPT_DIR_REQ | SMP_OPT_FINAL, forby->np_expr, SMP_T_STR); |
| if (smp) { |
| struct ist validate_n = ist2(smp->data.u.str.area, smp->data.u.str.data); |
| struct ist validate_o = ist2(smp->data.u.str.area, smp->data.u.str.data); |
| |
| /* validate nodeport */ |
| if (http_7239_extract_nodeport(&validate_n, NULL) && |
| !istlen(validate_n)) { |
| /* no special care needed, input is already rfc compliant, |
| * just print as regular non quoted string |
| */ |
| chunk_cat(out, &smp->data.u.str); |
| } |
| else if (http_7239_extract_obfs(&validate_o, NULL) && |
| !istlen(validate_o)) { |
| /* raw user input that should be printed as 7239 obfs */ |
| chunk_appendf(out, "_%.*s", (int)smp->data.u.str.data, smp->data.u.str.area); |
| } |
| /* else: not compliant */ |
| } |
| /* else: smp error */ |
| } |
| } |
| |
| static inline void http_build_7239_header_node(struct buffer *out, |
| struct stream *s, struct proxy *curproxy, |
| const struct sockaddr_storage *addr, |
| struct http_ext_7239_forby *forby) |
| { |
| size_t offset_start; |
| size_t offset_save; |
| |
| offset_start = out->data; |
| if (forby->np_mode) |
| chunk_appendf(out, "\""); |
| offset_save = out->data; |
| http_build_7239_header_node(out, s, curproxy, addr, &curproxy->http.fwd.p_by); |
| if (offset_save == out->data) { |
| /* could not build nodename, either because some |
| * data is not available or user is providing bad input |
| */ |
| chunk_appendf(out, "unknown"); |
| } |
| if (forby->np_mode) { |
| chunk_appendf(out, ":"); |
| offset_save = out->data; |
| http_build_7239_header_nodeport(out, s, curproxy, addr, &curproxy->http.fwd.p_by); |
| if (offset_save == out->data) { |
| /* could not build nodeport, either because some data is |
| * not available or user is providing bad input |
| */ |
| out->data = offset_save - 1; |
| } |
| } |
| if (out->data != offset_start && out->area[offset_start] == '"') |
| chunk_appendf(out, "\""); /* add matching end quote */ |
| } |
| |
| static inline void http_build_7239_header_host(struct buffer *out, |
| struct stream *s, struct proxy *curproxy, |
| struct htx *htx, struct http_ext_7239_host *host) |
| { |
| struct http_hdr_ctx ctx = { .blk = NULL }; |
| char *str = NULL; |
| int str_len = 0; |
| |
| if (host->mode == HTTP_7239_HOST_ORIG && |
| http_find_header(htx, ist("host"), &ctx, 0)) { |
| str = ctx.value.ptr; |
| str_len = ctx.value.len; |
| print_host: |
| { |
| struct ist validate = ist2(str, str_len); |
| /* host check, to ensure rfc compliant output |
| * (assumming host is quoted/escaped) |
| */ |
| if (http_7239_extract_host(&validate, NULL, 1) && !istlen(validate)) |
| chunk_memcat(out, str, str_len); |
| /* else: not compliant or partially compliant */ |
| } |
| |
| } |
| else if (host->mode == HTTP_7239_HOST_SMP && host->expr) { |
| struct sample *smp; |
| |
| smp = sample_fetch_as_type(curproxy, s->sess, s, |
| SMP_OPT_DIR_REQ | SMP_OPT_FINAL, host->expr, SMP_T_STR); |
| if (smp) { |
| str = smp->data.u.str.area; |
| str_len = smp->data.u.str.data; |
| goto print_host; |
| } |
| /* else: smp error */ |
| } |
| } |
| |
| /* Tries build 7239 header according to <curproxy> parameters and <s> context |
| * It both depends on <curproxy>->http_ext->fwd for config and <s> for request |
| * context data. |
| * The function will write output to <out> buffer |
| * Returns 1 for success and 0 for error (ie: not enough space in buffer) |
| */ |
| static int http_build_7239_header(struct buffer *out, |
| struct stream *s, struct proxy *curproxy, struct htx *htx) |
| { |
| struct connection *cli_conn = objt_conn(strm_sess(s)->origin); |
| |
| if (curproxy->http.fwd.p_proto) { |
| chunk_appendf(out, "%sproto=%s", ((out->data) ? ";" : ""), |
| ((conn_is_ssl(cli_conn)) ? "https" : "http")); |
| } |
| if (curproxy->http.fwd.p_host.mode) { |
| /* always add quotes for host parameter to make output compliancy checks simpler */ |
| chunk_appendf(out, "%shost=\"", ((out->data) ? ";" : "")); |
| /* ignore return value for now, but could be useful some day */ |
| http_build_7239_header_host(out, s, curproxy, htx, |
| &curproxy->http.fwd.p_host); |
| chunk_appendf(out, "\""); |
| } |
| |
| if (curproxy->http.fwd.p_by.nn_mode) { |
| const struct sockaddr_storage *dst = sc_dst(s->scf); |
| |
| chunk_appendf(out, "%sby=", ((out->data) ? ";" : "")); |
| http_build_7239_header_node(out, s, curproxy, dst, &curproxy->http.fwd.p_by); |
| } |
| |
| if (curproxy->http.fwd.p_for.nn_mode) { |
| const struct sockaddr_storage *src = sc_src(s->scf); |
| |
| chunk_appendf(out, "%sfor=", ((out->data) ? ";" : "")); |
| http_build_7239_header_node(out, s, curproxy, src, &curproxy->http.fwd.p_for); |
| } |
| if (unlikely(out->data == out->size)) { |
| /* not enough space in buffer, error */ |
| return 0; |
| } |
| return 1; |
| } |
| |
| /* This function will try to inject 7239 forwarded header |
| * Returns 1 for success and 0 for failure |
| */ |
| int http_handle_7239_header(struct stream *s, struct channel *req) |
| { |
| struct htx *htx = htxbuf(&req->buf); |
| struct proxy *curproxy = s->be; /* ignore frontend */ |
| int validate = 1; |
| struct http_hdr_ctx find = { .blk = NULL }; |
| struct http_hdr_ctx last = { .blk = NULL}; |
| struct ist hdr = ist("forwarded"); |
| |
| BUG_ON(!(curproxy->options & PR_O_HTTP_7239)); /* should not happen */ |
| |
| /* ok, let's build forwarded header */ |
| chunk_reset(&trash); |
| if (unlikely(!http_build_7239_header(&trash, s, curproxy, htx))) |
| return 0; /* error when building header (bad user conf or memory error) */ |
| |
| /* validate existing forwarded header (including multiple values), |
| * hard stop if error is encountered |
| */ |
| while (http_find_header(htx, hdr, &find, 0)) { |
| /* validate current header chunk */ |
| if (!http_validate_7239_header(find.value, FORWARDED_HEADER_ALL, NULL)) { |
| /* at least one error, existing forwarded header not OK, add our own |
| * forwarded header, so that it can be trusted |
| */ |
| validate = 0; |
| break; |
| } |
| last = find; |
| } |
| /* no errors, append our data at the end of existing header */ |
| if (last.blk && validate) { |
| if (unlikely(!http_append_header_value(htx, &last, ist2(trash.area, trash.data)))) |
| return 0; /* htx error */ |
| } |
| else { |
| if (unlikely(!http_add_header(htx, hdr, ist2(trash.area, trash.data)))) |
| return 0; /* htx error */ |
| } |
| return 1; |
| } |
| |
| /* This function will try to inject x-forwarded-for header if |
| * configured on the frontend or the backend (or both) |
| * Returns 1 for success and 0 for failure |
| */ |
| int http_handle_xff_header(struct stream *s, struct channel *req) |
| { |
| struct session *sess = s->sess; |
| struct htx *htx = htxbuf(&req->buf); |
| const struct sockaddr_storage *src = sc_src(s->scf); |
| struct http_hdr_ctx ctx = { .blk = NULL }; |
| struct http_ext_xff *f_xff = ((sess->fe->options & PR_O_HTTP_XFF) ? &sess->fe->http.xff : NULL); |
| struct http_ext_xff *b_xff = ((s->be->options & PR_O_HTTP_XFF) ? &s->be->http.xff : NULL); |
| struct ist hdr; |
| |
| /* xff is expected to be enabled on be, or fe, or both */ |
| BUG_ON(!f_xff && !b_xff); |
| |
| hdr = ((b_xff) ? b_xff->hdr_name : f_xff->hdr_name); |
| |
| if (f_xff && f_xff->mode == HTTP_XFF_IFNONE && |
| b_xff && b_xff->mode == HTTP_XFF_IFNONE && |
| http_find_header(htx, hdr, &ctx, 0)) { |
| /* The header is set to be added only if none is present |
| * and we found it, so don't do anything. |
| */ |
| } |
| else if (src && src->ss_family == AF_INET) { |
| /* Add an X-Forwarded-For header unless the source IP is |
| * in the 'except' network range. |
| */ |
| if ((!f_xff || ipcmp2net(src, &f_xff->except_net)) && |
| (!b_xff || ipcmp2net(src, &b_xff->except_net))) { |
| unsigned char *pn = (unsigned char *)&((struct sockaddr_in *)src)->sin_addr; |
| |
| /* Note: we rely on the backend to get the header name to be used for |
| * x-forwarded-for, because the header is really meant for the backends. |
| * However, if the backend did not specify any option, we have to rely |
| * on the frontend's header name. |
| */ |
| chunk_printf(&trash, "%d.%d.%d.%d", pn[0], pn[1], pn[2], pn[3]); |
| if (unlikely(!http_add_header(htx, hdr, ist2(trash.area, trash.data)))) |
| return 0; |
| } |
| } |
| else if (src && src->ss_family == AF_INET6) { |
| /* Add an X-Forwarded-For header unless the source IP is |
| * in the 'except' network range. |
| */ |
| if ((!f_xff || ipcmp2net(src, &f_xff->except_net)) && |
| (!b_xff || ipcmp2net(src, &b_xff->except_net))) { |
| char pn[INET6_ADDRSTRLEN]; |
| |
| inet_ntop(AF_INET6, |
| (const void *)&((struct sockaddr_in6 *)(src))->sin6_addr, |
| pn, sizeof(pn)); |
| |
| /* Note: we rely on the backend to get the header name to be used for |
| * x-forwarded-for, because the header is really meant for the backends. |
| * However, if the backend did not specify any option, we have to rely |
| * on the frontend's header name. |
| */ |
| chunk_printf(&trash, "%s", pn); |
| if (unlikely(!http_add_header(htx, hdr, ist2(trash.area, trash.data)))) |
| return 0; |
| } |
| } |
| |
| return 1; |
| } |
| |
| /* This function will try to inject x-original-to header if |
| * configured on the frontend or the backend (or both) |
| * Returns 1 for success and 0 for failure |
| */ |
| int http_handle_xot_header(struct stream *s, struct channel *req) |
| { |
| struct session *sess = s->sess; |
| struct htx *htx = htxbuf(&req->buf); |
| const struct sockaddr_storage *dst = sc_dst(s->scf); |
| struct http_ext_xot *f_xot = ((sess->fe->options & PR_O_HTTP_XOT) ? &sess->fe->http.xot : NULL); |
| struct http_ext_xot *b_xot = ((s->be->options & PR_O_HTTP_XOT) ? &s->be->http.xot : NULL); |
| struct ist hdr; |
| |
| /* xot is expected to be enabled on be, or fe, or both */ |
| BUG_ON(!f_xot && !b_xot); |
| |
| hdr = ((b_xot) ? b_xot->hdr_name : f_xot->hdr_name); |
| |
| if (dst && dst->ss_family == AF_INET) { |
| /* Add an X-Original-To header unless the destination IP is |
| * in the 'except' network range. |
| */ |
| if ((!f_xot || ipcmp2net(dst, &f_xot->except_net)) && |
| (!b_xot || ipcmp2net(dst, &b_xot->except_net))) { |
| unsigned char *pn = (unsigned char *)&((struct sockaddr_in *)dst)->sin_addr; |
| |
| /* Note: we rely on the backend to get the header name to be used for |
| * x-original-to, because the header is really meant for the backends. |
| * However, if the backend did not specify any option, we have to rely |
| * on the frontend's header name. |
| */ |
| chunk_printf(&trash, "%d.%d.%d.%d", pn[0], pn[1], pn[2], pn[3]); |
| if (unlikely(!http_add_header(htx, hdr, ist2(trash.area, trash.data)))) |
| return 0; |
| } |
| } |
| else if (dst && dst->ss_family == AF_INET6) { |
| /* Add an X-Original-To header unless the source IP is |
| * in the 'except' network range. |
| */ |
| if ((!f_xot || ipcmp2net(dst, &f_xot->except_net)) && |
| (!b_xot || ipcmp2net(dst, &b_xot->except_net))) { |
| char pn[INET6_ADDRSTRLEN]; |
| |
| inet_ntop(AF_INET6, |
| (const void *)&((struct sockaddr_in6 *)dst)->sin6_addr, |
| pn, sizeof(pn)); |
| |
| /* Note: we rely on the backend to get the header name to be used for |
| * x-forwarded-for, because the header is really meant for the backends. |
| * However, if the backend did not specify any option, we have to rely |
| * on the frontend's header name. |
| */ |
| chunk_printf(&trash, "%s", pn); |
| if (unlikely(!http_add_header(htx, hdr, ist2(trash.area, trash.data)))) |
| return 0; |
| } |
| } |
| return 1; |
| } |
| |
| /* |
| * =========== CONFIG =========== |
| * below are helpers to parse http ext options from the config |
| */ |
| static int proxy_http_parse_oom(const char *file, int linenum) |
| { |
| int err_code = 0; |
| |
| ha_alert("parsing [%s:%d]: out of memory.\n", file, linenum); |
| err_code |= ERR_ALERT | ERR_ABORT; |
| return err_code; |
| } |
| |
| static inline int _proxy_http_parse_7239_expr(char **args, int *cur_arg, |
| const char *file, int linenum, |
| char **expr_s) |
| { |
| int err_code = 0; |
| |
| if (!*args[*cur_arg + 1]) { |
| ha_alert("parsing [%s:%d]: '%s' expects <expr> as argument.\n", |
| file, linenum, args[*cur_arg]); |
| err_code |= ERR_ALERT | ERR_FATAL; |
| goto out; |
| } |
| *cur_arg += 1; |
| ha_free(expr_s); |
| *expr_s = strdup(args[*cur_arg]); |
| if (!*expr_s) |
| return proxy_http_parse_oom(file, linenum); |
| *cur_arg += 1; |
| out: |
| return err_code; |
| } |
| |
| int proxy_http_parse_7239(char **args, int cur_arg, |
| struct proxy *curproxy, const struct proxy *defpx, |
| const char *file, int linenum) |
| { |
| int err_code = 0; |
| |
| if (warnifnotcap(curproxy, PR_CAP_BE, file, linenum, "option forwarded", NULL)) { |
| /* option is ignored for frontends */ |
| err_code |= ERR_WARN; |
| goto out; |
| } |
| |
| curproxy->options |= PR_O_HTTP_7239; |
| curproxy->http.fwd.p_proto = 0; |
| curproxy->http.fwd.p_host.mode = 0; |
| curproxy->http.fwd.p_for.nn_mode = 0; |
| curproxy->http.fwd.p_for.np_mode = 0; |
| curproxy->http.fwd.p_by.nn_mode = 0; |
| curproxy->http.fwd.p_by.np_mode = 0; |
| ha_free(&curproxy->http.fwd.c_file); |
| curproxy->http.fwd.c_file = strdup(file); |
| curproxy->http.fwd.c_line = linenum; |
| |
| /* start at 2, since 0+1 = "option" "forwarded" */ |
| cur_arg = 2; |
| if (!*(args[cur_arg])) { |
| /* no optional argument provided, use default settings */ |
| curproxy->http.fwd.p_for.nn_mode = HTTP_7239_FORBY_ORIG; /* enable for and mimic xff */ |
| curproxy->http.fwd.p_proto = 1; /* enable proto */ |
| goto out; |
| } |
| /* loop to go through optional arguments */ |
| while (*(args[cur_arg])) { |
| if (strcmp(args[cur_arg], "proto") == 0) { |
| curproxy->http.fwd.p_proto = 1; |
| cur_arg += 1; |
| } else if (strcmp(args[cur_arg], "host") == 0) { |
| curproxy->http.fwd.p_host.mode = HTTP_7239_HOST_ORIG; |
| cur_arg += 1; |
| } else if (strcmp(args[cur_arg], "host-expr") == 0) { |
| curproxy->http.fwd.p_host.mode = HTTP_7239_HOST_SMP; |
| err_code |= _proxy_http_parse_7239_expr(args, &cur_arg, file, linenum, |
| &curproxy->http.fwd.p_host.expr_s); |
| if (err_code & ERR_FATAL) |
| goto out; |
| } else if (strcmp(args[cur_arg], "by") == 0) { |
| curproxy->http.fwd.p_by.nn_mode = HTTP_7239_FORBY_ORIG; |
| cur_arg += 1; |
| } else if (strcmp(args[cur_arg], "by-expr") == 0) { |
| curproxy->http.fwd.p_by.nn_mode = HTTP_7239_FORBY_SMP; |
| err_code |= _proxy_http_parse_7239_expr(args, &cur_arg, file, linenum, |
| &curproxy->http.fwd.p_by.nn_expr_s); |
| if (err_code & ERR_FATAL) |
| goto out; |
| } else if (strcmp(args[cur_arg], "for") == 0) { |
| curproxy->http.fwd.p_for.nn_mode = HTTP_7239_FORBY_ORIG; |
| cur_arg += 1; |
| } else if (strcmp(args[cur_arg], "for-expr") == 0) { |
| curproxy->http.fwd.p_for.nn_mode = HTTP_7239_FORBY_SMP; |
| err_code |= _proxy_http_parse_7239_expr(args, &cur_arg, file, linenum, |
| &curproxy->http.fwd.p_for.nn_expr_s); |
| if (err_code & ERR_FATAL) |
| goto out; |
| } else if (strcmp(args[cur_arg], "by_port") == 0) { |
| curproxy->http.fwd.p_by.np_mode = HTTP_7239_FORBY_ORIG; |
| cur_arg += 1; |
| } else if (strcmp(args[cur_arg], "by_port-expr") == 0) { |
| curproxy->http.fwd.p_by.np_mode = HTTP_7239_FORBY_SMP; |
| err_code |= _proxy_http_parse_7239_expr(args, &cur_arg, file, linenum, |
| &curproxy->http.fwd.p_by.np_expr_s); |
| if (err_code & ERR_FATAL) |
| goto out; |
| } else if (strcmp(args[cur_arg], "for_port") == 0) { |
| curproxy->http.fwd.p_for.np_mode = HTTP_7239_FORBY_ORIG; |
| cur_arg += 1; |
| } else if (strcmp(args[cur_arg], "for_port-expr") == 0) { |
| curproxy->http.fwd.p_for.np_mode = HTTP_7239_FORBY_SMP; |
| err_code |= _proxy_http_parse_7239_expr(args, &cur_arg, file, linenum, |
| &curproxy->http.fwd.p_for.np_expr_s); |
| if (err_code & ERR_FATAL) |
| goto out; |
| } else { |
| /* unknown suboption - catchall */ |
| ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'proto', 'host', " |
| "'host-expr', 'by', 'by-expr', 'by_port', 'by_port-expr', " |
| "'for', 'for-expr', 'for_port' and 'for_port-expr'.\n", |
| file, linenum, args[0], args[1]); |
| err_code |= ERR_ALERT | ERR_FATAL; |
| goto out; |
| } |
| } /* end while loop */ |
| |
| /* consistency check */ |
| if (curproxy->http.fwd.p_by.np_mode && |
| !curproxy->http.fwd.p_by.nn_mode) { |
| curproxy->http.fwd.p_by.np_mode = 0; |
| ha_free(&curproxy->http.fwd.p_by.np_expr_s); |
| ha_warning("parsing [%s:%d] : '%s %s' : '%s' will be ignored because both 'by' " |
| "and 'by-expr' are unset\n", |
| file, linenum, args[0], args[1], |
| ((curproxy->http.fwd.p_by.np_mode == HTTP_7239_FORBY_ORIG) ? "by_port" : "by_port-expr")); |
| err_code |= ERR_WARN; |
| } |
| if (curproxy->http.fwd.p_for.np_mode && |
| !curproxy->http.fwd.p_for.nn_mode) { |
| curproxy->http.fwd.p_for.np_mode = 0; |
| ha_free(&curproxy->http.fwd.p_for.np_expr_s); |
| ha_warning("parsing [%s:%d] : '%s %s' : '%s' will be ignored because both 'for' " |
| "and 'for-expr' are unset\n", |
| file, linenum, args[0], args[1], |
| ((curproxy->http.fwd.p_for.np_mode == HTTP_7239_FORBY_ORIG) ? "for_port" : "for_port-expr")); |
| err_code |= ERR_WARN; |
| } |
| |
| out: |
| return err_code; |
| } |
| |
| /* returns 0 for success and positive value |
| * (equals to number of errors) in case of error |
| */ |
| int proxy_http_compile_7239(struct proxy *curproxy) |
| { |
| char *err = NULL; |
| int cfgerr = 0; |
| int loop; |
| |
| BUG_ON(!(curproxy->options & PR_O_HTTP_7239)); /* should not happen */ |
| if (!(curproxy->cap & PR_CAP_BE)) { |
| /* no backend cap: not supported (ie: frontend) |
| * Moreover, 7239 settings are only inherited from default |
| * if proxy is backend capable.. going further would result in |
| * undefined behavior */ |
| goto out; |
| } |
| |
| curproxy->conf.args.ctx = ARGC_OPT; /* option */ |
| curproxy->conf.args.file = curproxy->http.fwd.c_file; |
| curproxy->conf.args.line = curproxy->http.fwd.c_line; |
| |
| for (loop = 0; loop < 5; loop++) { |
| char *expr_str = NULL; |
| struct sample_expr **expr = NULL; |
| int smp = 0; |
| int idx = 0; |
| |
| switch (loop) { |
| case 0: |
| /* host */ |
| expr_str = curproxy->http.fwd.p_host.expr_s; |
| expr = &curproxy->http.fwd.p_host.expr; |
| smp = (curproxy->http.fwd.p_host.mode == |
| HTTP_7239_HOST_SMP); |
| break; |
| case 1: |
| /* by->node */ |
| expr_str = curproxy->http.fwd.p_by.nn_expr_s; |
| expr = &curproxy->http.fwd.p_by.nn_expr; |
| smp = (curproxy->http.fwd.p_by.nn_mode == |
| HTTP_7239_FORBY_SMP); |
| break; |
| case 2: |
| /* by->nodeport */ |
| expr_str = curproxy->http.fwd.p_by.np_expr_s; |
| expr = &curproxy->http.fwd.p_by.np_expr; |
| smp = (curproxy->http.fwd.p_by.np_mode == |
| HTTP_7239_FORBY_SMP); |
| break; |
| case 3: |
| /* for->node */ |
| expr_str = curproxy->http.fwd.p_for.nn_expr_s; |
| expr = &curproxy->http.fwd.p_for.nn_expr; |
| smp = (curproxy->http.fwd.p_for.nn_mode == |
| HTTP_7239_FORBY_SMP); |
| break; |
| case 4: |
| /* for->nodeport */ |
| expr_str = curproxy->http.fwd.p_for.np_expr_s; |
| expr = &curproxy->http.fwd.p_for.np_expr; |
| smp = (curproxy->http.fwd.p_for.np_mode == |
| HTTP_7239_FORBY_SMP); |
| break; |
| } |
| if (!smp || !expr_str) |
| continue; /* no expr */ |
| /* expr cannot be NULL past this point */ |
| ALREADY_CHECKED(expr); |
| |
| *expr = |
| sample_parse_expr((char*[]){expr_str, NULL}, &idx, |
| curproxy->http.fwd.c_file, |
| curproxy->http.fwd.c_line, |
| &err, &curproxy->conf.args, NULL); |
| |
| if (!*expr) { |
| ha_alert("%s '%s' [%s:%d]: failed to parse 'option forwarded' expression '%s' in : %s.\n", |
| proxy_type_str(curproxy), curproxy->id, |
| curproxy->http.fwd.c_file, curproxy->http.fwd.c_line, |
| expr_str, err); |
| ha_free(&err); |
| cfgerr++; |
| } |
| } |
| curproxy->conf.args.file = NULL; |
| curproxy->conf.args.line = 0; |
| |
| out: |
| return cfgerr; |
| } |
| |
| /* x-forwarded-for */ |
| int proxy_http_parse_xff(char **args, int cur_arg, |
| struct proxy *curproxy, const struct proxy *defpx, |
| const char *file, int linenum) |
| { |
| int err_code = 0; |
| |
| /* insert x-forwarded-for field, but not for the IP address listed as an except. |
| * set default options (ie: bitfield, header name, etc) |
| */ |
| |
| curproxy->options |= PR_O_HTTP_XFF; |
| |
| curproxy->http.xff.mode = HTTP_XFF_ALWAYS; |
| |
| istfree(&curproxy->http.xff.hdr_name); |
| curproxy->http.xff.hdr_name = istdup(ist(DEF_XFORWARDFOR_HDR)); |
| if (!isttest(curproxy->http.xff.hdr_name)) |
| return proxy_http_parse_oom(file, linenum); |
| curproxy->http.xff.except_net.family = AF_UNSPEC; |
| |
| /* loop to go through arguments - start at 2, since 0+1 = "option" "forwardfor" */ |
| cur_arg = 2; |
| while (*(args[cur_arg])) { |
| if (strcmp(args[cur_arg], "except") == 0) { |
| unsigned char mask; |
| int i; |
| |
| /* suboption except - needs additional argument for it */ |
| if (*(args[cur_arg+1]) && |
| str2net(args[cur_arg+1], 1, &curproxy->http.xff.except_net.addr.v4.ip, &curproxy->http.xff.except_net.addr.v4.mask)) { |
| curproxy->http.xff.except_net.family = AF_INET; |
| curproxy->http.xff.except_net.addr.v4.ip.s_addr &= curproxy->http.xff.except_net.addr.v4.mask.s_addr; |
| } |
| else if (*(args[cur_arg+1]) && |
| str62net(args[cur_arg+1], &curproxy->http.xff.except_net.addr.v6.ip, &mask)) { |
| curproxy->http.xff.except_net.family = AF_INET6; |
| len2mask6(mask, &curproxy->http.xff.except_net.addr.v6.mask); |
| for (i = 0; i < 16; i++) |
| curproxy->http.xff.except_net.addr.v6.ip.s6_addr[i] &= curproxy->http.xff.except_net.addr.v6.mask.s6_addr[i]; |
| } |
| else { |
| ha_alert("parsing [%s:%d] : '%s %s %s' expects <address>[/mask] as argument.\n", |
| file, linenum, args[0], args[1], args[cur_arg]); |
| err_code |= ERR_ALERT | ERR_FATAL; |
| goto out; |
| } |
| /* flush useless bits */ |
| cur_arg += 2; |
| } else if (strcmp(args[cur_arg], "header") == 0) { |
| /* suboption header - needs additional argument for it */ |
| if (*(args[cur_arg+1]) == 0) { |
| ha_alert("parsing [%s:%d] : '%s %s %s' expects <header_name> as argument.\n", |
| file, linenum, args[0], args[1], args[cur_arg]); |
| err_code |= ERR_ALERT | ERR_FATAL; |
| goto out; |
| } |
| istfree(&curproxy->http.xff.hdr_name); |
| curproxy->http.xff.hdr_name = istdup(ist(args[cur_arg+1])); |
| if (!isttest(curproxy->http.xff.hdr_name)) |
| return proxy_http_parse_oom(file, linenum); |
| cur_arg += 2; |
| } else if (strcmp(args[cur_arg], "if-none") == 0) { |
| curproxy->http.xff.mode = HTTP_XFF_IFNONE; |
| cur_arg += 1; |
| } else { |
| /* unknown suboption - catchall */ |
| ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'except', 'header' and 'if-none'.\n", |
| file, linenum, args[0], args[1]); |
| err_code |= ERR_ALERT | ERR_FATAL; |
| goto out; |
| } |
| } /* end while loop */ |
| out: |
| return err_code; |
| } |
| |
| /* x-original-to */ |
| int proxy_http_parse_xot(char **args, int cur_arg, |
| struct proxy *curproxy, const struct proxy *defpx, |
| const char *file, int linenum) |
| { |
| int err_code = 0; |
| |
| /* insert x-original-to field, but not for the IP address listed as an except. |
| * set default options (ie: bitfield, header name, etc) |
| */ |
| |
| curproxy->options |= PR_O_HTTP_XOT; |
| |
| istfree(&curproxy->http.xot.hdr_name); |
| curproxy->http.xot.hdr_name = istdup(ist(DEF_XORIGINALTO_HDR)); |
| if (!isttest(curproxy->http.xot.hdr_name)) |
| return proxy_http_parse_oom(file, linenum); |
| curproxy->http.xot.except_net.family = AF_UNSPEC; |
| |
| /* loop to go through arguments - start at 2, since 0+1 = "option" "originalto" */ |
| cur_arg = 2; |
| while (*(args[cur_arg])) { |
| if (strcmp(args[cur_arg], "except") == 0) { |
| unsigned char mask; |
| int i; |
| |
| /* suboption except - needs additional argument for it */ |
| if (*(args[cur_arg+1]) && |
| str2net(args[cur_arg+1], 1, &curproxy->http.xot.except_net.addr.v4.ip, &curproxy->http.xot.except_net.addr.v4.mask)) { |
| curproxy->http.xot.except_net.family = AF_INET; |
| curproxy->http.xot.except_net.addr.v4.ip.s_addr &= curproxy->http.xot.except_net.addr.v4.mask.s_addr; |
| } |
| else if (*(args[cur_arg+1]) && |
| str62net(args[cur_arg+1], &curproxy->http.xot.except_net.addr.v6.ip, &mask)) { |
| curproxy->http.xot.except_net.family = AF_INET6; |
| len2mask6(mask, &curproxy->http.xot.except_net.addr.v6.mask); |
| for (i = 0; i < 16; i++) |
| curproxy->http.xot.except_net.addr.v6.ip.s6_addr[i] &= curproxy->http.xot.except_net.addr.v6.mask.s6_addr[i]; |
| } |
| else { |
| ha_alert("parsing [%s:%d] : '%s %s %s' expects <address>[/mask] as argument.\n", |
| file, linenum, args[0], args[1], args[cur_arg]); |
| err_code |= ERR_ALERT | ERR_FATAL; |
| goto out; |
| } |
| cur_arg += 2; |
| } else if (strcmp(args[cur_arg], "header") == 0) { |
| /* suboption header - needs additional argument for it */ |
| if (*(args[cur_arg+1]) == 0) { |
| ha_alert("parsing [%s:%d] : '%s %s %s' expects <header_name> as argument.\n", |
| file, linenum, args[0], args[1], args[cur_arg]); |
| err_code |= ERR_ALERT | ERR_FATAL; |
| goto out; |
| } |
| istfree(&curproxy->http.xot.hdr_name); |
| curproxy->http.xot.hdr_name = istdup(ist(args[cur_arg+1])); |
| if (!isttest(curproxy->http.xot.hdr_name)) |
| return proxy_http_parse_oom(file, linenum); |
| cur_arg += 2; |
| } else { |
| /* unknown suboption - catchall */ |
| ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'except' and 'header'.\n", |
| file, linenum, args[0], args[1]); |
| err_code |= ERR_ALERT | ERR_FATAL; |
| goto out; |
| } |
| } /* end while loop */ |
| |
| out: |
| return err_code; |
| } |
| |
| /* |
| * =========== MGMT =========== |
| * below are helpers to manage http ext options |
| */ |
| |
| void http_ext_7239_clean(struct http_ext_7239 *clean) |
| { |
| ha_free(&clean->c_file); |
| ha_free(&clean->p_host.expr_s); |
| ha_free(&clean->p_by.nn_expr_s); |
| ha_free(&clean->p_by.np_expr_s); |
| ha_free(&clean->p_for.nn_expr_s); |
| ha_free(&clean->p_for.np_expr_s); |
| |
| release_sample_expr(clean->p_host.expr); |
| clean->p_host.expr = NULL; |
| release_sample_expr(clean->p_by.nn_expr); |
| clean->p_by.nn_expr = NULL; |
| release_sample_expr(clean->p_by.np_expr); |
| clean->p_by.np_expr = NULL; |
| release_sample_expr(clean->p_for.nn_expr); |
| clean->p_for.nn_expr = NULL; |
| release_sample_expr(clean->p_for.np_expr); |
| clean->p_for.np_expr = NULL; |
| } |
| |
| void http_ext_xff_clean(struct http_ext_xff *clean) |
| { |
| istfree(&clean->hdr_name); |
| } |
| |
| void http_ext_xot_clean(struct http_ext_xot *clean) |
| { |
| istfree(&clean->hdr_name); |
| } |
| |
| void http_ext_7239_copy(struct http_ext_7239 *dest, const struct http_ext_7239 *orig) |
| { |
| if (orig->c_file) |
| dest->c_file = strdup(orig->c_file); |
| dest->c_line = orig->c_line; |
| /* proto */ |
| dest->p_proto = orig->p_proto; |
| /* host */ |
| dest->p_host.mode = orig->p_host.mode; |
| if (orig->p_host.expr_s) |
| dest->p_host.expr_s = strdup(orig->p_host.expr_s); |
| /* by - nodename */ |
| dest->p_by.nn_mode = orig->p_by.nn_mode; |
| if (orig->p_by.nn_expr_s) |
| dest->p_by.nn_expr_s = strdup(orig->p_by.nn_expr_s); |
| /* by - nodeport */ |
| dest->p_by.np_mode = orig->p_by.np_mode; |
| if (orig->p_by.np_expr_s) |
| dest->p_by.np_expr_s = strdup(orig->p_by.np_expr_s); |
| /* for - nodename */ |
| dest->p_for.nn_mode = orig->p_for.nn_mode; |
| if (orig->p_for.nn_expr_s) |
| dest->p_for.nn_expr_s = strdup(orig->p_for.nn_expr_s); |
| /* for - nodeport */ |
| dest->p_for.np_mode = orig->p_for.np_mode; |
| if (orig->p_for.np_expr_s) |
| dest->p_for.np_expr_s = strdup(orig->p_for.np_expr_s); |
| } |
| |
| void http_ext_xff_copy(struct http_ext_xff *dest, const struct http_ext_xff *orig) |
| { |
| if (isttest(orig->hdr_name)) |
| dest->hdr_name = istdup(orig->hdr_name); |
| dest->mode = orig->mode; |
| dest->except_net = orig->except_net; |
| } |
| |
| void http_ext_xot_copy(struct http_ext_xot *dest, const struct http_ext_xot *orig) |
| { |
| if (isttest(orig->hdr_name)) |
| dest->hdr_name = istdup(orig->hdr_name); |
| dest->except_net = orig->except_net; |
| } |
| |
| /* |
| * =========== CONV =========== |
| * related converters |
| */ |
| |
| /* input: string representing 7239 forwarded header single value |
| * does not take arguments |
| * output: 1 if header is RFC compliant, 0 otherwise |
| */ |
| static int sample_conv_7239_valid(const struct arg *args, struct sample *smp, void *private) |
| { |
| struct ist input = ist2(smp->data.u.str.area, smp->data.u.str.data); |
| |
| smp->data.type = SMP_T_BOOL; |
| smp->data.u.sint = !!http_validate_7239_header(input, FORWARDED_HEADER_ALL, NULL); |
| return 1; |
| } |
| |
| /* input: string representing 7239 forwarded header single value |
| * argument: parameter name to look for in the header |
| * output: header parameter raw value, as a string |
| */ |
| static int sample_conv_7239_field(const struct arg *args, struct sample *smp, void *private) |
| { |
| struct ist input = ist2(smp->data.u.str.area, smp->data.u.str.data); |
| struct buffer *output; |
| struct forwarded_header_ctx ctx; |
| int validate; |
| int field = 0; |
| |
| if (strcmp(args->data.str.area, "proto") == 0) |
| field = FORWARDED_HEADER_PROTO; |
| else if (strcmp(args->data.str.area, "host") == 0) |
| field = FORWARDED_HEADER_HOST; |
| else if (strcmp(args->data.str.area, "for") == 0) |
| field = FORWARDED_HEADER_FOR; |
| else if (strcmp(args->data.str.area, "by") == 0) |
| field = FORWARDED_HEADER_BY; |
| |
| validate = http_validate_7239_header(input, FORWARDED_HEADER_ALL, &ctx); |
| if (!(validate & field)) |
| return 0; /* invalid header or header does not contain field */ |
| output = get_trash_chunk(); |
| switch (field) { |
| case FORWARDED_HEADER_PROTO: |
| if (ctx.proto == FORWARDED_HEADER_HTTP) |
| chunk_appendf(output, "http"); |
| else if (ctx.proto == FORWARDED_HEADER_HTTPS) |
| chunk_appendf(output, "https"); |
| break; |
| case FORWARDED_HEADER_HOST: |
| chunk_istcat(output, ctx.host); |
| break; |
| case FORWARDED_HEADER_FOR: |
| chunk_istcat(output, ctx.nfor.raw); |
| break; |
| case FORWARDED_HEADER_BY: |
| chunk_istcat(output, ctx.nby.raw); |
| break; |
| default: |
| break; |
| } |
| smp->flags &= ~SMP_F_CONST; |
| smp->data.type = SMP_T_STR; |
| smp->data.u.str = *output; |
| return 1; |
| } |
| |
| /* input: substring representing 7239 forwarded header node |
| * output: forwarded header nodename translated to either |
| * ipv4 address, ipv6 address or str |
| * ('_' prefix if obfuscated, or "unknown" if unknown) |
| */ |
| static int sample_conv_7239_n2nn(const struct arg *args, struct sample *smp, void *private) |
| { |
| struct ist input = ist2(smp->data.u.str.area, smp->data.u.str.data); |
| struct forwarded_header_node ctx; |
| struct buffer *output; |
| |
| if (http_7239_extract_node(&input, &ctx, 1) == 0) |
| return 0; /* could not extract node */ |
| switch (ctx.nodename.type) { |
| case FORWARDED_HEADER_UNK: |
| output = get_trash_chunk(); |
| chunk_appendf(output, "unknown"); |
| smp->flags &= ~SMP_F_CONST; |
| smp->data.type = SMP_T_STR; |
| smp->data.u.str = *output; |
| break; |
| case FORWARDED_HEADER_OBFS: |
| output = get_trash_chunk(); |
| chunk_appendf(output, "_"); /* append obfs prefix */ |
| chunk_istcat(output, ctx.nodename.obfs); |
| smp->flags &= ~SMP_F_CONST; |
| smp->data.type = SMP_T_STR; |
| smp->data.u.str = *output; |
| break; |
| case FORWARDED_HEADER_IP: |
| if (ctx.nodename.ip.ss_family == AF_INET) { |
| smp->data.type = SMP_T_IPV4; |
| smp->data.u.ipv4 = ((struct sockaddr_in *)&ctx.nodename.ip)->sin_addr; |
| } |
| else if (ctx.nodename.ip.ss_family == AF_INET6) { |
| smp->data.type = SMP_T_IPV6; |
| smp->data.u.ipv6 = ((struct sockaddr_in6 *)&ctx.nodename.ip)->sin6_addr; |
| } |
| else |
| return 0; /* unsupported */ |
| break; |
| default: |
| return 0; /* unsupported */ |
| } |
| return 1; |
| } |
| |
| /* Note: must not be declared <const> as its list will be overwritten */ |
| static struct sample_conv_kw_list sample_conv_kws = {ILH, { |
| { "rfc7239_is_valid", sample_conv_7239_valid, 0, NULL, SMP_T_STR, SMP_T_BOOL}, |
| { "rfc7239_field", sample_conv_7239_field, ARG1(1,STR), NULL, SMP_T_STR, SMP_T_STR}, |
| { "rfc7239_n2nn", sample_conv_7239_n2nn, 0, NULL, SMP_T_STR, SMP_T_ANY}, |
| { NULL, NULL, 0, 0, 0 }, |
| }}; |
| |
| INITCALL1(STG_REGISTER, sample_register_convs, &sample_conv_kws); |