+ 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 +513,37 @@ static int remoteip_modify_request(reque
*/
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.
@@ -427,6 +730,464 @@ static int remoteip_modify_request(reque
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",
+ 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,
@@ -450,11 +1211,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')"),