/*
Apache 2.2/2.4 mod_myfixip -- Author: G.Grandes
v0.1 - 2011.05.07, Init version (SSL)
v0.2 - 2011.05.28, Mix version (SSL & Non-SSL)
v0.3 - 2014.12.06, Support for PROXY protocol v1 (haproxy)
v0.4 - 2014.12.06, Porting to Apache 2.4
v0.5 - 2015.01.14, Backport to Apache 2.2 with dual support 2.2/2.4
Fix fragmented TCP frames in AWS-ELB
v0.6 - 2015.01.17, Fix fragmented SSL frames
Removed RewriteIPHookPortSSL directive
v0.7 - 2015.01.24, use defined format (APR_OFF_T_FMT) for apr_off_t
v0.8 - 2015.06.29, fixing zero length
v0.9 - 2015.07.03, handle fragmented headers
v1.0 - 2015.08.01, fix excess memory usage with keepalive request
v1.1 - 2015.08.01, improved handling of mod_proxy request
v1.2 - 2015.08.06, memory cleanups: bucket and brigades
v1.3 - 2015.12.27, connection cleanup: non-PROXY partial headers
v1.4 - 2016.01.06, fix order with mod_security2
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
In HTTP (no SSL): this will fix "useragent_ip" field if the request
contains an "X-Cluster-Client-Ip" header field, and the request came
directly from a one of the IP Addresses specified in the configuration
file (RewriteIPAllow directive).
In HTTPS (SSL): this will fix "useragent_ip" field if any of:
1) the connection buffer begins with "HELOxxxx" (there xxxx is IPv4 in
binary format -netorder-)
2) buffer follow PROXY protocol v1
- TCP/IPv4 :
"PROXY TCP4 255.255.255.255 255.255.255.255 65535 65535\r\n"
=> 5 + 1 + 4 + 1 + 15 + 1 + 15 + 1 + 5 + 1 + 5 + 2 = 56 chars
- TCP/IPv6 :
"PROXY TCP6 ffff:f...f:ffff ffff:f...f:ffff 65535 65535\r\n"
=> 5 + 1 + 4 + 1 + 39 + 1 + 39 + 1 + 5 + 1 + 5 + 2 = 104 chars
- unknown connection (short form) :
"PROXY UNKNOWN\r\n"
=> 5 + 1 + 7 + 2 = 15 chars
- worst case (optional fields set to 0xff) :
"PROXY UNKNOWN ffff:f...f:ffff ffff:f...f:ffff 65535 65535\r\n"
=> 5 + 1 + 7 + 1 + 39 + 1 + 39 + 1 + 5 + 1 + 5 + 2 = 107 chars
Complete Proxy-Protocol:
http://haproxy.1wt.eu/download/1.5/doc/proxy-protocol.txt
The rewrite address of request is allowed from a one of the IP Addresses
specified in the configuration file (RewriteIPAllow directive).
Usage:
# Global
RewriteIPResetHeader off
RewriteIPAllow 192.168.0.0/16 127.0.0.1
# VirtualHost
RewriteIPResetHeader on
To play with this module first compile it into a
DSO file and install it into Apache's modules directory
by running:
$ apxs2 -c -i mod_myfixip.c
# How-TO enable PROXY protocol in ELB:
https://gist.github.com/pablitoc/91c40d820f207879969c
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// References:
// http://web.archive.org/web/20150328010857/http://ci.apache.org/projects/httpd/trunk/doxygen/
// http://apr.apache.org/docs/apr/1.5/
// http://httpd.apache.org/docs/2.4/developer/
// http://onlamp.com/pub/ct/38
// http://svn.apache.org/repos/asf/httpd/httpd/tags/2.4.0/modules/metadata/mod_remoteip.c
// http://docs.aws.amazon.com/ElasticLoadBalancing/latest/DeveloperGuide/elb-listener-config.html
// http://docs.aws.amazon.com/ElasticLoadBalancing/latest/DeveloperGuide/enable-proxy-protocol.html
// http://www.haproxy.org/download/1.5/doc/proxy-protocol.txt
// http://blog.haproxy.com/haproxy/proxy-protocol/
#define CORE_PRIVATE
#include "httpd.h"
#include "http_config.h"
#include "http_connection.h"
#include "http_protocol.h"
#include "http_log.h"
#include "ap_mpm.h"
#include "apr_strings.h"
#include "scoreboard.h"
#include "http_core.h"
#include "ap_listen.h"
#include
#include
#include
#define MODULE_NAME "mod_myfixip"
#define MODULE_VERSION "1.4"
module AP_MODULE_DECLARE_DATA myfixip_module;
#define PROXY "PROXY"
#define HELO "HELO"
#define TEST "TEST"
#define TEST_RES_OK "OK" "\n"
#define NOTE_ORIGINAL_IP "FIXIP_ORIGINAL_USERAGENT_IP"
#define NOTE_REWRITE_IP "FIXIP_REWRITE_USERAGENT_IP"
#define NOTE_CLIENT_TRUST "FIXIP_CLIENT_TRUSTED"
#ifndef HDR_USERAGENT_IP
#define HDR_USERAGENT_IP "X-Cluster-Client-Ip" // FIXME: Do configurable name
#endif
//#define DEBUG
#define PROXY_HEAD_LENGTH 4
#define PROXY_MAX_LENGTH 107
#define PAD_MAGIC 0x04202015
// Apache 2.4 or 2.2
#if AP_SERVER_MINORVERSION_NUMBER > 3
#define _REMOTE_HOST c->remote_host
#define _CLIENT_IP c->client_ip
#define _CLIENT_ADDR c->client_addr
#define _USERAGENT_IP r->useragent_ip
#define _USERAGENT_ADDR r->useragent_addr
#else
#define _REMOTE_HOST c->remote_host
#define _CLIENT_IP c->remote_ip
#define _CLIENT_ADDR c->remote_addr
#define _USERAGENT_IP c->remote_ip
#define _USERAGENT_ADDR c->remote_addr
#endif
typedef struct
{
apr_time_t time;
apr_port_t port;
apr_array_header_t *allows;
int resetHeader;
} my_config;
typedef struct {
apr_ipsubnet_t *ip;
} accesslist;
typedef enum {
PHASE_WANT_HEAD, // first 4 bytes
PHASE_WANT_BINIP, // next 4 bytes
PHASE_WANT_LINE, // full PROXY header
PHASE_DONE
} my_phase;
typedef struct
{
int magic;
my_phase phase;
ap_input_mode_t mode;
apr_off_t need;
apr_off_t recv;
apr_off_t offset;
char buf[PROXY_MAX_LENGTH + 1];
int pad;
} my_ctx;
static const char *const myfixip_filter_name = "myfixip_filter_name";
/**
* Create per-server configuration structure
*/
static void *create_config(apr_pool_t *p, server_rec *s)
{
my_config *conf = apr_palloc(p, sizeof(my_config));
conf->allows = apr_array_make(p, 1, sizeof(accesslist));
conf->resetHeader = 0;
conf->time = apr_time_now();
return conf;
}
/**
* Merge per-server configuration structure
*/
static void *merge_config(apr_pool_t *p, void *parent_server1_conf, void *add_server2_conf)
{
my_config *merged_config = (my_config *) apr_pcalloc(p, sizeof(my_config));
memcpy(merged_config, parent_server1_conf, sizeof(my_config));
my_config *s1conf = (my_config *) parent_server1_conf;
my_config *s2conf = (my_config *) add_server2_conf;
//merged_config->allows = (s1conf->allows == s2conf->allows) ? s1conf->allows : s2conf->allows;
merged_config->resetHeader = (s1conf->resetHeader == s2conf->resetHeader) ? s1conf->resetHeader : s2conf->resetHeader;
return (void *) merged_config;
}
/**
* Parse the RewriteIPResetHeader directive
*/
static const char *reset_header_config_cmd(cmd_parms *parms, void *mconfig, int flag)
{
my_config *conf = ap_get_module_config(parms->server->module_config, &myfixip_module);
const char *err = ap_check_cmd_context (parms, NOT_IN_DIR_LOC_FILE|NOT_IN_LIMIT);
if (err != NULL) {
return err;
}
conf->resetHeader = flag ? TRUE : FALSE;
return NULL;
}
/**
* Parse the RewriteIPAllow directive
*/
static const char *allow_config_cmd(cmd_parms *cmd, void *dv, const char *where_c)
{
my_config *d = ap_get_module_config(cmd->server->module_config, &myfixip_module);
accesslist *a;
char *where = apr_pstrdup(cmd->pool, where_c);
char *s;
char msgbuf[120];
apr_status_t rv;
a = (accesslist *) apr_array_push(d->allows);
if ((s = ap_strchr(where, '/'))) {
*s++ = '\0';
rv = apr_ipsubnet_create(&a->ip, where, s, cmd->pool);
if (APR_STATUS_IS_EINVAL(rv)) {
/* looked nothing like an IP address */
return "An IP address was expected";
}
else if (rv != APR_SUCCESS) {
apr_strerror(rv, msgbuf, sizeof msgbuf);
return apr_pstrdup(cmd->pool, msgbuf);
}
}
else if (!APR_STATUS_IS_EINVAL(rv = apr_ipsubnet_create(&a->ip, where, NULL, cmd->pool))) {
if (rv != APR_SUCCESS) {
apr_strerror(rv, msgbuf, sizeof msgbuf);
return apr_pstrdup(cmd->pool, msgbuf);
}
}
else { /* no slash, didn't look like an IP address => must be a host */
return "An IP address was expected";
}
return NULL;
}
/**
* Array describing structure of configuration directives
*/
static command_rec cmds[] = {
AP_INIT_FLAG("RewriteIPResetHeader", reset_header_config_cmd, NULL, RSRC_CONF, "Reset HTTP-Header in this SSL vhost?"),
AP_INIT_ITERATE("RewriteIPAllow", allow_config_cmd, NULL, RSRC_CONF, "IP-address wildcards"),
{NULL}
};
/**
* Set up startup-time initialization
*/
static int post_config(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s)
{
ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, NULL, MODULE_NAME " " MODULE_VERSION " started");
return OK;
}
/**
* Find remote_addr in ACL
*/
static int find_accesslist(apr_array_header_t *a, apr_sockaddr_t *remote_addr)
{
accesslist *ap = (accesslist *) a->elts;
int i;
for (i = 0; i < a->nelts; ++i) {
if (apr_ipsubnet_test(ap[i].ip, remote_addr)) {
return 1;
}
}
return 0;
}
/**
* Check if client_ip is trusted
*/
static int check_trusted( conn_rec *c, my_config *conf )
{
const char *trusted;
trusted = apr_table_get( c->notes, NOTE_CLIENT_TRUST);
if (trusted) return (trusted[0] == 'Y');
// Find Access List & Permit/Deny rewrite IP of Client
if (find_accesslist(conf->allows, _CLIENT_ADDR)) {
apr_table_setn(c->notes, NOTE_CLIENT_TRUST, "Y");
return 1;
}
apr_table_setn(c->notes, NOTE_CLIENT_TRUST, "N");
return 0;
}
/**
* Check if connection is inbound
*/
static int check_inbound( conn_rec *c )
{
apr_port_t port = c->local_addr->port;
ap_listen_rec *l;
for (l = ap_listeners; l != NULL; l = l->next) {
if (l->bind_addr != NULL) {
if (port == l->bind_addr->port) {
return 1;
}
}
}
return 0;
}
/**
* Pre Connection
*/
static int pre_connection(conn_rec *c, void *csd)
{
my_config *conf = ap_get_module_config (c->base_server->module_config, &myfixip_module);
#ifdef DEBUG
ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL, MODULE_NAME "::pre_connection IP Connection remote: %s:%d localport=%d (1)", _CLIENT_IP, _CLIENT_ADDR->port, c->local_addr->port);
#endif
if (!check_inbound(c)) { // Not Inbound (mod_proxy)
return DECLINED;
}
#ifdef DEBUG
ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL, MODULE_NAME "::pre_connection IP Connection from: %s:%d to port=%d (1)", _CLIENT_IP, _CLIENT_ADDR->port, c->local_addr->port);
#endif
if (!check_trusted(c, conf)) { // Not Trusted
return DECLINED;
}
my_ctx *cctx = apr_palloc(c->pool, sizeof(my_ctx));
cctx->phase = PHASE_WANT_HEAD;
cctx->mode = AP_MODE_READBYTES;
cctx->need = PROXY_HEAD_LENGTH;
cctx->recv = 0;
cctx->offset = 0;
memset(cctx->buf, 0, sizeof(cctx->buf));
cctx->magic = (PAD_MAGIC ^ c->id ^ conf->time) & 0x7FFFFFFF;
cctx->pad = cctx->magic;
ap_add_input_filter(myfixip_filter_name, cctx, NULL, c);
return DECLINED;
}
/**
* Transform binary-network-address to human
*/
static const char *fromBinIPtoString(apr_pool_t *p, const char *binip)
{
// Rewrite IP
struct in_addr inp;
memcpy((char *)&inp, binip, 4);
char *str_ip = inet_ntoa(inp);
if (inet_aton(str_ip, &inp) == 0) {
return NULL;
}
return apr_pstrdup( p, str_ip );
}
/**
* Save original UserAgent IP in connection note
*/
static void save_req_ip(request_rec *r)
{
conn_rec *c = r->connection;
const char *old_ip = apr_table_get(c->notes, NOTE_ORIGINAL_IP);
if (!old_ip) {
apr_table_set(c->notes, NOTE_ORIGINAL_IP, _USERAGENT_IP);
}
}
/**
* Rewrite UserAgent IP
*/
static void rewrite_req_ip(request_rec *r, const char *new_ip)
{
conn_rec *c = r->connection;
// Rewrite IP
apr_sockaddr_t *temp_sa = _USERAGENT_ADDR;
apr_sockaddr_info_get(&temp_sa, new_ip,
APR_UNSPEC, temp_sa->port,
APR_IPV4_ADDR_OK, c->pool);
_USERAGENT_ADDR = temp_sa;
apr_sockaddr_ip_get(&_USERAGENT_IP, _USERAGENT_ADDR);
apr_sockaddr_ip_get(&_REMOTE_HOST, _USERAGENT_ADDR);
//c->remote_host = NULL; // Force DNS re-resolution
#ifdef DEBUG
ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL, MODULE_NAME "::rewrite_req_ip IP Connection from: %s:%d [%s] to port=%d newip=%s (OK)", _CLIENT_IP, _CLIENT_ADDR->port, _USERAGENT_IP, c->local_addr->port, new_ip);
#endif
}
/**
* Rewrite UserAgent IP
*/
static int process_proxy_header(ap_filter_t *f)
{
conn_rec *c = f->c;
my_ctx *ctx = f->ctx;
if (ctx->offset < 15) {
return FALSE;
}
char *end = ctx->buf + ctx->offset - 2;
if ((end[0] != '\r') || (end[1] != '\n')) {
return FALSE;
}
end[0] = ' '; // for next split
end[1] = 0;
#ifdef DEBUG
ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL, MODULE_NAME "::process_proxy_header DEBUG: CMD=PROXY header=%s", ctx->buf);
#endif
apr_size_t length = (end + 2 - ctx->buf);
int size = length - 1;
char *ptr = (char *) ctx->buf;
int tok = 0;
char *srcip = NULL, *dstip = NULL, *srcport = NULL, *dstport = NULL;
while (ptr) {
char *f = memchr(ptr, ' ', size);
if (!f) {
break;
}
*f = '\0';
#ifdef DEBUG
ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL, MODULE_NAME "::process_proxy_header DEBUG: CMD=PROXY token=%s [%d]", ptr, tok);
#endif
// PROXY TCP4 255.255.255.255 255.255.255.255 65535 65535
switch (tok) {
case 0: // PROXY
if (ptr[4] != 'Y') {
return FALSE;
}
break;
case 2: // SRCIP
srcip = ptr;
break;
case 3: // DSTIP
dstip = ptr;
break;
case 4: // SRCPORT
srcport = ptr;
break;
case 5: // DSTPORT
dstport = ptr;
break;
case 1: // PROTO
if (strncmp("TCP", ptr, 3) == 0) {
if ((ptr[3] != '4') &&
(ptr[3] != '6')) {
return FALSE;
}
}
break;
default:
srcip = dstip = srcport = dstport = NULL;
return FALSE;
}
size -= (f + 1 - ptr);
ptr = f + 1;
tok++;
}
if (!dstport) {
return FALSE;
}
if (ctx->pad != ctx->magic) {
ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL, MODULE_NAME "::process_proxy_header padding magic fail (bad=%d vs good=%d)", ctx->pad, ctx->magic);
return FALSE;
}
apr_table_set(c->notes, NOTE_REWRITE_IP, srcip);
#ifdef DEBUG
ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL, MODULE_NAME "::process_proxy_header DEBUG: CMD=PROXY tokens OK");
#endif
return TRUE;
}
/**
* Process input stream
*/
static apr_status_t helocon_filter_in(ap_filter_t *f, apr_bucket_brigade *b, ap_input_mode_t mode, apr_read_type_e block, apr_off_t readbytes)
{
conn_rec *c = f->c;
my_ctx *ctx = f->ctx;
// Fail quickly if the connection has already been aborted.
if (c->aborted) {
apr_brigade_cleanup(b);
return APR_ECONNABORTED;
}
// Fast passthrough
if (ctx->phase == PHASE_DONE) {
return ap_get_brigade(f->next, b, mode, block, readbytes);
}
// Process Head
do {
#ifdef DEBUG
ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL, MODULE_NAME "::helocon_filter_in from: %s:%d to port=%d need=%" APR_OFF_T_FMT " phase=%d (1)", _CLIENT_IP, _CLIENT_ADDR->port, c->local_addr->port, ctx->need, ctx->phase);
#endif
if (APR_BRIGADE_EMPTY(b)) {
#ifdef DEBUG
ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL, MODULE_NAME "::helocon_filter_in from: %s:%d to port=%d need=%" APR_OFF_T_FMT " phase=%d (2)", _CLIENT_IP, _CLIENT_ADDR->port, c->local_addr->port, ctx->need, ctx->phase);
#endif
apr_status_t s = ap_get_brigade(f->next, b, ctx->mode, APR_BLOCK_READ, ctx->need);
if (s != APR_SUCCESS) {
#ifdef DEBUG
ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL, MODULE_NAME "::helocon_filter_in from: %s:%d to port=%d need=%" APR_OFF_T_FMT " phase=%d (fail)(1)", _CLIENT_IP, _CLIENT_ADDR->port, c->local_addr->port, ctx->need, ctx->phase);
#endif
return s;
}
}
if (ctx->phase == PHASE_DONE) {
return APR_SUCCESS;
}
if (APR_BRIGADE_EMPTY(b)) {
#ifdef DEBUG
ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL, MODULE_NAME "::helocon_filter_in from: %s:%d to port=%d need=%" APR_OFF_T_FMT " phase=%d (empty)", _CLIENT_IP, _CLIENT_ADDR->port, c->local_addr->port, ctx->need, ctx->phase);
#endif
return APR_SUCCESS;
}
apr_bucket *e = NULL;
for (e = APR_BRIGADE_FIRST(b); e != APR_BRIGADE_SENTINEL(b); e = APR_BUCKET_NEXT(e)) {
if (e->type == NULL) {
#ifdef DEBUG
ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL, MODULE_NAME "::helocon_filter_in from: %s:%d to port=%d need=%" APR_OFF_T_FMT " phase=%d (type=NULL)", _CLIENT_IP, _CLIENT_ADDR->port, c->local_addr->port, ctx->need, ctx->phase);
#endif
return APR_SUCCESS;
}
// We need more data
if (ctx->need > 0) {
const char *str = NULL;
apr_size_t length = 0;
apr_status_t s = apr_bucket_read(e, &str, &length, APR_BLOCK_READ);
if (s != APR_SUCCESS) {
#ifdef DEBUG
ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL, MODULE_NAME "::helocon_filter_in from: %s:%d to port=%d need=%" APR_OFF_T_FMT " recv=%" APR_OFF_T_FMT " phase=%d readed=%" APR_SIZE_T_FMT " (fail)(2)", _CLIENT_IP, _CLIENT_ADDR->port, c->local_addr->port, ctx->need, ctx->recv, ctx->phase, length);
#endif
return s;
}
#ifdef DEBUG
ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL, MODULE_NAME "::helocon_filter_in from: %s:%d to port=%d need=%" APR_OFF_T_FMT " recv=%" APR_OFF_T_FMT " phase=%d readed=%" APR_SIZE_T_FMT " (3)", _CLIENT_IP, _CLIENT_ADDR->port, c->local_addr->port, ctx->need, ctx->recv, ctx->phase, length);
#endif
if (length > 0) {
if ((ctx->offset + length) > PROXY_MAX_LENGTH) { // Overflow
ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL, MODULE_NAME "::helocon_filter_in ERROR: PROXY protocol header overflow from=%s to port=%d length=%" APR_OFF_T_FMT, _CLIENT_IP, c->local_addr->port, (ctx->offset + length));
goto ABORT_CONN2;
}
memcpy(ctx->buf + ctx->offset, str, length);
if (ctx->pad != ctx->magic) {
ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL, MODULE_NAME "::helocon_filter_in padding magic fail (bad=%d vs good=%d)", ctx->pad, ctx->magic);
goto ABORT_CONN;
}
ctx->offset += length;
ctx->recv += length;
ctx->need -= length;
ctx->buf[ctx->offset] = 0;
// delete HEAD
if (e->length > length) {
apr_bucket_split(e, length);
}
}
apr_bucket_delete(e);
if (length == 0) {
#ifdef DEBUG
ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL, MODULE_NAME "::helocon_filter_in DEBUG bucket flush=%d meta=%d", APR_BUCKET_IS_FLUSH(e) ? 1 : 0, APR_BUCKET_IS_METADATA(e) ? 1 : 0);
#endif
continue;
}
}
// Handle GETLINE mode
if (ctx->mode == AP_MODE_GETLINE) {
if ((ctx->need > 0) && (ctx->recv > 2)) {
char *end = memchr(ctx->buf, '\r', ctx->offset - 1);
if (end) {
#ifdef DEBUG
ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL, MODULE_NAME "::helocon_filter_in DEBUG: GETLINE OK");
#endif
if ((end[0] == '\r') && (end[1] == '\n')) {
ctx->need = 0;
}
}
}
}
if (ctx->need <= 0) {
#ifdef DEBUG
ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL, MODULE_NAME "::helocon_filter_in from: %s:%d to port=%d need=%" APR_OFF_T_FMT " recv=%" APR_OFF_T_FMT " phase=%d (4)", _CLIENT_IP, _CLIENT_ADDR->port, c->local_addr->port, ctx->need, ctx->recv, ctx->phase);
#endif
switch (ctx->phase) {
case PHASE_WANT_HEAD: {
#ifdef DEBUG
ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL, MODULE_NAME "::helocon_filter_in from: %s:%d to port=%d phase=%d checking=%s buf=%s", _CLIENT_IP, _CLIENT_ADDR->port, c->local_addr->port, ctx->phase, "HEAD", ctx->buf);
#endif
// TEST Command
#ifdef DEBUG
ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL, MODULE_NAME "::helocon_filter_in DEBUG: CMD=TEST CHECK");
#endif
if (strncmp(TEST, ctx->buf, 4) == 0) {
apr_socket_t *csd = ap_get_module_config(c->conn_config, &core_module);
apr_size_t length = strlen(TEST_RES_OK);
apr_socket_send(csd, TEST_RES_OK, &length);
apr_socket_shutdown(csd, APR_SHUTDOWN_WRITE);
apr_socket_close(csd);
#ifdef DEBUG
ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL, MODULE_NAME "::helocon_filter_in DEBUG: CMD=TEST OK");
#endif
// No need to check for SUCCESS, we did that above
c->aborted = 1;
apr_brigade_cleanup(b);
return APR_ECONNABORTED;
}
// HELO Command
#ifdef DEBUG
ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL, MODULE_NAME "::helocon_filter_in DEBUG: CMD=HELO CHECK");
#endif
if (strncmp(HELO, ctx->buf, 4) == 0) {
#ifdef DEBUG
ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL, MODULE_NAME "::helocon_filter_in DEBUG: CMD=HELO OK");
#endif
ctx->phase = PHASE_WANT_BINIP;
ctx->mode = AP_MODE_READBYTES;
ctx->need = 4;
ctx->recv = 0;
break;
}
// PROXY Command
#ifdef DEBUG
ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL, MODULE_NAME "::helocon_filter_in DEBUG: CMD=PROXY CHECK");
#endif
if (strncmp(PROXY, ctx->buf, 4) == 0) {
#ifdef DEBUG
ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL, MODULE_NAME "::helocon_filter_in DEBUG: CMD=PROXY OK");
#endif
ctx->phase = PHASE_WANT_LINE;
ctx->mode = AP_MODE_GETLINE;
ctx->need = PROXY_MAX_LENGTH - ctx->offset;
ctx->recv = 0;
break;
}
// ELSE... GET / POST / etc
ctx->phase = PHASE_DONE;
#ifdef DEBUG
ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL, MODULE_NAME "::helocon_filter_in DEBUG from: %s:%d to port=%d newBucket (1) size=%" APR_OFF_T_FMT, _CLIENT_IP, _CLIENT_ADDR->port, c->local_addr->port, ctx->offset);
#endif
// Restore original data
if (ctx->offset) {
e = apr_bucket_heap_create(ctx->buf, ctx->offset, NULL, c->bucket_alloc);
APR_BRIGADE_INSERT_HEAD(b, e);
goto END_CONN;
}
break;
}
case PHASE_WANT_BINIP: {
#ifdef DEBUG
ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL, MODULE_NAME "::helocon_filter_in from: %s:%d to port=%d phase=%d checking=%s", _CLIENT_IP, _CLIENT_ADDR->port, c->local_addr->port, ctx->phase, "BINIP");
#endif
// REWRITE CLIENT IP
const char *new_ip = fromBinIPtoString(c->pool, ctx->buf+4);
if (!new_ip) {
ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL, MODULE_NAME "::helocon_filter_in ERROR: HELO+IP invalid");
goto ABORT_CONN;
}
apr_table_set(c->notes, NOTE_REWRITE_IP, new_ip);
ctx->phase = PHASE_DONE;
#ifdef DEBUG
ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL, MODULE_NAME "::helocon_filter_in DEBUG from: %s:%d to port=%d newip=%s", _CLIENT_IP, _CLIENT_ADDR->port, c->local_addr->port, new_ip);
#endif
break;
}
case PHASE_WANT_LINE: {
#ifdef DEBUG
ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL, MODULE_NAME "::helocon_filter_in from: %s:%d to port=%d phase=%d checking=%s buf=%s", _CLIENT_IP, _CLIENT_ADDR->port, c->local_addr->port, ctx->phase, "LINE", ctx->buf);
#endif
ctx->phase = PHASE_DONE;
char *end = memchr(ctx->buf, '\r', ctx->offset - 1);
if (!end) {
goto ABORT_CONN;
}
if ((end[0] != '\r') || (end[1] != '\n')) {
goto ABORT_CONN;
}
if (!process_proxy_header(f)) {
goto ABORT_CONN;
}
// Restore original data
int count = (ctx->offset - ((end - ctx->buf) + 2));
#ifdef DEBUG
ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL, MODULE_NAME "::helocon_filter_in DEBUG from: %s:%d to port=%d newBucket (2) size=%d rest=%s", _CLIENT_IP, _CLIENT_ADDR->port, c->local_addr->port, count, end + 2);
#endif
if (count > 0) {
e = apr_bucket_heap_create(end + 2, count, NULL, c->bucket_alloc);
APR_BRIGADE_INSERT_HEAD(b, e);
goto END_CONN;
}
break;
}
}
if (ctx->phase == PHASE_DONE) {
#ifdef DEBUG
ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL, MODULE_NAME "::helocon_filter_in from: %s:%d to port=%d phase=%d (DONE)", _CLIENT_IP, _CLIENT_ADDR->port, c->local_addr->port, ctx->phase);
#endif
ctx->mode = mode;
ctx->need = 0;
ctx->recv = 0;
}
break;
}
}
} while (ctx->phase != PHASE_DONE);
END_CONN:
return ap_get_brigade(f->next, b, mode, block, readbytes);
ABORT_CONN:
ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL, MODULE_NAME "::helocon_filter_in ERROR: PROXY protocol header invalid from=%s to port=%d", _CLIENT_IP, c->local_addr->port);
ABORT_CONN2:
c->aborted = 1;
apr_brigade_cleanup(b);
return APR_ECONNABORTED;
}
static int post_read_handler(request_rec *r)
{
conn_rec *c = r->connection;
my_config *conf = ap_get_module_config (c->base_server->module_config, &myfixip_module);
const char *new_ip = NULL;
// Save original IP
save_req_ip(r);
new_ip = apr_table_get(c->notes, NOTE_REWRITE_IP);
if (conf->resetHeader || new_ip || !check_trusted(c, conf)) {
apr_table_unset(r->headers_in, HDR_USERAGENT_IP);
}
if (new_ip) {
// Set Header
apr_table_set(r->headers_in, HDR_USERAGENT_IP, new_ip);
} else {
// Get Header
new_ip = apr_table_get(r->headers_in, HDR_USERAGENT_IP);
}
#ifdef DEBUG
ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL, MODULE_NAME "::post_read_handler IP Connection from: %s:%d [%s] to port=%d newip=%s (OK)", _CLIENT_IP, _CLIENT_ADDR->port, _USERAGENT_IP, c->local_addr->port, new_ip);
#endif
if (new_ip && strcmp(_USERAGENT_IP, new_ip)) { // Change
rewrite_req_ip(r, new_ip);
}
return DECLINED;
}
static void child_init(apr_pool_t *p, server_rec *s)
{
ap_add_version_component(p, MODULE_NAME "/" MODULE_VERSION);
}
static void register_hooks(apr_pool_t *p)
{
static const char *const postread_afterme_list[] = {
"mod_security2.c",
NULL
};
/*
* mod_ssl is AP_FTYPE_CONNECTION + 5 and mod_myfixip needs to
* be called before mod_ssl.
*/
ap_register_input_filter(myfixip_filter_name, helocon_filter_in, NULL, AP_FTYPE_CONNECTION + 9);
ap_hook_post_config(post_config, NULL, NULL, APR_HOOK_MIDDLE);
ap_hook_child_init(child_init, NULL, NULL, APR_HOOK_MIDDLE);
ap_hook_pre_connection(pre_connection, NULL, NULL, APR_HOOK_MIDDLE);
ap_hook_post_read_request(post_read_handler, NULL, postread_afterme_list, APR_HOOK_REALLY_FIRST);
}
module AP_MODULE_DECLARE_DATA myfixip_module = {
STANDARD20_MODULE_STUFF,
NULL, // create per-dir config structures
NULL, // merge per-dir config structures
create_config, // create per-server config structures
merge_config, // merge per-server config structures
cmds, // table of config file commands
register_hooks // register hooks
};