diff options
author | Graham Leggett <minfrin@apache.org> | 2018-02-14 10:55:44 +0000 |
---|---|---|
committer | Graham Leggett <minfrin@apache.org> | 2018-02-14 10:55:44 +0000 |
commit | 397103c9e0944124798587ffb08eca7354742df8 (patch) | |
tree | 8bf13374383d3faac5039208aad9edb6b1cc915f /modules | |
parent | 602b4c56188d431cc55127bc8bc8f2ecb57d691a (diff) | |
download | httpd-397103c9e0944124798587ffb08eca7354742df8.tar.gz |
mod_remoteip: Add PROXY protocol support
trunk patch: http://svn.apache.org/r1776575
http://svn.apache.org/r1776578 (doc fix)
http://svn.apache.org/r1776624
http://svn.apache.org/r1776627 (shortened name + doc fix)
http://svn.apache.org/r1776674 (attribution moved to CHANGES)
http://svn.apache.org/r1776734
http://svn.apache.org/r1776740 (attribution updated in mod_remotip.c)
http://svn.apache.org/r1778268 (fix compiler warning)
http://svn.apache.org/r1780725 (set buckets aside)
http://svn.apache.org/r1781030 (fix strict GCC warning)
http://svn.apache.org/r1781031 (reference the filter by handle)
http://svn.apache.org/r1781701 (rework optional processing case)
http://svn.apache.org/r1788674 (final edge cases/ignore slave conns)
http://svn.apache.org/r1789800 (remove optional processing)
http://svn.apache.org/r1790169 (rename "exception" directive)
http://svn.apache.org/r1790457 (Update directive name in err message)
http://svn.apache.org/r1790691
http://svn.apache.org/r1806985
http://svn.apache.org/r1818279
2.4 convenience patch (includes CHANGES):
http://home.apache.org/~ylavic/patches/RemoteIPProxyProtocol.2.4-v3.patch
+1: druggeri, jim, minfrin
[Reverted by r1824246]
git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/branches/2.4.x@1824221 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'modules')
-rw-r--r-- | modules/metadata/mod_remoteip.c | 772 |
1 files changed, 770 insertions, 2 deletions
diff --git a/modules/metadata/mod_remoteip.c b/modules/metadata/mod_remoteip.c index 28e01df297..8604d6b95e 100644 --- a/modules/metadata/mod_remoteip.c +++ b/modules/metadata/mod_remoteip.c @@ -16,11 +16,13 @@ #include "ap_config.h" #include "ap_mmn.h" +#include "ap_listen.h" #include "httpd.h" #include "http_config.h" #include "http_connection.h" #include "http_protocol.h" #include "http_log.h" +#include "http_main.h" #include "apr_strings.h" #include "apr_lib.h" #define APR_WANT_BYTEFUNC @@ -36,6 +38,12 @@ typedef struct { void *internal; } remoteip_proxymatch_t; +typedef struct remoteip_addr_info { + struct remoteip_addr_info *next; + apr_sockaddr_t *addr; + server_rec *source; +} remoteip_addr_info; + typedef struct { /** The header to retrieve a proxy-via IP list */ const char *header_name; @@ -48,6 +56,17 @@ typedef struct { * with the most commonly encountered listed first */ apr_array_header_t *proxymatch_ip; + + remoteip_addr_info *proxy_protocol_enabled; + remoteip_addr_info *proxy_protocol_optional; + remoteip_addr_info *proxy_protocol_disabled; + + /** A flag indicating whether or not proxyprotocol + * is optoinal for this specific server + */ + int pp_optional; + + apr_pool_t *pool; } remoteip_config_t; typedef struct { @@ -59,12 +78,92 @@ typedef struct { const char *proxied_remote; } remoteip_req_t; +/* For PROXY protocol processing */ +static const char *remoteip_filter_name = "REMOTEIP_INPUT"; + +typedef struct { + char line[108]; +} proxy_v1; + +typedef union { + struct { /* for TCP/UDP over IPv4, len = 12 */ + apr_uint32_t src_addr; + apr_uint32_t dst_addr; + apr_uint16_t src_port; + apr_uint16_t dst_port; + } ip4; + struct { /* for TCP/UDP over IPv6, len = 36 */ + apr_byte_t src_addr[16]; + apr_byte_t dst_addr[16]; + apr_uint16_t src_port; + apr_uint16_t dst_port; + } ip6; + struct { /* for AF_UNIX sockets, len = 216 */ + apr_byte_t src_addr[108]; + apr_byte_t dst_addr[108]; + } unx; +} proxy_v2_addr; + +typedef struct { + apr_byte_t sig[12]; /* hex 0D 0A 0D 0A 00 0D 0A 51 55 49 54 0A */ + apr_byte_t ver_cmd; /* protocol version and command */ + apr_byte_t fam; /* protocol family and address */ + apr_uint16_t len; /* number of following bytes part of the header */ + proxy_v2_addr addr; +} proxy_v2; + +typedef union { + proxy_v1 v1; + proxy_v2 v2; +} proxy_header; + +static const char v2sig[12] = "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A"; +#define MIN_V1_HDR_LEN 15 +#define MIN_V2_HDR_LEN 16 +#define MIN_HDR_LEN MIN_V1_HDR_LEN + +/* XXX: Unsure if this is needed if v6 support is not available on + this platform */ +#ifndef INET6_ADDRSTRLEN +#define INET6_ADDRSTRLEN 46 +#endif + +typedef struct { + char header[sizeof(proxy_header)]; + apr_size_t rcvd; + apr_size_t need; + int version; + ap_input_mode_t mode; + apr_bucket_brigade *bb; + int done; +} remoteip_filter_context; + +/** Holds the resolved proxy info for this connection and any addition + configurable parameters +*/ +typedef struct { + /** The parsed client address in native format */ + apr_sockaddr_t *client_addr; + /** Character representation of the client */ + char *client_ip; + /** Flag indicating that the PROXY header may be omitted on this + connection (do not abort if it is missing). */ + int proxy_protocol_optional; +} remoteip_conn_config_t; + +typedef enum { HDR_DONE, HDR_ERROR, HDR_MISSING, HDR_NEED_MORE } remoteip_parse_status_t; + static void *create_remoteip_server_config(apr_pool_t *p, server_rec *s) { remoteip_config_t *config = apr_pcalloc(p, sizeof *config); /* config->header_name = NULL; * config->proxies_header_name = NULL; */ + config->proxy_protocol_enabled = NULL; + config->proxy_protocol_optional = NULL; + config->proxy_protocol_disabled = NULL; + config->pp_optional = 0; + config->pool = p; return config; } @@ -85,6 +184,9 @@ static void *merge_remoteip_server_config(apr_pool_t *p, void *globalv, config->proxymatch_ip = server->proxymatch_ip ? server->proxymatch_ip : global->proxymatch_ip; + config->pp_optional = server->pp_optional + ? server->pp_optional + : global->pp_optional; return config; } @@ -215,13 +317,184 @@ static const char *proxylist_read(cmd_parms *cmd, void *cfg, return NULL; } +/** Similar apr_sockaddr_equal, except that it compares ports too. */ +static int remoteip_sockaddr_equal(apr_sockaddr_t *addr1, apr_sockaddr_t *addr2) +{ + return (addr1->port == addr2->port && apr_sockaddr_equal(addr1, addr2)); +} + +/** Similar remoteip_sockaddr_equal, except that it handles wildcard addresses + * and ports too. + */ +static int remoteip_sockaddr_compat(apr_sockaddr_t *addr1, apr_sockaddr_t *addr2) +{ + /* test exact address equality */ + if (apr_sockaddr_equal(addr1, addr2) && + (addr1->port == addr2->port || addr1->port == 0 || addr2->port == 0)) { + return 1; + } + + /* test address wildcards */ + if (apr_sockaddr_is_wildcard(addr1) && + (addr1->port == 0 || addr1->port == addr2->port)) { + return 1; + } + + if (apr_sockaddr_is_wildcard(addr2) && + (addr2->port == 0 || addr2->port == addr1->port)) { + return 1; + } + + return 0; +} + +static int remoteip_addr_in_list(remoteip_addr_info *list, apr_sockaddr_t *addr) +{ + for (; list; list = list->next) { + if (remoteip_sockaddr_compat(list->addr, addr)) { + return 1; + } + } + + return 0; +} + +static void remoteip_warn_enable_conflict(remoteip_addr_info *prev, server_rec *new, const char* arg) +{ + char buf[INET6_ADDRSTRLEN]; + + apr_sockaddr_ip_getbuf(buf, sizeof(buf), prev->addr); + + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, new, APLOGNO(03491) + "RemoteIPProxyProtocolEnable: previous setting for %s:%hu from virtual " + "host {%s:%hu in %s} is being overriden by virtual host " + "{%s:%hu in %s}; new setting is '%s'", + buf, prev->addr->port, prev->source->server_hostname, + prev->source->addrs->host_port, prev->source->defn_name, + new->server_hostname, new->addrs->host_port, new->defn_name, + arg); +} + +static const char *remoteip_enable_proxy_protocol(cmd_parms *cmd, void *config, + const char *arg) +{ + remoteip_config_t *global_conf; + remoteip_config_t *server_conf; + server_addr_rec *addr; + remoteip_addr_info **add; + int list_len = 2; + remoteip_addr_info **rem_list[list_len]; + remoteip_addr_info *list; + int i; + + global_conf = ap_get_module_config(ap_server_conf->module_config, + &remoteip_module); + server_conf = ap_get_module_config(cmd->server->module_config, + &remoteip_module); + + if (strcasecmp(arg, "On") == 0) { + add = &global_conf->proxy_protocol_enabled; + rem_list[0] = &global_conf->proxy_protocol_optional; + rem_list[1] = &global_conf->proxy_protocol_disabled; + } + else if (strcasecmp(arg, "Optional") == 0) { + add = &global_conf->proxy_protocol_optional; + rem_list[0] = &global_conf->proxy_protocol_enabled; + rem_list[1] = &global_conf->proxy_protocol_disabled; + server_conf->pp_optional = 1; + } + else if (strcasecmp(arg, "Off") == 0 ) { + add = &global_conf->proxy_protocol_disabled; + rem_list[0] = &global_conf->proxy_protocol_enabled; + rem_list[1] = &global_conf->proxy_protocol_optional; + } + else { + return apr_pstrcat(cmd->pool, "Unrecognized option for ProxyProtocolEnable `%s'", arg, NULL); + } + + for (addr = cmd->server->addrs; addr; addr = addr->next) { + /* remove address from other lists */ + for (i = 0; i < list_len ; i++) { + remoteip_addr_info **rem = rem_list[i]; + if (*rem) { + if (remoteip_sockaddr_equal((*rem)->addr, addr->host_addr)) { + remoteip_warn_enable_conflict(*rem, cmd->server, arg); + *rem = (*rem)->next; + } + else { + for (list = *rem; list->next; list = list->next) { + if (remoteip_sockaddr_equal(list->next->addr, addr->host_addr)) { + remoteip_warn_enable_conflict(list->next, cmd->server, arg); + list->next = list->next->next; + break; + } + } + } + } + } + + /* add address to desired list */ + if (!remoteip_addr_in_list(*add, addr->host_addr)) { + remoteip_addr_info *info = apr_palloc(global_conf->pool, sizeof(*info)); + info->addr = addr->host_addr; + info->source = cmd->server; + info->next = *add; + *add = info; + } + } + + return NULL; +} + +static int remoteip_hook_pre_config(apr_pool_t *pconf, apr_pool_t *plog, + apr_pool_t *ptemp) +{ + remoteip_config_t *config = (remoteip_config_t *) + create_remoteip_server_config(pconf, NULL); + ap_set_module_config(ap_server_conf->module_config, &remoteip_module, + config); + + return OK; +} + +static int remoteip_hook_post_config(apr_pool_t *pconf, apr_pool_t *plog, + apr_pool_t *ptemp, server_rec *s) +{ + remoteip_config_t *conf; + remoteip_addr_info *info; + char buf[INET6_ADDRSTRLEN]; + + conf = ap_get_module_config(ap_server_conf->module_config, + &remoteip_module); + + for (info = conf->proxy_protocol_enabled; info; info = info->next) { + apr_sockaddr_ip_getbuf(buf, sizeof(buf), info->addr); + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, s, APLOGNO(03492) + "RemoteIPProxyProtocol: enabled on %s:%hu", buf, info->addr->port); + } + for (info = conf->proxy_protocol_optional; info; info = info->next) { + apr_sockaddr_ip_getbuf(buf, sizeof(buf), info->addr); + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, s, APLOGNO(03493) + "RemoteIPProxyProtocol: optional on %s:%hu", buf, info->addr->port); + } + for (info = conf->proxy_protocol_disabled; info; info = info->next) { + apr_sockaddr_ip_getbuf(buf, sizeof(buf), info->addr); + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, s, APLOGNO(03494) + "RemoteIPProxyProtocol: disabled on %s:%hu", buf, info->addr->port); + } + + return OK; +} + static int remoteip_modify_request(request_rec *r) { conn_rec *c = r->connection; remoteip_config_t *config = (remoteip_config_t *) ap_get_module_config(r->server->module_config, &remoteip_module); - remoteip_req_t *req = NULL; + remoteip_conn_config_t *conn_config = (remoteip_conn_config_t *) + ap_get_module_config(r->connection->conn_config, &remoteip_module); + remoteip_req_t *req = NULL; apr_sockaddr_t *temp_sa; apr_status_t rv; @@ -237,10 +510,37 @@ static int remoteip_modify_request(request_rec *r) */ void *internal = NULL; - if (!config->header_name) { + /* No header defined or results from our input filter */ + if (!config->header_name && !conn_config) { return DECLINED; } + /* Easy parsing case - just position the data we already have from PROXY + protocol handling allowing it to take precedence and return + */ + if (conn_config) { + /* We may have gotten here if processing was optional - check for that */ + if (!conn_config->client_addr) { + if (config->pp_optional) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03495) + "RemoteIPProxyProtocol data is missing, but was optional. Allowing request."); + return OK; + } + else { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(03496) + "RemoteIPProxyProtocol data is missing, but required! Aborting request."); + return HTTP_BAD_REQUEST; + } + } + + r->useragent_addr = conn_config->client_addr; + r->useragent_ip = conn_config->client_ip; + + ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, + "Using %s as client's IP from PROXY protocol", r->useragent_ip); + return OK; + } + if (config->proxymatch_ip) { /* This indicates that a RemoteIPInternalProxy, RemoteIPInternalProxyList, RemoteIPTrustedProxy or RemoteIPTrustedProxyList directive is configured. @@ -424,6 +724,464 @@ static int remoteip_modify_request(request_rec *r) return OK; } +static int remoteip_is_server_port(apr_port_t port) +{ + ap_listen_rec *lr; + + for (lr = ap_listeners; lr; lr = lr->next) { + if (lr->bind_addr && lr->bind_addr->port == port) { + return 1; + } + } + + return 0; +} + +/* + * Human readable format: + * PROXY {TCP4|TCP6|UNKNOWN} <client-ip-addr> <dest-ip-addr> <client-port> <dest-port><CR><LF> + */ +static remoteip_parse_status_t remoteip_process_v1_header(conn_rec *c, + remoteip_conn_config_t *conn_conf, + proxy_header *hdr, apr_size_t len, + apr_size_t *hdr_len) +{ + char *end, *word, *host, *valid_addr_chars, *saveptr; + char buf[sizeof(hdr->v1.line)]; + apr_port_t port; + apr_status_t ret; + apr_int32_t family; + +#define GET_NEXT_WORD(field) \ + word = apr_strtok(NULL, " ", &saveptr); \ + if (!word) { \ + ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(03497) \ + "RemoteIPProxyProtocol: no " field " found in header '%s'", \ + hdr->v1.line); \ + return HDR_ERROR; \ + } + + end = memchr(hdr->v1.line, '\r', len - 1); + if (!end || end[1] != '\n') { + return HDR_NEED_MORE; /* partial or invalid header */ + } + + *end = '\0'; + *hdr_len = end + 2 - hdr->v1.line; /* skip header + CRLF */ + + /* parse in separate buffer so have the original for error messages */ + strcpy(buf, hdr->v1.line); + + apr_strtok(buf, " ", &saveptr); + + /* parse family */ + GET_NEXT_WORD("family") + if (strcmp(word, "UNKNOWN") == 0) { + conn_conf->client_addr = c->client_addr; + conn_conf->client_ip = c->client_ip; + return HDR_DONE; + } + else if (strcmp(word, "TCP4") == 0) { + family = APR_INET; + valid_addr_chars = "0123456789."; + } + else if (strcmp(word, "TCP6") == 0) { +#if APR_HAVE_IPV6 + family = APR_INET6; + valid_addr_chars = "0123456789abcdefABCDEF:"; +#else + ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(03498) + "RemoteIPProxyProtocol: Unable to parse v6 address - APR is not compiled with IPv6 support", + word, hdr->v1.line); + return HDR_ERROR; +#endif + } + else { + ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(03499) + "RemoteIPProxyProtocol: unknown family '%s' in header '%s'", + word, hdr->v1.line); + return HDR_ERROR; + } + + /* parse client-addr */ + GET_NEXT_WORD("client-address") + + if (strspn(word, valid_addr_chars) != strlen(word)) { + ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(03500) + "RemoteIPProxyProtocol: invalid client-address '%s' found in " + "header '%s'", word, hdr->v1.line); + return HDR_ERROR; + } + + host = word; + + /* parse dest-addr */ + GET_NEXT_WORD("destination-address") + + /* parse client-port */ + GET_NEXT_WORD("client-port") + if (sscanf(word, "%hu", &port) != 1) { + ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(03501) + "RemoteIPProxyProtocol: error parsing port '%s' in header '%s'", + word, hdr->v1.line); + return HDR_ERROR; + } + + /* parse dest-port */ + /* GET_NEXT_WORD("destination-port") - no-op since we don't care about it */ + + /* create a socketaddr from the info */ + ret = apr_sockaddr_info_get(&conn_conf->client_addr, host, family, port, 0, + c->pool); + if (ret != APR_SUCCESS) { + conn_conf->client_addr = NULL; + ap_log_cerror(APLOG_MARK, APLOG_ERR, ret, c, APLOGNO(03502) + "RemoteIPProxyProtocol: error converting family '%d', host '%s'," + " and port '%hu' to sockaddr; header was '%s'", + family, host, port, hdr->v1.line); + return HDR_ERROR; + } + + conn_conf->client_ip = apr_pstrdup(c->pool, host); + + return HDR_DONE; +} + +/** Add our filter to the connection if it is requested + */ +static int remoteip_hook_pre_connection(conn_rec *c, void *csd) +{ + remoteip_config_t *conf; + remoteip_conn_config_t *conn_conf; + int optional; + + conf = ap_get_module_config(ap_server_conf->module_config, + &remoteip_module); + + /* Used twice - do the check only once */ + optional = remoteip_addr_in_list(conf->proxy_protocol_optional, c->local_addr); + + /* check if we're enabled for this connection */ + if ((!remoteip_addr_in_list(conf->proxy_protocol_enabled, c->local_addr) + && !optional ) + || remoteip_addr_in_list(conf->proxy_protocol_disabled, c->local_addr)) { + return DECLINED; + } + + /* mod_proxy creates outgoing connections - we don't want those */ + if (!remoteip_is_server_port(c->local_addr->port)) { + return DECLINED; + } + + /* add our filter */ + if (!ap_add_input_filter(remoteip_filter_name, NULL, NULL, c)) { + /* XXX: Shouldn't this WARN in log? */ + return DECLINED; + } + + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(03503) + "RemoteIPProxyProtocol: enabled on connection to %s:%hu", + c->local_ip, c->local_addr->port); + + /* this holds the resolved proxy info for this connection */ + conn_conf = apr_pcalloc(c->pool, sizeof(*conn_conf)); + + /* Propagate the optional flag so the connection handler knows not to + abort if the header is mising. NOTE: This means we must check after + we read the request that the header was NOT optional, too. + */ + conn_conf->proxy_protocol_optional = optional; + + ap_set_module_config(c->conn_config, &remoteip_module, conn_conf); + + return OK; +} + +/* Binary format: + * <sig><cmd><proto><addr-len><addr> + * sig = \x0D \x0A \x0D \x0A \x00 \x0D \x0A \x51 \x55 \x49 \x54 \x0A + * cmd = <4-bits-version><4-bits-command> + * 4-bits-version = \x02 + * 4-bits-command = {\x00|\x01} (\x00 = LOCAL: discard con info; \x01 = PROXY) + * proto = <4-bits-family><4-bits-protocol> + * 4-bits-family = {\x00|\x01|\x02|\x03} (AF_UNSPEC, AF_INET, AF_INET6, AF_UNIX) + * 4-bits-protocol = {\x00|\x01|\x02} (UNSPEC, STREAM, DGRAM) + */ +static remoteip_parse_status_t remoteip_process_v2_header(conn_rec *c, + remoteip_conn_config_t *conn_conf, + proxy_header *hdr) +{ + apr_status_t ret; + + switch (hdr->v2.ver_cmd & 0xF) { + case 0x01: /* PROXY command */ + switch (hdr->v2.fam) { + case 0x11: /* TCPv4 */ + ret = apr_sockaddr_info_get(&conn_conf->client_addr, NULL, + APR_INET, + ntohs(hdr->v2.addr.ip4.src_port), + 0, c->pool); + if (ret != APR_SUCCESS) { + conn_conf->client_addr = NULL; + ap_log_cerror(APLOG_MARK, APLOG_ERR, ret, c, APLOGNO(03504) + "RemoteIPPProxyProtocol: error creating sockaddr"); + return HDR_ERROR; + } + + conn_conf->client_addr->sa.sin.sin_addr.s_addr = + hdr->v2.addr.ip4.src_addr; + break; + + case 0x21: /* TCPv6 */ +#if APR_HAVE_IPV6 + ret = apr_sockaddr_info_get(&conn_conf->client_addr, NULL, + APR_INET6, + ntohs(hdr->v2.addr.ip6.src_port), + 0, c->pool); + if (ret != APR_SUCCESS) { + conn_conf->client_addr = NULL; + ap_log_cerror(APLOG_MARK, APLOG_ERR, ret, c, APLOGNO(03505) + "RemoteIPProxyProtocol: error creating sockaddr"); + return HDR_ERROR; + } + memcpy(&conn_conf->client_addr->sa.sin6.sin6_addr.s6_addr, + hdr->v2.addr.ip6.src_addr, 16); + break; +#else + ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(03506) + "RemoteIPProxyProtocol: APR is not compiled with IPv6 support"); + return HDR_ERROR; +#endif + default: + /* unsupported protocol, keep local connection address */ + return HDR_DONE; + } + break; /* we got a sockaddr now */ + + case 0x00: /* LOCAL command */ + /* keep local connection address for LOCAL */ + return HDR_DONE; + + default: + /* not a supported command */ + ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(03507) + "RemoteIPProxyProtocol: unsupported command %.2hx", + (unsigned short)hdr->v2.ver_cmd); + return HDR_ERROR; + } + + /* got address - compute the client_ip from it */ + ret = apr_sockaddr_ip_get(&conn_conf->client_ip, conn_conf->client_addr); + if (ret != APR_SUCCESS) { + conn_conf->client_addr = NULL; + ap_log_cerror(APLOG_MARK, APLOG_ERR, ret, c, APLOGNO(03508) + "RemoteIPProxyProtocol: error converting address to string"); + return HDR_ERROR; + } + + return HDR_DONE; +} + +/** Determine if this is a v1 or v2 PROXY header. + */ +static int remoteip_determine_version(conn_rec *c, const char *ptr) +{ + proxy_header *hdr = (proxy_header *) ptr; + + /* assert len >= 14 */ + + if (memcmp(&hdr->v2, v2sig, sizeof(v2sig)) == 0 && + (hdr->v2.ver_cmd & 0xF0) == 0x20) { + return 2; + } + else if (memcmp(hdr->v1.line, "PROXY ", 6) == 0) { + return 1; + } + else { + return -1; + } +} + +/* Capture the first bytes on the protocol and parse the proxy protocol header. + * Removes itself when the header is complete. + */ +static apr_status_t remoteip_input_filter(ap_filter_t *f, + apr_bucket_brigade *bb_out, + ap_input_mode_t mode, + apr_read_type_e block, + apr_off_t readbytes) +{ + apr_status_t ret; + remoteip_filter_context *ctx = f->ctx; + remoteip_conn_config_t *conn_conf; + apr_bucket *b; + remoteip_parse_status_t psts; + const char *ptr; + apr_size_t len; + + if (f->c->aborted) { + return APR_ECONNABORTED; + } + + /* allocate/retrieve the context that holds our header */ + if (!ctx) { + ctx = f->ctx = apr_palloc(f->c->pool, sizeof(*ctx)); + ctx->rcvd = 0; + ctx->need = MIN_HDR_LEN; + ctx->version = 0; + ctx->mode = AP_MODE_READBYTES; + ctx->bb = apr_brigade_create(f->c->pool, f->c->bucket_alloc); + ctx->done = 0; + } + + if (ctx->done) { + /* Note: because we're a connection filter we can't remove ourselves + * when we're done, so we have to stay in the chain and just go into + * passthrough mode. + */ + return ap_get_brigade(f->next, bb_out, mode, block, readbytes); + } + + conn_conf = ap_get_module_config(f->c->conn_config, &remoteip_module); + + /* try to read a header's worth of data */ + while (!ctx->done) { + if (APR_BRIGADE_EMPTY(ctx->bb)) { + ret = ap_get_brigade(f->next, ctx->bb, ctx->mode, block, + ctx->need - ctx->rcvd); + if (ret != APR_SUCCESS) { + return ret; + } + } + if (APR_BRIGADE_EMPTY(ctx->bb)) { + return APR_EOF; + } + + while (!ctx->done && !APR_BRIGADE_EMPTY(ctx->bb)) { + b = APR_BRIGADE_FIRST(ctx->bb); + + ret = apr_bucket_read(b, &ptr, &len, block); + if (APR_STATUS_IS_EAGAIN(ret) && block == APR_NONBLOCK_READ) { + return APR_SUCCESS; + } + if (ret != APR_SUCCESS) { + return ret; + } + + memcpy(ctx->header + ctx->rcvd, ptr, len); + ctx->rcvd += len; + + /* Remove instead of delete - we may put this bucket + back into bb_out if the header was optional and we + pass down the chain */ + APR_BUCKET_REMOVE(b); + psts = HDR_NEED_MORE; + + if (ctx->version == 0) { + /* reading initial chunk */ + if (ctx->rcvd >= MIN_HDR_LEN) { + ctx->version = remoteip_determine_version(f->c, ctx->header); + if (ctx->version < 0) { + psts = HDR_MISSING; + } + else if (ctx->version == 1) { + ctx->mode = AP_MODE_GETLINE; + ctx->need = sizeof(proxy_v1); + } + else if (ctx->version == 2) { + ctx->need = MIN_V2_HDR_LEN; + } + } + } + else if (ctx->version == 1) { + psts = remoteip_process_v1_header(f->c, conn_conf, + (proxy_header *) ctx->header, + ctx->rcvd, &ctx->need); + } + else if (ctx->version == 2) { + if (ctx->rcvd >= MIN_V2_HDR_LEN) { + ctx->need = MIN_V2_HDR_LEN + + ntohs(((proxy_header *) ctx->header)->v2.len); + } + if (ctx->rcvd >= ctx->need) { + psts = remoteip_process_v2_header(f->c, conn_conf, + (proxy_header *) ctx->header); + } + } + else { + ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, f->c, APLOGNO(03509) + "RemoteIPProxyProtocol: internal error: unknown version " + "%d", ctx->version); + f->c->aborted = 1; + apr_bucket_delete(b); + apr_brigade_destroy(ctx->bb); + return APR_ECONNABORTED; + } + + switch (psts) { + case HDR_MISSING: + if (conn_conf->proxy_protocol_optional) { + /* Same as DONE, but don't delete the bucket. Rather, put it + back into the brigade and move the request along the stack */ + ctx->done = 1; + APR_BRIGADE_INSERT_HEAD(bb_out, b); + return ap_pass_brigade(f->next, ctx->bb); + } + else { + ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, f->c, APLOGNO(03510) + "RemoteIPProxyProtocol: no valid PROXY header found"); + /* fall through to error case */ + } + case HDR_ERROR: + f->c->aborted = 1; + apr_bucket_delete(b); + apr_brigade_destroy(ctx->bb); + return APR_ECONNABORTED; + + case HDR_DONE: + apr_bucket_delete(b); + ctx->done = 1; + break; + + case HDR_NEED_MORE: + /* It is safe to delete this bucket if we get here since we know + that we are definitely processing a header (we've read enough to + know if the signatures exist on the line) */ + apr_bucket_delete(b); + break; + } + } + } + + /* we only get here when done == 1 */ + if (psts == HDR_DONE) { + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, f->c, APLOGNO(03511) + "RemoteIPProxyProtocol: received valid PROXY header: %s:%hu", + conn_conf->client_ip, conn_conf->client_addr->port); + } + else { + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, f->c, APLOGNO(03512) + "RemoteIPProxyProtocol: PROXY header was missing"); + } + + if (ctx->rcvd > ctx->need || !APR_BRIGADE_EMPTY(ctx->bb)) { + ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, f->c, APLOGNO(03513) + "RemoteIPProxyProtocol: internal error: have data left over; " + " need=%lu, rcvd=%lu, brigade-empty=%d", ctx->need, + ctx->rcvd, APR_BRIGADE_EMPTY(ctx->bb)); + f->c->aborted = 1; + apr_brigade_destroy(ctx->bb); + return APR_ECONNABORTED; + } + + /* clean up */ + apr_brigade_destroy(ctx->bb); + ctx->bb = NULL; + + /* now do the real read for the upper layer */ + return ap_get_brigade(f->next, bb_out, mode, block, readbytes); +} + static const command_rec remoteip_cmds[] = { AP_INIT_TAKE1("RemoteIPHeader", header_name_set, NULL, RSRC_CONF, @@ -447,11 +1205,21 @@ static const command_rec remoteip_cmds[] = RSRC_CONF | EXEC_ON_READ, "The filename to read the list of internal proxies, " "see the RemoteIPInternalProxy directive"), + AP_INIT_TAKE1("RemoteIPProxyProtocolEnable", remoteip_enable_proxy_protocol, NULL, + RSRC_CONF, "Enable proxy-protocol handling (`on', `off')"), { NULL } }; static void register_hooks(apr_pool_t *p) { + /* mod_ssl is CONNECTION + 5, so we want something higher (earlier); + * mod_reqtimeout is CONNECTION + 8, so we want something lower (later) */ + ap_register_input_filter(remoteip_filter_name, remoteip_input_filter, NULL, + AP_FTYPE_CONNECTION + 7); + + ap_hook_pre_config(remoteip_hook_pre_config, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_post_config(remoteip_hook_post_config, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_pre_connection(remoteip_hook_pre_connection, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_post_read_request(remoteip_modify_request, NULL, NULL, APR_HOOK_FIRST); } |