MINOR: Add Mod Defender integration as contrib

This is a service that talks SPOE protocol and uses the Mod Defender (a
NAXSI clone) functionality to detect HTTP attacks. It returns a HTTP
status code to indicate whether the request is suspicious or not, based on
NAXSI rules. The value of the returned code can be used in HAProxy rules
to determine if the HTTP request should be blocked/rejected.
diff --git a/contrib/mod_defender/Makefile b/contrib/mod_defender/Makefile
new file mode 100644
index 0000000..119d824
--- /dev/null
+++ b/contrib/mod_defender/Makefile
@@ -0,0 +1,50 @@
+DESTDIR    =
+PREFIX     = /usr/local
+BINDIR     = $(PREFIX)/bin
+
+CC = gcc
+LD = $(CC)
+
+CXX = g++
+
+ifeq ($(MOD_DEFENDER_SRC),)
+MOD_DEFENDER_SRC := ./mod_defender_src
+endif
+
+ifeq ($(APACHE2_INC),)
+APACHE2_INC := /usr/include/apache2
+endif
+
+ifeq ($(APR_INC),)
+APR_INC := /usr/include/apr-1.0
+endif
+
+CFLAGS  = -g -Wall -pthread
+LDFLAGS = -lpthread  -levent -levent_pthreads -lapr-1 -laprutil-1 -lstdc++
+INCS += -I../../include -I../../ebtree -I$(MOD_DEFENDER_SRC) -I$(APACHE2_INC) -I$(APR_INC)
+LIBS =
+
+CXXFLAGS = -g -std=gnu++11
+CXXINCS += -I$(MOD_DEFENDER_SRC) -I$(MOD_DEFENDER_SRC)/deps -I$(APACHE2_INC) -I$(APR_INC)
+
+SRCS = standalone.o spoa.o defender.o \
+	$(wildcard $(MOD_DEFENDER_SRC)/deps/libinjection/*.c)
+OBJS = $(patsubst %.c, %.o, $(SRCS))
+
+CXXSRCS = $(wildcard $(MOD_DEFENDER_SRC)/*.cpp)
+CXXOBJS = $(patsubst %.cpp, %.o, $(CXXSRCS))
+
+defender: $(OBJS) $(CXXOBJS)
+	$(LD) -o $@ $^ $(LDFLAGS) $(LIBS)
+
+install: defender
+	install defender $(DESTDIR)$(BINDIR)
+
+clean:
+	rm -f defender $(OBJS) $(CXXOBJS)
+
+%.o:	%.c
+	$(CC) $(CFLAGS) $(INCS) -c -o $@ $<
+
+%.o:	%.cpp
+	$(CXX) $(CXXFLAGS) $(CXXINCS) -c -o $@ $<
diff --git a/contrib/mod_defender/README b/contrib/mod_defender/README
new file mode 100644
index 0000000..f417773
--- /dev/null
+++ b/contrib/mod_defender/README
@@ -0,0 +1,159 @@
+                       --------------------------
+                        Mod Defender for HAProxy
+                       --------------------------
+
+
+This is a service that talks SPOE protocol and uses the Mod Defender
+(https://github.com/VultureProject/mod_defender) functionality to detect
+HTTP attacks. It returns a HTTP status code to indicate whether the request
+is suspicious or not, based on NAXSI rules. The value of the returned code
+can be used in HAProxy rules to determine if the HTTP request should be
+blocked/rejected.
+
+Unlike ModSecurity, Mod Defender is a whitelist based WAF (everything is
+disallowed, unless there are rules saying otherwise). It's a partial
+replication of NAXSI and it uses NAXSI compatible rules configuration
+format.
+
+
+1) How to build it
+------------------
+
+Required packages :
+
+    * Mod Defender source (https://github.com/VultureProject/mod_defender)
+    * Asynchronous event notification library and headers (libevent)
+    * Apache 2 (>= 2.4) development headers
+    * APR library and headers
+    * GNU C (gcc) and C++ (g++) >= 4.9
+    * GNU Standard C++ Library v3 (libstdc++)
+    * GNU Make
+
+
+Compile the source :
+
+    $ make MOD_DEFENDER_SRC=/path/to/mod_defender_src
+
+
+2) Configuration
+----------------
+
+Download the Naxsi core rules file :
+
+    $ wget -O /path/to/core.rules \
+    https://raw.githubusercontent.com/nbs-system/naxsi/master/naxsi_config/naxsi_core.rules
+
+
+Create the Mod Defender configuration file. For example :
+
+    # Defender toggle
+    Defender On
+    # Match log path
+    MatchLog /path/to/defender_match.log
+    # JSON Match log path
+    JSONMatchLog /path/to/defender_json_match.log
+    # Request body limit
+    RequestBodyLimit 8388608
+    # Learning mode toggle
+    LearningMode Off
+    # Extensive Learning log toggle
+    ExtensiveLog Off
+    # Libinjection SQL toggle
+    LibinjectionSQL On
+    # Libinjection XSS toggle
+    LibinjectionXSS On
+
+    # Rules
+    Include /path/to/core.rules
+
+    # Score action
+    CheckRule "$SQL >= 8" BLOCK
+    CheckRule "$RFI >= 8" BLOCK
+    CheckRule "$TRAVERSAL >= 4" BLOCK
+    CheckRule "$EVADE >= 4" BLOCK
+    CheckRule "$XSS >= 8" BLOCK
+    CheckRule "$UPLOAD >= 8" BLOCK
+
+    # Whitelists
+    # ....
+
+
+Next step is to configure the SPOE for use with the Mod Defender service.
+Example configuration (args elements order is important) :
+
+    [mod_defender]
+
+    spoe-agent mod-defender-agent
+        messages check-request
+        option   var-prefix    defender
+        timeout  hello         100ms
+        timeout  idle          30s
+        timeout  processing    15ms
+        use-backend spoe-mod-defender
+
+    spoe-message check-request
+        args src unique-id method path query req.ver req.hdrs_bin req.body
+        event on-frontend-http-request
+
+
+The engine is in the scope "mod_defender". To enable it, you must set the
+following line in a frontend/listener section :
+
+    frontend my_frontend
+        ...
+        filter spoe engine mod_defender config /path/to/spoe-mod-defender.conf
+        ...
+
+
+Also, we must define the "spoe-mod-defender" backend in HAProxy configuration :
+
+    backend spoe-mod-defender
+      mode tcp
+      balance roundrobin
+      timeout connect 5s
+      timeout server  3m
+      server defender1 127.0.0.1:12345
+
+
+The Mod Defender status is returned in a variable "sess.defender.status" --
+it contains the returned HTTP status code. The request is considered
+malicious if the variable contains value greater than zero.
+
+The following rule can be used to reject all suspicious HTTP requests :
+
+    http-request deny if { var(sess.defender.status) -m int gt 0 }
+
+
+3) Start the service
+--------------------
+
+To start the service, you need to use "defender" binary :
+
+    $ ./defender -h
+    Usage : ./defender [OPTION]...
+        -h                   Print this message
+        -f <config-file>     Mod Defender configuration file
+        -l <log-file>        Mod Defender log file
+        -d                   Enable the debug mode
+        -m <max-frame-size>  Specify the maximum frame size (default : 16384)
+        -p <port>            Specify the port to listen on (default : 12345)
+        -n <num-workers>     Specify the number of workers (default : 10)
+        -c <capability>      Enable the support of the specified capability
+        -t <time>            Set a delay to process a message (default: 0)
+                               The value is specified in milliseconds by default,
+                               but can be in any other unit if the number is suffixed
+                               by a unit (us, ms, s)
+
+        Supported capabilities: fragmentation, pipelining, async
+
+Example:
+
+    $ ./defender -n 4 -f /path/to/mod_defender.conf -d -l /path/to/error.log
+
+
+4) Known bugs and limitations
+-----------------------------
+
+In its current state, the module is limited by haproxy to the analysis of
+the first buffer. One workaround may consist in significantly increasing
+haproxy's buffer size.
diff --git a/contrib/mod_defender/defender.c b/contrib/mod_defender/defender.c
new file mode 100644
index 0000000..c86d25e
--- /dev/null
+++ b/contrib/mod_defender/defender.c
@@ -0,0 +1,633 @@
+/*
+ * Mod Defender for HAProxy
+ *
+ * Copyright 2017 HAProxy Technologies, Dragan Dosen <ddosen@haproxy.com>
+ *
+ * Mod Defender
+ * Copyright (c) 2017 Annihil (https://github.com/VultureProject/mod_defender)
+ *
+ * Parts of code based on Apache HTTP Server source
+ * Copyright 2015 The Apache Software Foundation (http://www.apache.org/)
+ *
+ * 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
+ * 3 of the License, or (at your option) any later version.
+ *
+ */
+#include <limits.h>
+#include <stdio.h>
+#include <stdarg.h>
+
+#include <common/defaults.h>
+#include <common/standard.h>
+#include <common/chunk.h>
+#include <common/time.h>
+
+#include <proto/spoe.h>
+
+#include <http_core.h>
+#include <http_main.h>
+#include <http_log.h>
+#include <http_request.h>
+
+#include <apr_pools.h>
+#include <apr_strings.h>
+
+#include "spoa.h"
+#include "standalone.h"
+#include "defender.h"
+
+#define DEFENDER_NAME "defender"
+#define DEFENDER_INPUT_FILTER "DEFENDER_IN"
+#define DEFENDER_DEFAULT_UNIQUE_ID "unique_id"
+#define DEFENDER_BRIGADE_REQUEST "defender-brigade-request"
+
+extern module AP_MODULE_DECLARE_DATA defender_module;
+
+DECLARE_HOOK(int,post_config,(apr_pool_t *pconf,apr_pool_t *plog, apr_pool_t *ptemp,server_rec *s))
+DECLARE_HOOK(int,fixups,(request_rec *r))
+DECLARE_HOOK(int,header_parser,(request_rec *r))
+
+char *defender_name = DEFENDER_NAME;
+const char *defender_argv[] = { DEFENDER_NAME, NULL };
+const char *defender_unknown_hostname = "";
+
+void *defender_module_config = NULL;
+static server_rec *server = NULL;
+apr_pool_t *defender_pool = NULL;
+
+char hostname[MAX_HOSTNAME_LEN];
+char defender_cwd[MAXPATHLEN];
+
+static apr_status_t defender_bucket_read(apr_bucket *b, const char **str,
+                                         apr_size_t *len, apr_read_type_e block);
+static void defender_bucket_destroy(void *data);
+
+static const apr_bucket_type_t apr_bucket_type_defender = {
+	"defender", 8, APR_BUCKET_DATA,
+	defender_bucket_destroy,
+	defender_bucket_read,
+	apr_bucket_setaside_noop,
+	apr_bucket_shared_split,
+	apr_bucket_shared_copy
+};
+
+struct apr_bucket_defender {
+	apr_bucket_refcount refcount;
+	struct chunk buf;
+};
+
+static apr_status_t defender_bucket_read(apr_bucket *b, const char **str,
+                                         apr_size_t *len, apr_read_type_e block)
+{
+	struct apr_bucket_defender *d = b->data;
+
+	*str = d->buf.str;
+	*len = d->buf.len;
+
+	return APR_SUCCESS;
+}
+
+static void defender_bucket_destroy(void *data)
+{
+	struct apr_bucket_defender *d = data;
+
+	if (apr_bucket_shared_destroy(d))
+		apr_bucket_free(d);
+}
+
+static apr_bucket *defender_bucket_make(apr_bucket *b, const struct chunk *buf)
+{
+	struct apr_bucket_defender *d;
+
+	d = apr_bucket_alloc(sizeof(*d), b->list);
+
+	d->buf.str = buf->str;
+	d->buf.len = buf->len;
+	d->buf.size = 0;
+
+	b = apr_bucket_shared_make(b, d, 0, buf->len);
+	b->type = &apr_bucket_type_defender;
+	return b;
+}
+
+static apr_bucket *defender_bucket_create(const struct chunk *buf,
+                                          apr_bucket_alloc_t *list)
+{
+	apr_bucket *b = apr_bucket_alloc(sizeof(*b), list);
+
+	APR_BUCKET_INIT(b);
+	b->free = apr_bucket_free;
+	b->list = list;
+	return defender_bucket_make(b, buf);
+}
+
+static void defender_logger(int level, char *str)
+{
+	LOG(&null_worker, "%s", str);
+}
+
+static char *defender_strdup(apr_pool_t *pool, const char *src, uint64_t len)
+{
+	char *dst;
+
+	if (!(dst = apr_pcalloc(pool, len + 1)))
+		return NULL;
+
+	memcpy(dst, src, len);
+	dst[len] = '\0';
+
+	return dst;
+}
+
+static char *defender_printf(apr_pool_t *pool, const char *fmt, ...)
+{
+	va_list argp;
+	char *dst;
+	int len;
+
+	va_start(argp, fmt);
+	len = vsnprintf(NULL, 0, fmt, argp);
+	if (len < 0)
+		return NULL;
+	va_end(argp);
+
+	if (!(dst = apr_pcalloc(pool, len + 1)))
+		return NULL;
+
+	va_start(argp, fmt);
+	len = vsnprintf(dst, len + 1, fmt, argp);
+	va_end(argp);
+
+	return dst;
+}
+
+static char *defender_addr2str(apr_pool_t *pool, struct sample *addr)
+{
+	sa_family_t family;
+	const void *src;
+	char *dst;
+
+	switch (addr->data.type) {
+	case SMP_T_IPV4:
+		src = &addr->data.u.ipv4;
+		family = AF_INET;
+		break;
+	case SMP_T_IPV6:
+		src = &addr->data.u.ipv6;
+		family = AF_INET6;
+		break;
+	default:
+		return NULL;
+	}
+
+	if (!(dst = apr_pcalloc(pool, INET6_ADDRSTRLEN + 1)))
+		return NULL;
+
+	if (inet_ntop(family, src, dst, INET6_ADDRSTRLEN))
+		return dst;
+
+	return NULL;
+}
+
+static void defender_pre_config()
+{
+	apr_pool_t *ptemp = NULL;
+
+	defender_module.module_index = 0;
+	defender_module.register_hooks(defender_pool);
+
+	apr_pool_create(&ptemp, defender_pool);
+	run_ap_hook_post_config(defender_pool, defender_pool, ptemp, server);
+	apr_pool_destroy(ptemp);
+}
+
+static const char *defender_read_config(const char *file)
+{
+	apr_pool_t *ptemp = NULL;
+	const char *err;
+	const char *fullname;
+
+	defender_module_config = defender_module.create_dir_config(defender_pool, "/");
+	if (defender_module_config == NULL) {
+		return "cannot allocate space for the configuration structure";
+	}
+
+	apr_pool_create(&ptemp, defender_pool);
+
+	fullname = ap_server_root_relative(ptemp, file);
+
+	err = read_module_config(server, defender_module_config,
+	                         defender_module.cmds,
+	                         defender_pool, ptemp, fullname);
+
+	apr_pool_destroy(ptemp);
+
+    return err;
+}
+
+static void defender_post_config()
+{
+	apr_pool_t *ptemp = NULL;
+
+	apr_pool_create(&ptemp, defender_pool);
+	run_ap_hook_post_config(defender_pool, defender_pool, ptemp, server);
+	apr_pool_destroy(ptemp);
+}
+
+static const char *defender_set_logger(const char *file)
+{
+	char *logname;
+
+	logger = defender_logger;
+
+	if (file == NULL)
+		return NULL;
+
+	logname = ap_server_root_relative(defender_pool, file);
+
+	if (apr_file_open(&server->error_log, logname,
+	                  APR_APPEND | APR_WRITE | APR_CREATE | APR_LARGEFILE,
+	                  APR_OS_DEFAULT, defender_pool) != APR_SUCCESS) {
+		return apr_pstrcat(defender_pool, "Cannot open log file, ",
+		                   logname, NULL);
+	}
+	server->error_fname = logname;
+
+	return NULL;
+}
+
+static apr_status_t defender_input_filter(ap_filter_t *f,
+                                          apr_bucket_brigade *new_bb,
+                                          ap_input_mode_t mode,
+                                          apr_read_type_e block,
+                                          apr_off_t readbytes)
+{
+	apr_bucket_brigade *bb = NULL;
+	apr_bucket *b = NULL, *a = NULL;
+	apr_status_t rv;
+
+	bb = (apr_bucket_brigade *)apr_table_get(f->r->notes, DEFENDER_BRIGADE_REQUEST);
+
+	if (bb == NULL || (bb && !APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(bb)))) {
+		b = apr_bucket_eos_create(f->c->bucket_alloc);
+		APR_BRIGADE_INSERT_TAIL(new_bb, b);
+		if (bb == NULL)
+			return APR_SUCCESS;
+	}
+
+	rv = apr_brigade_partition(bb, readbytes, &a);
+	if (rv != APR_SUCCESS && rv != APR_INCOMPLETE)
+		return rv;
+
+	b = APR_BRIGADE_FIRST(bb);
+
+	while (b != a) {
+		if (APR_BUCKET_IS_EOS(b))
+			ap_remove_input_filter(f);
+
+		APR_BUCKET_REMOVE(b);
+		APR_BRIGADE_INSERT_TAIL(new_bb, b);
+		b = APR_BRIGADE_FIRST(bb);
+	}
+
+	return APR_SUCCESS;
+}
+
+static conn_rec *defender_create_conn()
+{
+	conn_rec *c = NULL;
+	apr_pool_t *ptrans = NULL;
+
+	apr_pool_create(&ptrans, defender_pool);
+
+	c = apr_pcalloc(ptrans, sizeof(conn_rec));
+
+	c->pool = ptrans;
+	c->local_ip = "127.0.0.1";
+	c->local_addr = server->addrs->host_addr;
+	c->local_host = defender_name;
+	c->client_addr = server->addrs->host_addr;
+	c->remote_host = defender_name;
+
+	c->id = 1;
+	c->base_server = server;
+	c->bucket_alloc = apr_bucket_alloc_create(ptrans);
+
+	return c;
+}
+
+static request_rec *defender_create_request(conn_rec *conn)
+{
+	request_rec *r = NULL;
+	apr_pool_t *p = NULL;
+	struct ap_logconf *l;
+
+	apr_pool_create(&p, conn->pool);
+
+	r = apr_pcalloc(p, sizeof(request_rec));
+
+	r->pool = p;
+	r->connection = conn;
+	r->server = conn->base_server;
+
+	r->headers_in = apr_table_make(p, 25);
+	r->headers_out = apr_table_make(p, 12);
+	r->subprocess_env = apr_table_make(p, 25);
+	r->err_headers_out = apr_table_make(p, 5);
+	r->notes = apr_table_make(p, 5);
+
+	r->request_config = apr_palloc(p, sizeof(void *));
+	r->per_dir_config = apr_palloc(p, sizeof(void *));
+	((void **)r->per_dir_config)[0] = defender_module_config;
+
+	r->handler = defender_name;
+
+	r->parsed_uri.scheme = "http";
+	r->parsed_uri.is_initialized = 1;
+	r->parsed_uri.port = 80;
+	r->parsed_uri.port_str = "80";
+	r->parsed_uri.fragment = "";
+
+	r->input_filters = NULL;
+	r->output_filters = NULL;
+
+	l = apr_pcalloc(p, sizeof(struct ap_logconf));
+	l->level = APLOG_DEBUG;
+	r->log = l;
+
+	return r;
+}
+
+static int defender_process_headers(request_rec *r)
+{
+	return run_ap_hook_header_parser(r);
+}
+
+static int defender_process_body(request_rec *r)
+{
+	ap_add_input_filter(DEFENDER_INPUT_FILTER, NULL, r, r->connection);
+	return run_ap_hook_fixups(r);
+}
+
+int defender_init(const char *config_file, const char *log_file)
+{
+	apr_status_t rv;
+	const char *msg;
+
+	if (!config_file) {
+		LOG(&null_worker, "Mod Defender configuration file not specified.\n");
+		return 0;
+	}
+
+	apr_initialize();
+	apr_pool_create(&defender_pool, NULL);
+	apr_hook_global_pool = defender_pool;
+
+	ap_server_root = getcwd(defender_cwd, APR_PATH_MAX);
+
+	server = (server_rec *) apr_palloc(defender_pool, sizeof(server_rec));
+	server->process = apr_palloc(defender_pool, sizeof(process_rec));
+	server->process->argc = 1;
+	server->process->argv = defender_argv;
+	server->process->short_name = defender_name;
+	server->process->pconf = defender_pool;
+	server->process->pool = defender_pool;
+
+	server->addrs = apr_palloc(defender_pool, sizeof(server_addr_rec));
+	rv = apr_sockaddr_info_get(&server->addrs->host_addr,
+	                           "127.0.0.1", APR_UNSPEC, 0, 0,
+	                           defender_pool);
+	if (rv != APR_SUCCESS) {
+		LOG(&null_worker, "Mod Defender getaddrinfo failed.\n");
+		return 0;
+	}
+
+	server->path = "/";
+	server->pathlen = strlen(server->path);
+	server->port = 0;
+	server->server_admin = defender_name;
+	server->server_scheme = "";
+	server->error_fname = NULL;
+	server->error_log = NULL;
+	server->limit_req_line = DEFAULT_LIMIT_REQUEST_LINE;
+	server->limit_req_fieldsize = DEFAULT_LIMIT_REQUEST_FIELDSIZE;
+	server->limit_req_fields = DEFAULT_LIMIT_REQUEST_FIELDS;
+	server->timeout = apr_time_from_sec(DEFAULT_TIMEOUT);
+
+	memset(hostname, 0, sizeof(hostname));
+	gethostname(hostname, sizeof(hostname) - 1);
+	server->server_hostname = hostname;
+
+	server->addrs->host_port = 0;
+	server->names = server->wild_names = NULL;
+	server->is_virtual = 0;
+
+	server->lookup_defaults = NULL;
+	server->module_config = NULL;
+
+	msg = defender_set_logger(log_file);
+	if (msg != NULL) {
+		LOG(&null_worker, "Mod Defender init failed: %s\n", msg);
+		return 0;
+	}
+
+	ap_register_input_filter(DEFENDER_INPUT_FILTER, defender_input_filter,
+	                         NULL, AP_FTYPE_RESOURCE);
+
+	defender_pre_config();
+
+	msg = defender_read_config(config_file);
+	if (msg != NULL) {
+		LOG(&null_worker, "Mod Defender configuration failed: %s\n", msg);
+		return 0;
+	}
+
+	defender_post_config();
+
+	return 1;
+}
+
+int defender_process_request(struct worker *worker, struct defender_request *request)
+{
+	struct conn_rec *c = NULL;
+	struct request_rec *r = NULL;
+
+	struct apr_bucket_brigade *bb = NULL;
+	struct apr_bucket *d = NULL, *e = NULL;
+
+	struct chunk *method;
+	struct chunk *path;
+	struct chunk *query;
+	struct chunk *version;
+	struct chunk *body;
+
+	struct defender_header hdr;
+	char *hdr_ptr, *hdr_end;
+
+	const char *ptr;
+
+	int status = DECLINED;
+
+	if (!(c = defender_create_conn()))
+		goto out;
+
+	if (!(r = defender_create_request(c)))
+		goto out;
+
+	/* request */
+	r->request_time = apr_time_now();
+
+	if (request->clientip.data.type != SMP_T_IPV4 &&
+	    request->clientip.data.type != SMP_T_IPV6)
+		goto out;
+
+	if (!(r->useragent_ip = defender_addr2str(r->pool, &request->clientip)))
+		goto out;
+
+	if (request->id.data.u.str.str && request->id.data.u.str.len > 0) {
+		apr_table_setn(r->subprocess_env, "UNIQUE_ID",
+		               defender_strdup(r->pool, request->id.data.u.str.str,
+		                               request->id.data.u.str.len));
+	}
+	else {
+		apr_table_setn(r->subprocess_env, "UNIQUE_ID",
+		               DEFENDER_DEFAULT_UNIQUE_ID);
+	}
+
+	method = &request->method.data.u.str;
+	path = &request->path.data.u.str;
+	query = &request->query.data.u.str;
+	version = &request->version.data.u.str;
+
+	r->method_number = lookup_builtin_method(method->str, method->len);
+	if (!(r->method = defender_strdup(r->pool, method->str, method->len)))
+		goto out;
+
+	r->unparsed_uri = defender_printf(r->pool, "%.*s%s%.*s",
+	                                  path->len, path->str,
+	                                  query->len > 0 ? "?" : "",
+	                                  query->len, query->str);
+	if (!r->unparsed_uri)
+		goto out;
+
+	if (!(r->uri = defender_strdup(r->pool, path->str, path->len)))
+		goto out;
+
+	r->parsed_uri.path = r->filename = r->uri;
+
+	if (!(r->args = defender_strdup(r->pool, query->str, query->len)))
+		goto out;
+
+	r->parsed_uri.query = r->args;
+
+	r->protocol = defender_printf(r->pool, "%s%.*s",
+	                              version->len > 0 ? "HTTP/" : "",
+	                              version->len, version->str);
+	if (!r->protocol)
+		goto out;
+
+	r->the_request = defender_printf(r->pool, "%.*s %s%s%s",
+	                                 method->len, method->str,
+	                                 r->unparsed_uri,
+	                                 version->len > 0 ? " " : "",
+	                                 r->protocol);
+	if (!r->the_request)
+		goto out;
+
+	/* headers */
+	if (request->headers.data.type != SMP_T_BIN)
+		goto misc;
+
+	hdr_ptr = request->headers.data.u.str.str;
+	hdr_end = hdr_ptr + request->headers.data.u.str.len;
+
+	while (1) {
+		memset(&hdr, 0, sizeof(hdr));
+
+		if (decode_varint(&hdr_ptr, hdr_end, &hdr.name.len) == -1)
+			goto out;
+		if (!(hdr.name.str = defender_strdup(r->pool, hdr_ptr, hdr.name.len)))
+			goto out;
+
+		hdr_ptr += hdr.name.len;
+		if (hdr_ptr > hdr_end)
+			goto out;
+
+		if (decode_varint(&hdr_ptr, hdr_end, &hdr.value.len) == -1)
+			goto out;
+		if (!(hdr.value.str = defender_strdup(r->pool, hdr_ptr, hdr.value.len)))
+			goto out;
+
+		hdr_ptr += hdr.value.len;
+		if (hdr_ptr > hdr_end)
+			goto out;
+
+		if (!hdr.name.len && !hdr.value.len)
+			break;
+
+		apr_table_setn(r->headers_in, hdr.name.str, hdr.value.str);
+	}
+
+misc:
+
+	r->hostname = apr_table_get(r->headers_in, "Host");
+	if (!r->hostname)
+		r->hostname = defender_unknown_hostname;
+	r->parsed_uri.hostname = (char *)r->hostname;
+
+	r->content_type = apr_table_get(r->headers_in, "Content-Type");
+	r->content_encoding = apr_table_get(r->headers_in, "Content-Encoding");
+	ptr = apr_table_get(r->headers_in, "Content-Length");
+	if (ptr)
+		r->clength = strtol(ptr, NULL, 10);
+
+	/* body */
+	body = &request->body.data.u.str;
+
+	bb = apr_brigade_create(r->pool, c->bucket_alloc);
+	if (bb == NULL)
+		goto out;
+
+	d = defender_bucket_create(body, c->bucket_alloc);
+	if (d == NULL)
+		goto out;
+
+	APR_BRIGADE_INSERT_TAIL(bb, d);
+
+	e = apr_bucket_eos_create(c->bucket_alloc);
+	APR_BRIGADE_INSERT_TAIL(bb, e);
+
+	apr_table_setn(r->notes, DEFENDER_BRIGADE_REQUEST, (char *)bb);
+
+	/* process */
+	status = defender_process_headers(r);
+
+	if (status == DECLINED)
+		status = defender_process_body(r);
+
+	apr_brigade_cleanup(bb);
+
+	/* success */
+	if (status == DECLINED)
+		status = OK;
+
+out:
+
+	if (r && r->pool) {
+		apr_table_clear(r->headers_in);
+		apr_table_clear(r->headers_out);
+		apr_table_clear(r->subprocess_env);
+		apr_table_clear(r->err_headers_out);
+		apr_table_clear(r->notes);
+		apr_pool_destroy(r->pool);
+	}
+
+	if (c && c->pool) {
+		apr_bucket_alloc_destroy(c->bucket_alloc);
+		apr_pool_destroy(c->pool);
+	}
+
+	return status;
+}
diff --git a/contrib/mod_defender/defender.h b/contrib/mod_defender/defender.h
new file mode 100644
index 0000000..088416e
--- /dev/null
+++ b/contrib/mod_defender/defender.h
@@ -0,0 +1,42 @@
+/*
+ * Mod Defender for HAProxy
+ *
+ * Copyright 2017 HAProxy Technologies, Dragan Dosen <ddosen@haproxy.com>
+ *
+ * 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
+ * 3 of the License, or (at your option) any later version.
+ *
+ */
+#ifndef __DEFENDER_H__
+#define __DEFENDER_H__
+
+#include <types/sample.h>
+
+struct defender_request {
+	struct sample clientip;
+	struct sample id;
+	struct sample method;
+	struct sample path;
+	struct sample query;
+	struct sample version;
+	struct sample headers;
+	struct sample body;
+};
+
+struct defender_header {
+	struct {
+		char     *str;
+		uint64_t  len;
+	} name;
+	struct {
+		char     *str;
+		uint64_t  len;
+	} value;
+};
+
+int defender_init(const char *config_file, const char *log_file);
+int defender_process_request(struct worker *worker, struct defender_request *request);
+
+#endif /* __DEFENDER_H__ */
diff --git a/contrib/mod_defender/spoa.c b/contrib/mod_defender/spoa.c
new file mode 100644
index 0000000..c4e15bb
--- /dev/null
+++ b/contrib/mod_defender/spoa.c
@@ -0,0 +1,1886 @@
+/*
+ * Mod Defender for HAProxy
+ *
+ * Copyright 2017 HAProxy Technologies, Dragan Dosen <ddosen@haproxy.com>
+ *
+ * Based on "A Random IP reputation service acting as a Stream Processing Offload Agent"
+ * Copyright 2016 HAProxy Technologies, Christopher Faulet <cfaulet@haproxy.com>
+ *
+ * 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
+ * 3 of the License, or (at your option) any later version.
+ *
+ */
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+#include <errno.h>
+#include <stdio.h>
+#include <signal.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <err.h>
+#include <ctype.h>
+
+#include <pthread.h>
+
+#include <event2/util.h>
+#include <event2/event.h>
+#include <event2/event_struct.h>
+#include <event2/thread.h>
+
+#include <common/mini-clist.h>
+#include <common/chunk.h>
+
+#include <proto/spoe.h>
+
+#include "spoa.h"
+#include "defender.h"
+
+#define DEFAULT_PORT       12345
+#define CONNECTION_BACKLOG 10
+#define NUM_WORKERS        10
+#define MAX_FRAME_SIZE     16384
+#define SPOP_VERSION       "1.0"
+
+#define SLEN(str) (sizeof(str)-1)
+
+#define DEBUG(x...)				\
+	do {					\
+		if (debug)			\
+			LOG(x);			\
+	} while (0)
+
+
+enum spoa_state {
+	SPOA_ST_CONNECTING = 0,
+	SPOA_ST_PROCESSING,
+	SPOA_ST_DISCONNECTING,
+};
+
+enum spoa_frame_type {
+	SPOA_FRM_T_UNKNOWN = 0,
+	SPOA_FRM_T_HAPROXY,
+	SPOA_FRM_T_AGENT,
+};
+
+struct spoe_engine {
+	char       *id;
+
+	struct list processing_frames;
+	struct list outgoing_frames;
+
+	struct list clients;
+	struct list list;
+};
+
+struct spoe_frame {
+	enum spoa_frame_type type;
+	char                *buf;
+	unsigned int         offset;
+	unsigned int         len;
+
+	unsigned int         stream_id;
+	unsigned int         frame_id;
+	unsigned int         flags;
+	bool                 hcheck;          /* true is the CONNECT frame is a healthcheck */
+	bool                 fragmented;      /* true if the frame is fragmented */
+	int                  defender_status; /* mod_defender returned status */
+
+	struct event         process_frame_event;
+	struct worker       *worker;
+	struct spoe_engine  *engine;
+	struct client       *client;
+	struct list          list;
+
+	char                *frag_buf; /* used to accumulate payload of a fragmented frame */
+	unsigned int         frag_len;
+
+	char                 data[0];
+};
+
+struct client {
+	int                 fd;
+	unsigned long       id;
+	enum spoa_state     state;
+
+	struct event        read_frame_event;
+	struct event        write_frame_event;
+
+	struct spoe_frame  *incoming_frame;
+	struct spoe_frame  *outgoing_frame;
+
+	struct list         processing_frames;
+	struct list         outgoing_frames;
+
+	unsigned int        max_frame_size;
+	int                 status_code;
+
+	char               *engine_id;
+	struct spoe_engine *engine;
+	bool                pipelining;
+	bool                async;
+	bool                fragmentation;
+
+	struct worker      *worker;
+	struct list         by_worker;
+	struct list         by_engine;
+};
+
+/* Globals */
+static struct worker *workers          = NULL;
+struct worker         null_worker      = { .id = 0 };
+static unsigned long  clicount         = 0;
+static int            server_port      = DEFAULT_PORT;
+static int            num_workers      = NUM_WORKERS;
+static unsigned int   max_frame_size   = MAX_FRAME_SIZE;
+struct timeval        processing_delay = {0, 0};
+static bool           debug            = false;
+static bool           pipelining       = false;
+static bool           async            = false;
+static bool           fragmentation    = false;
+
+
+static const char *spoe_frm_err_reasons[SPOE_FRM_ERRS] = {
+	[SPOE_FRM_ERR_NONE]               = "normal",
+	[SPOE_FRM_ERR_IO]                 = "I/O error",
+	[SPOE_FRM_ERR_TOUT]               = "a timeout occurred",
+	[SPOE_FRM_ERR_TOO_BIG]            = "frame is too big",
+	[SPOE_FRM_ERR_INVALID]            = "invalid frame received",
+	[SPOE_FRM_ERR_NO_VSN]             = "version value not found",
+	[SPOE_FRM_ERR_NO_FRAME_SIZE]      = "max-frame-size value not found",
+	[SPOE_FRM_ERR_NO_CAP]             = "capabilities value not found",
+	[SPOE_FRM_ERR_BAD_VSN]            = "unsupported version",
+	[SPOE_FRM_ERR_BAD_FRAME_SIZE]     = "max-frame-size too big or too small",
+	[SPOE_FRM_ERR_FRAG_NOT_SUPPORTED] = "fragmentation not supported",
+	[SPOE_FRM_ERR_INTERLACED_FRAMES]  = "invalid interlaced frames",
+	[SPOE_FRM_ERR_FRAMEID_NOTFOUND]   = "frame-id not found",
+	[SPOE_FRM_ERR_RES]                = "resource allocation error",
+	[SPOE_FRM_ERR_UNKNOWN]            = "an unknown error occurred",
+};
+
+static void signal_cb(evutil_socket_t, short, void *);
+static void accept_cb(evutil_socket_t, short, void *);
+static void worker_monitor_cb(evutil_socket_t, short, void *);
+static void process_frame_cb(evutil_socket_t, short, void *);
+static void read_frame_cb(evutil_socket_t, short, void *);
+static void write_frame_cb(evutil_socket_t, short, void *);
+
+static void use_spoe_engine(struct client *);
+static void unuse_spoe_engine(struct client *);
+static void release_frame(struct spoe_frame *);
+static void release_client(struct client *);
+
+/* Check the protocol version. It returns -1 if an error occurred, the number of
+ * read bytes otherwise. */
+static int
+check_proto_version(struct spoe_frame *frame, char **buf, char *end)
+{
+	char      *str, *p = *buf;
+	uint64_t   sz;
+	int        ret;
+
+	/* Get the list of all supported versions by HAProxy */
+	if ((*p++ & SPOE_DATA_T_MASK) != SPOE_DATA_T_STR)
+		return -1;
+	ret = spoe_decode_buffer(&p, end, &str, &sz);
+	if (ret == -1 || !str)
+		return -1;
+
+	DEBUG(frame->worker, "<%lu> Supported versions : %.*s",
+	      frame->client->id, (int)sz, str);
+
+	/* TODO: Find the right verion in supported ones */
+
+	ret  = (p - *buf);
+	*buf = p;
+	return ret;
+}
+
+/* Check max frame size value. It returns -1 if an error occurred, the number of
+ * read bytes otherwise. */
+static int
+check_max_frame_size(struct spoe_frame *frame, char **buf, char *end)
+{
+	char    *p = *buf;
+	uint64_t sz;
+	int      type, ret;
+
+	/* Get the max-frame-size value of HAProxy */
+	type =  *p++;
+	if ((type & SPOE_DATA_T_MASK) != SPOE_DATA_T_INT32  &&
+	    (type & SPOE_DATA_T_MASK) != SPOE_DATA_T_INT64  &&
+	    (type & SPOE_DATA_T_MASK) != SPOE_DATA_T_UINT32 &&
+	    (type & SPOE_DATA_T_MASK) != SPOE_DATA_T_UINT64)
+		return -1;
+	if (decode_varint(&p, end, &sz) == -1)
+		return -1;
+
+	/* Keep the lower value */
+	if (sz < frame->client->max_frame_size)
+		frame->client->max_frame_size = sz;
+
+	DEBUG(frame->worker, "<%lu> HAProxy maximum frame size : %u",
+	      frame->client->id, (unsigned int)sz);
+
+	ret  = (p - *buf);
+	*buf = p;
+	return ret;
+}
+
+/* Check healthcheck value. It returns -1 if an error occurred, the number of
+ * read bytes otherwise. */
+static int
+check_healthcheck(struct spoe_frame *frame, char **buf, char *end)
+{
+	char *p = *buf;
+	int   type, ret;
+
+	/* Get the "healthcheck" value */
+	type = *p++;
+	if ((type & SPOE_DATA_T_MASK) != SPOE_DATA_T_BOOL)
+		return -1;
+	frame->hcheck = ((type & SPOE_DATA_FL_TRUE) == SPOE_DATA_FL_TRUE);
+
+	DEBUG(frame->worker, "<%lu> HELLO healthcheck : %s",
+	      frame->client->id, (frame->hcheck ? "true" : "false"));
+
+	ret  = (p - *buf);
+	*buf = p;
+	return ret;
+}
+
+/* Check capabilities value. It returns -1 if an error occurred, the number of
+ * read bytes otherwise. */
+static int
+check_capabilities(struct spoe_frame *frame, char **buf, char *end)
+{
+	struct client *client = frame->client;
+	char          *str, *p = *buf;
+	uint64_t       sz;
+	int            ret;
+
+	if ((*p++ & SPOE_DATA_T_MASK) != SPOE_DATA_T_STR)
+		return -1;
+	if (spoe_decode_buffer(&p, end, &str, &sz) == -1)
+		return -1;
+	if (str == NULL) /* this is not an error */
+		goto end;
+
+	DEBUG(frame->worker, "<%lu> HAProxy capabilities : %.*s",
+	      client->id, (int)sz, str);
+
+	while (sz) {
+		char *delim;
+
+		/* Skip leading spaces */
+		for (; isspace(*str) && sz; sz--);
+
+		if (sz >= 10 && !strncmp(str, "pipelining", 10)) {
+			str += 10; sz -= 10;
+			if (!sz || isspace(*str) || *str == ',') {
+				DEBUG(frame->worker,
+				      "<%lu> HAProxy supports frame pipelining",
+				      client->id);
+				client->pipelining = true;
+			}
+		}
+		else if (sz >= 5 && !strncmp(str, "async", 5)) {
+			str += 5; sz -= 5;
+			if (!sz || isspace(*str) || *str == ',') {
+				DEBUG(frame->worker,
+				      "<%lu> HAProxy supports asynchronous frame",
+				      client->id);
+				client->async = true;
+			}
+		}
+		else if (sz >= 13 && !strncmp(str, "fragmentation", 13)) {
+			str += 13; sz -= 13;
+			if (!sz || isspace(*str) || *str == ',') {
+				DEBUG(frame->worker,
+				      "<%lu> HAProxy supports fragmented frame",
+				      client->id);
+				client->fragmentation = true;
+			}
+		}
+
+		if (!sz || (delim = memchr(str, ',', sz)) == NULL)
+			break;
+		delim++;
+		sz -= (delim - str);
+		str = delim;
+	}
+  end:
+	ret  = (p - *buf);
+	*buf = p;
+	return ret;
+}
+
+/* Check engine-id value. It returns -1 if an error occurred, the number of
+ * read bytes otherwise. */
+static int
+check_engine_id(struct spoe_frame *frame, char **buf, char *end)
+{
+	struct client *client = frame->client;
+	char          *str, *p = *buf;
+	uint64_t       sz;
+	int            ret;
+
+	if ((*p++ & SPOE_DATA_T_MASK) != SPOE_DATA_T_STR)
+		return -1;
+
+	if (spoe_decode_buffer(&p, end, &str, &sz) == -1)
+		return -1;
+	if (str == NULL) /* this is not an error */
+		goto end;
+
+	if (client->engine != NULL)
+		goto end;
+
+	DEBUG(frame->worker, "<%lu> HAProxy engine id : %.*s",
+	      client->id, (int)sz, str);
+
+	client->engine_id = strndup(str, (int)sz);
+  end:
+	ret  = (p - *buf);
+	*buf = p;
+	return ret;
+}
+
+static int
+acc_payload(struct spoe_frame *frame)
+{
+	struct client *client = frame->client;
+	char          *buf;
+	size_t         len = frame->len - frame->offset;
+	int            ret = frame->offset;
+
+	/* No need to accumulation payload */
+	if (frame->fragmented == false)
+		return ret;
+
+	buf = realloc(frame->frag_buf, frame->frag_len + len);
+	if (buf == NULL) {
+		client->status_code = SPOE_FRM_ERR_RES;
+		return -1;
+	}
+	memcpy(buf + frame->frag_len, frame->buf + frame->offset, len);
+	frame->frag_buf  = buf;
+	frame->frag_len += len;
+
+	if (!(frame->flags & SPOE_FRM_FL_FIN)) {
+		/* Wait for next parts */
+		frame->buf    = (char *)(frame->data);
+		frame->offset = 0;
+		frame->len    = 0;
+		frame->flags  = 0;
+		return 1;
+	}
+
+	frame->buf    = frame->frag_buf;
+	frame->len    = frame->frag_len;
+	frame->offset = 0;
+	return ret;
+}
+
+/* Check disconnect status code. It returns -1 if an error occurred, the number
+ * of read bytes otherwise. */
+static int
+check_discon_status_code(struct spoe_frame *frame, char **buf, char *end)
+{
+	char    *p = *buf;
+	uint64_t sz;
+	int      type, ret;
+
+	/* Get the "status-code" value */
+	type =  *p++;
+	if ((type & SPOE_DATA_T_MASK) != SPOE_DATA_T_INT32 &&
+	    (type & SPOE_DATA_T_MASK) != SPOE_DATA_T_INT64 &&
+	    (type & SPOE_DATA_T_MASK) != SPOE_DATA_T_UINT32 &&
+	    (type & SPOE_DATA_T_MASK) != SPOE_DATA_T_UINT64)
+		return -1;
+	if (decode_varint(&p, end, &sz) == -1)
+		return -1;
+
+	frame->client->status_code = (unsigned int)sz;
+
+	DEBUG(frame->worker, "<%lu> Disconnect status code : %u",
+	      frame->client->id, frame->client->status_code);
+
+	ret  = (p - *buf);
+	*buf = p;
+	return ret;
+}
+
+/* Check the disconnect message. It returns -1 if an error occurred, the number
+ * of read bytes otherwise. */
+static int
+check_discon_message(struct spoe_frame *frame, char **buf, char *end)
+{
+	char    *str, *p = *buf;
+	uint64_t sz;
+	int      ret;
+
+	/* Get the "message" value */
+	if ((*p++ & SPOE_DATA_T_MASK) != SPOE_DATA_T_STR)
+		return -1;
+	ret = spoe_decode_buffer(&p, end, &str, &sz);
+	if (ret == -1 || !str)
+		return -1;
+
+	DEBUG(frame->worker, "<%lu> Disconnect message : %.*s",
+	      frame->client->id, (int)sz, str);
+
+	ret  = (p - *buf);
+	*buf = p;
+	return ret;
+}
+
+
+
+/* Decode a HELLO frame received from HAProxy. It returns -1 if an error
+ * occurred, otherwise the number of read bytes. HELLO frame cannot be
+ * ignored and having another frame than a HELLO frame is an error. */
+static int
+handle_hahello(struct spoe_frame *frame)
+{
+	struct client *client = frame->client;
+	char          *p, *end;
+
+	p = frame->buf;
+	end = frame->buf + frame->len;
+
+	/* Check frame type: we really want a HELLO frame */
+	if (*p++ != SPOE_FRM_T_HAPROXY_HELLO)
+		goto error;
+
+	DEBUG(frame->worker, "<%lu> Decode HAProxy HELLO frame", client->id);
+
+	/* Retrieve flags */
+	memcpy((char *)&(frame->flags), p, 4);
+	p += 4;
+
+	/* Fragmentation is not supported for HELLO frame */
+	if (!(frame->flags & SPOE_FRM_FL_FIN)) {
+		client->status_code = SPOE_FRM_ERR_FRAG_NOT_SUPPORTED;
+		goto error;
+	}
+
+	/* stream-id and frame-id must be cleared */
+	if (*p != 0 || *(p+1) != 0) {
+		client->status_code = SPOE_FRM_ERR_INVALID;
+		goto error;
+	}
+	p += 2;
+
+	/* Loop on K/V items */
+	while (p < end) {
+		char     *str;
+		uint64_t  sz;
+
+		/* Decode the item name */
+		spoe_decode_buffer(&p, end, &str, &sz);
+		if (!str) {
+			client->status_code = SPOE_FRM_ERR_INVALID;
+			goto error;
+		}
+
+		/* Check "supported-versions" K/V item */
+		if (!memcmp(str, "supported-versions", sz)) {
+			if (check_proto_version(frame, &p, end)  == -1) {
+				client->status_code = SPOE_FRM_ERR_INVALID;
+				goto error;
+			}
+		}
+		/* Check "max-frame-size" K/V item */
+		else if (!memcmp(str, "max-frame-size", sz)) {
+			if (check_max_frame_size(frame, &p, end) == -1) {
+				client->status_code = SPOE_FRM_ERR_INVALID;
+				goto error;
+			}
+		}
+		/* Check "healthcheck" K/V item */
+		else if (!memcmp(str, "healthcheck", sz)) {
+			if (check_healthcheck(frame, &p, end) == -1) {
+				client->status_code = SPOE_FRM_ERR_INVALID;
+				goto error;
+			}
+		}
+		/* Check "capabilities" K/V item */
+		else if (!memcmp(str, "capabilities", sz)) {
+			if (check_capabilities(frame, &p, end) == -1) {
+				client->status_code = SPOE_FRM_ERR_INVALID;
+				goto error;
+			}
+		}
+		/* Check "engine-id" K/V item */
+		else if (!memcmp(str, "engine-id", sz)) {
+			if (check_engine_id(frame, &p, end) == -1) {
+				client->status_code = SPOE_FRM_ERR_INVALID;
+				goto error;
+			}
+		}
+		else {
+			DEBUG(frame->worker, "<%lu> Skip K/V item : key=%.*s",
+			      client->id, (int)sz, str);
+
+			/* Silently ignore unknown item */
+			if (spoe_skip_data(&p, end) == -1) {
+				client->status_code = SPOE_FRM_ERR_INVALID;
+				goto error;
+			}
+		}
+	}
+
+	if (async == false || client->engine_id == NULL)
+		client->async = false;
+	if (pipelining == false)
+		client->pipelining = false;
+
+	if (client->async == true)
+		use_spoe_engine(client);
+
+	return (p - frame->buf);
+  error:
+	return -1;
+}
+
+/* Decode a DISCONNECT frame received from HAProxy. It returns -1 if an error
+ * occurred, otherwise the number of read bytes. DISCONNECT frame cannot be
+ * ignored and having another frame than a DISCONNECT frame is an error.*/
+static int
+handle_hadiscon(struct spoe_frame *frame)
+{
+	struct client *client = frame->client;
+	char          *p, *end;
+
+	p = frame->buf;
+	end = frame->buf + frame->len;
+
+	/* Check frame type: we really want a DISCONNECT frame */
+	if (*p++ != SPOE_FRM_T_HAPROXY_DISCON)
+		goto error;
+
+	DEBUG(frame->worker, "<%lu> Decode HAProxy DISCONNECT frame", client->id);
+
+	/* Retrieve flags */
+	memcpy((char *)&(frame->flags), p, 4);
+	p += 4;
+
+	/* Fragmentation is not supported for DISCONNECT frame */
+	if (!(frame->flags & SPOE_FRM_FL_FIN)) {
+		client->status_code = SPOE_FRM_ERR_FRAG_NOT_SUPPORTED;
+		goto error;
+	}
+
+	/* stream-id and frame-id must be cleared */
+	if (*p != 0 || *(p+1) != 0) {
+		client->status_code = SPOE_FRM_ERR_INVALID;
+		goto error;
+	}
+	p += 2;
+
+	client->status_code = SPOE_FRM_ERR_NONE;
+
+	/* Loop on K/V items */
+	while (p < end) {
+		char     *str;
+		uint64_t  sz;
+
+		/* Decode item key */
+		spoe_decode_buffer(&p, end, &str, &sz);
+		if (!str) {
+			client->status_code = SPOE_FRM_ERR_INVALID;
+			goto error;
+		}
+
+		/* Check "status-code" K/V item */
+		if (!memcmp(str, "status-code", sz)) {
+			if (check_discon_status_code(frame, &p, end) == -1) {
+				client->status_code = SPOE_FRM_ERR_INVALID;
+				goto error;
+			}
+		}
+		/* Check "message" K/V item */
+		else if (!memcmp(str, "message", sz)) {
+			if (check_discon_message(frame, &p, end) == -1) {
+				client->status_code = SPOE_FRM_ERR_INVALID;
+				goto error;
+			}
+		}
+		else {
+			DEBUG(frame->worker, "<%lu> Skip K/V item : key=%.*s",
+			      client->id, (int)sz, str);
+
+			/* Silently ignore unknown item */
+			if (spoe_skip_data(&p, end) == -1) {
+				client->status_code = SPOE_FRM_ERR_INVALID;
+				goto error;
+			}
+		}
+	}
+
+	return (p - frame->buf);
+  error:
+	return -1;
+}
+
+/* Decode a NOTIFY frame received from HAProxy. It returns -1 if an error
+ * occurred, 0 if it must be must be ignored, otherwise the number of read
+ * bytes. */
+static int
+handle_hanotify(struct spoe_frame *frame)
+{
+	struct client *client = frame->client;
+	char          *p, *end;
+	uint64_t       stream_id, frame_id;
+
+	p = frame->buf;
+	end = frame->buf + frame->len;
+
+	/* Check frame type */
+	if (*p++ != SPOE_FRM_T_HAPROXY_NOTIFY)
+		goto ignore;
+
+	DEBUG(frame->worker, "<%lu> Decode HAProxy NOTIFY frame", client->id);
+
+	/* Retrieve flags */
+	memcpy((char *)&(frame->flags), p, 4);
+	p += 4;
+
+	/* Fragmentation is not supported */
+	if (!(frame->flags & SPOE_FRM_FL_FIN) && fragmentation == false) {
+		client->status_code = SPOE_FRM_ERR_FRAG_NOT_SUPPORTED;
+		goto error;
+	}
+
+	/* Read the stream-id and frame-id */
+	if (decode_varint(&p, end, &stream_id) == -1)
+		goto ignore;
+	if (decode_varint(&p, end, &frame_id) == -1)
+		goto ignore;
+
+	frame->stream_id = (unsigned int)stream_id;
+	frame->frame_id  = (unsigned int)frame_id;
+
+	DEBUG(frame->worker, "<%lu> STREAM-ID=%u - FRAME-ID=%u"
+	      " - %s frame received"
+	      " - frag_len=%u - len=%u - offset=%ld",
+	      client->id, frame->stream_id, frame->frame_id,
+	      (frame->flags & SPOE_FRM_FL_FIN) ? "unfragmented" : "fragmented",
+	      frame->frag_len, frame->len, p - frame->buf);
+
+	frame->fragmented = !(frame->flags & SPOE_FRM_FL_FIN);
+	frame->offset = (p - frame->buf);
+	return acc_payload(frame);
+
+  ignore:
+	return 0;
+
+  error:
+	return -1;
+}
+
+/* Decode next part of a fragmented frame received from HAProxy. It returns -1
+ * if an error occurred, 0 if it must be must be ignored, otherwise the number
+ * of read bytes. */
+static int
+handle_hafrag(struct spoe_frame *frame)
+{
+	struct client *client = frame->client;
+	char          *p, *end;
+	uint64_t       stream_id, frame_id;
+
+	p = frame->buf;
+	end = frame->buf + frame->len;
+
+	/* Check frame type */
+	if (*p++ != SPOE_FRM_T_UNSET)
+		goto ignore;
+
+	DEBUG(frame->worker, "<%lu> Decode Next part of a fragmented frame", client->id);
+
+	/* Fragmentation is not supported */
+	if (fragmentation == false) {
+		client->status_code = SPOE_FRM_ERR_FRAG_NOT_SUPPORTED;
+		goto error;
+	}
+
+	/* Retrieve flags */
+	memcpy((char *)&(frame->flags), p, 4);
+	p+= 4;
+
+	/* Read the stream-id and frame-id */
+	if (decode_varint(&p, end, &stream_id) == -1)
+		goto ignore;
+	if (decode_varint(&p, end, &frame_id) == -1)
+		goto ignore;
+
+	if (frame->fragmented == false                  ||
+	    frame->stream_id != (unsigned int)stream_id ||
+	    frame->frame_id  != (unsigned int)frame_id) {
+		client->status_code = SPOE_FRM_ERR_INTERLACED_FRAMES;
+		goto error;
+	}
+
+	if (frame->flags & SPOE_FRM_FL_ABRT) {
+		DEBUG(frame->worker, "<%lu> STREAM-ID=%u - FRAME-ID=%u"
+		      " - Abort processing of a fragmented frame"
+		      " - frag_len=%u - len=%u - offset=%ld",
+		      client->id, frame->stream_id, frame->frame_id,
+		      frame->frag_len, frame->len, p - frame->buf);
+		goto ignore;
+	}
+
+	DEBUG(frame->worker, "<%lu> STREAM-ID=%u - FRAME-ID=%u"
+	      " - %s fragment of a fragmented frame received"
+	      " - frag_len=%u - len=%u - offset=%ld",
+	      client->id, frame->stream_id, frame->frame_id,
+	      (frame->flags & SPOE_FRM_FL_FIN) ? "last" : "next",
+	      frame->frag_len, frame->len, p - frame->buf);
+
+	frame->offset = (p - frame->buf);
+	return acc_payload(frame);
+
+  ignore:
+	return 0;
+
+  error:
+	return -1;
+}
+
+/* Encode a HELLO frame to send it to HAProxy. It returns the number of written
+ * bytes. */
+static int
+prepare_agenthello(struct spoe_frame *frame)
+{
+	struct client *client = frame->client;
+	char          *p, *end;
+	char           capabilities[64];
+	int            n;
+	unsigned int   flags  = SPOE_FRM_FL_FIN;
+
+	DEBUG(frame->worker, "<%lu> Encode Agent HELLO frame", client->id);
+	frame->type = SPOA_FRM_T_AGENT;
+
+	p   = frame->buf;
+	end = frame->buf+max_frame_size;
+
+	/* Frame Type */
+	*p++ = SPOE_FRM_T_AGENT_HELLO;
+
+	/* Set flags */
+	memcpy(p, (char *)&flags, 4);
+	p += 4;
+
+	/* No stream-id and frame-id for HELLO frames */
+	*p++ = 0;
+	*p++ = 0;
+
+	/* "version" K/V item */
+	spoe_encode_buffer("version", 7, &p, end);
+	*p++ = SPOE_DATA_T_STR;
+	spoe_encode_buffer(SPOP_VERSION, SLEN(SPOP_VERSION), &p, end);
+	DEBUG(frame->worker, "<%lu> Agent version : %s",
+	      client->id, SPOP_VERSION);
+
+
+	/* "max-frame-size" K/V item */
+	spoe_encode_buffer("max-frame-size", 14, &p ,end);
+	*p++ = SPOE_DATA_T_UINT32;
+	encode_varint(client->max_frame_size, &p, end);
+	DEBUG(frame->worker, "<%lu> Agent maximum frame size : %u",
+	      client->id, client->max_frame_size);
+
+	/* "capabilities" K/V item */
+	spoe_encode_buffer("capabilities", 12, &p, end);
+	*p++ = SPOE_DATA_T_STR;
+
+	memset(capabilities, 0, sizeof(capabilities));
+	n = 0;
+
+	/*     1. Fragmentation capability ? */
+	if (fragmentation == true) {
+		memcpy(capabilities, "fragmentation", 13);
+		n += 13;
+	}
+	/*     2. Pipelining capability ? */
+	if (client->pipelining == true) {
+		if (n) capabilities[n++] = ',';
+		memcpy(capabilities + n, "pipelining", 10);
+		n += 10;
+	}
+	/*     3. Async capability ? */
+	if (client->async == true) {
+		if (n) capabilities[n++] = ',';
+		memcpy(capabilities + n, "async", 5);
+		n += 5;
+	}
+	spoe_encode_buffer(capabilities, n, &p, end);
+
+	DEBUG(frame->worker, "<%lu> Agent capabilities : %.*s",
+	      client->id, n, capabilities);
+
+	frame->len = (p - frame->buf);
+	return frame->len;
+}
+
+/* Encode a DISCONNECT frame to send it to HAProxy. It returns the number of
+ * written bytes. */
+static int
+prepare_agentdicon(struct spoe_frame *frame)
+{
+	struct client *client = frame->client;
+	char           *p, *end;
+	const char     *reason;
+	int             rlen;
+	unsigned int    flags  = SPOE_FRM_FL_FIN;
+
+	DEBUG(frame->worker, "<%lu> Encode Agent DISCONNECT frame", client->id);
+	frame->type = SPOA_FRM_T_AGENT;
+
+	p   = frame->buf;
+	end = frame->buf+max_frame_size;
+
+	if (client->status_code >= SPOE_FRM_ERRS)
+		client->status_code = SPOE_FRM_ERR_UNKNOWN;
+	reason = spoe_frm_err_reasons[client->status_code];
+	rlen   = strlen(reason);
+
+	/* Frame type */
+	*p++ = SPOE_FRM_T_AGENT_DISCON;
+
+	/* Set flags */
+	memcpy(p, (char *)&flags, 4);
+	p += 4;
+
+	/* No stream-id and frame-id for DISCONNECT frames */
+	*p++ = 0;
+	*p++ = 0;
+
+	/* There are 2 mandatory items: "status-code" and "message" */
+
+	/* "status-code" K/V item */
+	spoe_encode_buffer("status-code", 11, &p, end);
+	*p++ = SPOE_DATA_T_UINT32;
+	encode_varint(client->status_code, &p, end);
+	DEBUG(frame->worker, "<%lu> Disconnect status code : %u",
+	      client->id, client->status_code);
+
+	/* "message" K/V item */
+	spoe_encode_buffer("message", 7, &p, end);
+	*p++ = SPOE_DATA_T_STR;
+	spoe_encode_buffer(reason, rlen, &p, end);
+	DEBUG(frame->worker, "<%lu> Disconnect message : %s",
+	      client->id, reason);
+
+	frame->len = (p - frame->buf);
+	return frame->len;
+}
+
+/* Encode a ACK frame to send it to HAProxy. It returns the number of written
+ * bytes. */
+static int
+prepare_agentack(struct spoe_frame *frame)
+{
+	char        *p, *end;
+	unsigned int flags  = SPOE_FRM_FL_FIN;
+
+	/* Be careful here, in async mode, frame->client can be NULL */
+
+	DEBUG(frame->worker, "Encode Agent ACK frame");
+	frame->type = SPOA_FRM_T_AGENT;
+
+	p   = frame->buf;
+	end = frame->buf+max_frame_size;
+
+	/* Frame type */
+	*p++ = SPOE_FRM_T_AGENT_ACK;
+
+	/* Set flags */
+	memcpy(p, (char *)&flags, 4);
+	p += 4;
+
+	/* Set stream-id and frame-id for ACK frames */
+	encode_varint(frame->stream_id, &p, end);
+	encode_varint(frame->frame_id, &p, end);
+
+	DEBUG(frame->worker, "STREAM-ID=%u - FRAME-ID=%u",
+	      frame->stream_id, frame->frame_id);
+
+	frame->len = (p - frame->buf);
+	return frame->len;
+}
+
+static int
+create_server_socket(void)
+{
+	struct sockaddr_in listen_addr;
+	int                fd, yes = 1;
+
+	fd = socket(AF_INET, SOCK_STREAM, 0);
+	if (fd < 0) {
+		LOG(&null_worker, "Failed to create service socket : %m");
+		return -1;
+	}
+
+	memset(&listen_addr, 0, sizeof(listen_addr));
+	listen_addr.sin_family = AF_INET;
+	listen_addr.sin_addr.s_addr = INADDR_ANY;
+	listen_addr.sin_port = htons(server_port);
+
+	if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) < 0 ||
+	    setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)) < 0) {
+		LOG(&null_worker, "Failed to set option on server socket : %m");
+		return -1;
+	}
+
+	if (bind(fd, (struct sockaddr *) &listen_addr, sizeof(listen_addr)) < 0) {
+		LOG(&null_worker, "Failed to bind server socket : %m");
+		return -1;
+	}
+
+	if (listen(fd, CONNECTION_BACKLOG) < 0) {
+		LOG(&null_worker, "Failed to listen on server socket : %m");
+		return -1;
+	}
+
+	return fd;
+}
+
+static void
+release_frame(struct spoe_frame *frame)
+{
+	struct worker *worker;
+
+	if (frame == NULL)
+		return;
+
+	if (event_pending(&frame->process_frame_event, EV_TIMEOUT, NULL))
+		event_del(&frame->process_frame_event);
+
+	worker = frame->worker;
+	LIST_DEL(&frame->list);
+	if (frame->frag_buf)
+		free(frame->frag_buf);
+	memset(frame, 0, sizeof(*frame)+max_frame_size+4);
+	LIST_ADDQ(&worker->frames, &frame->list);
+}
+
+static void
+release_client(struct client *c)
+{
+	struct spoe_frame *frame, *back;
+
+	if (c == NULL)
+		return;
+
+	DEBUG(c->worker, "<%lu> Release client", c->id);
+
+	LIST_DEL(&c->by_worker);
+	c->worker->nbclients--;
+
+	unuse_spoe_engine(c);
+	free(c->engine_id);
+
+	if (event_pending(&c->read_frame_event, EV_READ, NULL))
+		event_del(&c->read_frame_event);
+	if (event_pending(&c->write_frame_event, EV_WRITE, NULL))
+		event_del(&c->write_frame_event);
+
+	release_frame(c->incoming_frame);
+	release_frame(c->outgoing_frame);
+	list_for_each_entry_safe(frame, back, &c->processing_frames, list) {
+		release_frame(frame);
+	}
+	list_for_each_entry_safe(frame, back, &c->outgoing_frames, list) {
+		release_frame(frame);
+	}
+
+	if (c->fd >= 0)
+		close(c->fd);
+
+	free(c);
+}
+
+static void
+reset_frame(struct spoe_frame *frame)
+{
+	if (frame == NULL)
+		return;
+
+	if (frame->frag_buf)
+		free(frame->frag_buf);
+
+	frame->type            = SPOA_FRM_T_UNKNOWN;
+	frame->buf             = (char *)(frame->data);
+	frame->offset          = 0;
+	frame->len             = 0;
+	frame->stream_id       = 0;
+	frame->frame_id        = 0;
+	frame->flags           = 0;
+	frame->hcheck          = false;
+	frame->fragmented      = false;
+	frame->defender_status = -1;
+	frame->frag_buf        = NULL;
+	frame->frag_len        = 0;
+	LIST_INIT(&frame->list);
+}
+
+static void
+use_spoe_engine(struct client *client)
+{
+	struct spoe_engine *eng;
+
+	if (client->engine_id == NULL)
+		return;
+
+	list_for_each_entry(eng, &client->worker->engines, list) {
+		if (!strcmp(eng->id, client->engine_id))
+			goto end;
+	}
+
+	if ((eng = malloc(sizeof(*eng))) == NULL) {
+		client->async = false;
+		return;
+	}
+
+	eng->id = strdup(client->engine_id);
+	LIST_INIT(&eng->clients);
+	LIST_INIT(&eng->processing_frames);
+	LIST_INIT(&eng->outgoing_frames);
+	LIST_ADDQ(&client->worker->engines, &eng->list);
+	LOG(client->worker, "Add new SPOE engine '%s'", eng->id);
+
+  end:
+	client->engine = eng;
+	LIST_ADDQ(&eng->clients, &client->by_engine);
+}
+
+static void
+unuse_spoe_engine(struct client *client)
+{
+	struct spoe_engine *eng;
+	struct spoe_frame  *frame, *back;
+
+	if (client == NULL || client->engine == NULL)
+		return;
+
+	eng = client->engine;
+	client->engine = NULL;
+	LIST_DEL(&client->by_engine);
+	if (!LIST_ISEMPTY(&eng->clients))
+		return;
+
+	LOG(client->worker, "Remove SPOE engine '%s'", eng->id);
+	LIST_DEL(&eng->list);
+
+	list_for_each_entry_safe(frame, back, &eng->processing_frames, list) {
+		release_frame(frame);
+	}
+	list_for_each_entry_safe(frame, back, &eng->outgoing_frames, list) {
+		release_frame(frame);
+	}
+	free(eng->id);
+	free(eng);
+}
+
+
+static struct spoe_frame *
+acquire_incoming_frame(struct client *client)
+{
+	struct spoe_frame *frame;
+
+	frame = client->incoming_frame;
+	if (frame != NULL)
+		return frame;
+
+	if (LIST_ISEMPTY(&client->worker->frames)) {
+		if ((frame = calloc(1, sizeof(*frame)+max_frame_size+4)) == NULL) {
+			LOG(client->worker, "Failed to allocate new frame : %m");
+			return NULL;
+		}
+	}
+	else {
+		frame = LIST_NEXT(&client->worker->frames, typeof(frame), list);
+		LIST_DEL(&frame->list);
+	}
+
+	reset_frame(frame);
+	frame->worker = client->worker;
+	frame->engine = client->engine;
+	frame->client = client;
+
+	if (event_assign(&frame->process_frame_event, client->worker->base, -1,
+			 EV_TIMEOUT|EV_PERSIST, process_frame_cb, frame) < 0) {
+		LOG(client->worker, "Failed to create frame event");
+		return NULL;
+	}
+
+	client->incoming_frame = frame;
+	return frame;
+}
+
+static struct spoe_frame *
+acquire_outgoing_frame(struct client *client)
+{
+	struct spoe_engine *engine = client->engine;
+	struct spoe_frame  *frame = NULL;
+
+	if (client->outgoing_frame != NULL)
+		frame = client->outgoing_frame;
+	else if (!LIST_ISEMPTY(&client->outgoing_frames)) {
+		frame = LIST_NEXT(&client->outgoing_frames, typeof(frame), list);
+		LIST_DEL(&frame->list);
+		client->outgoing_frame = frame;
+	}
+	else if (engine!= NULL && !LIST_ISEMPTY(&engine->outgoing_frames)) {
+		frame = LIST_NEXT(&engine->outgoing_frames, typeof(frame), list);
+		LIST_DEL(&frame->list);
+		client->outgoing_frame = frame;
+	}
+	return frame;
+}
+
+static void
+write_frame(struct client *client, struct spoe_frame *frame)
+{
+	uint32_t netint;
+
+	LIST_DEL(&frame->list);
+
+	frame->buf    = (char *)(frame->data);
+	frame->offset = 0;
+	netint        = htonl(frame->len);
+	memcpy(frame->buf, &netint, 4);
+
+	if (client != NULL) { /* HELLO or DISCONNECT frames */
+		event_add(&client->write_frame_event, NULL);
+
+		/* Try to process the frame as soon as possible, and always
+		 * attach it to the client */
+		if (client->async || client->pipelining) {
+			if (client->outgoing_frame == NULL)
+				client->outgoing_frame = frame;
+			else
+				LIST_ADD(&client->outgoing_frames, &frame->list);
+		}
+		else {
+			client->outgoing_frame = frame;
+			event_del(&client->read_frame_event);
+		}
+	}
+	else { /* for all other frames */
+		if (frame->client == NULL) { /* async mode ! */
+			LIST_ADDQ(&frame->engine->outgoing_frames, &frame->list);
+			list_for_each_entry(client, &frame->engine->clients, by_engine)
+				event_add(&client->write_frame_event, NULL);
+		}
+		else if (frame->client->pipelining) {
+			LIST_ADDQ(&frame->client->outgoing_frames, &frame->list);
+			event_add(&frame->client->write_frame_event, NULL);
+		}
+		else {
+			frame->client->outgoing_frame = frame;
+			event_add(&frame->client->write_frame_event, NULL);
+			event_del(&frame->client->read_frame_event);
+		}
+	}
+}
+
+static void
+process_incoming_frame(struct spoe_frame *frame)
+{
+	struct client *client = frame->client;
+
+	if (event_add(&frame->process_frame_event, &processing_delay) < 0) {
+		LOG(client->worker, "Failed to process incoming frame");
+		release_frame(frame);
+		return;
+	}
+
+	if (client->async) {
+		frame->client = NULL;
+		LIST_ADDQ(&frame->engine->processing_frames, &frame->list);
+	}
+	else if (client->pipelining)
+		LIST_ADDQ(&client->processing_frames, &frame->list);
+	else
+		event_del(&client->read_frame_event);
+}
+
+static void
+signal_cb(evutil_socket_t sig, short events, void *user_data)
+{
+	struct event_base *base = user_data;
+	int                i;
+
+	DEBUG(&null_worker, "Stopping the server");
+
+	event_base_loopbreak(base);
+	DEBUG(&null_worker, "Main event loop stopped");
+
+	for (i = 0; i < num_workers; i++) {
+		event_base_loopbreak(workers[i].base);
+		DEBUG(&null_worker, "Event loop stopped for worker %02d",
+		      workers[i].id);
+	}
+}
+
+static void
+worker_monitor_cb(evutil_socket_t fd, short events, void *arg)
+{
+	struct worker *worker = arg;
+
+	LOG(worker, "%u clients connected", worker->nbclients);
+}
+
+static void
+process_frame_cb(evutil_socket_t fd, short events, void *arg)
+{
+	struct spoe_frame *frame  = arg;
+	char              *p, *end;
+	int                ret;
+
+	DEBUG(frame->worker,
+	      "Process frame messages : STREAM-ID=%u - FRAME-ID=%u - length=%u bytes",
+	      frame->stream_id, frame->frame_id, frame->len - frame->offset);
+
+	p   = frame->buf + frame->offset;
+	end = frame->buf + frame->len;
+
+	/* Loop on messages */
+	while (p < end) {
+		char    *str;
+		uint64_t sz;
+		int      nbargs;
+
+		/* Decode the message name */
+		spoe_decode_buffer(&p, end, &str, &sz);
+		if (!str)
+			goto stop_processing;
+
+		DEBUG(frame->worker, "Process SPOE Message '%.*s'", (int)sz, str);
+
+		nbargs = *p++;                     /* Get the number of arguments */
+		frame->offset = (p - frame->buf);  /* Save index to handle errors and skip args */
+		if (!memcmp(str, "check-request", sz)) {
+			struct defender_request request;
+
+			memset(&request, 0, sizeof(request));
+
+			if (nbargs != 8)
+				goto skip_message;
+
+			if (spoe_decode_buffer(&p, end, &str, &sz) == -1)
+				goto stop_processing;
+			if (spoe_decode_data(&p, end, &request.clientip) == -1)
+				goto skip_message;
+
+			if (spoe_decode_buffer(&p, end, &str, &sz) == -1)
+				goto stop_processing;
+			if (spoe_decode_data(&p, end, &request.id) == -1)
+				goto skip_message;
+
+			if (spoe_decode_buffer(&p, end, &str, &sz) == -1)
+				goto stop_processing;
+			if (spoe_decode_data(&p, end, &request.method) == -1)
+				goto skip_message;
+
+			if (spoe_decode_buffer(&p, end, &str, &sz) == -1)
+				goto stop_processing;
+			if (spoe_decode_data(&p, end, &request.path) == -1)
+				goto skip_message;
+
+			if (spoe_decode_buffer(&p, end, &str, &sz) == -1)
+				goto stop_processing;
+			if (spoe_decode_data(&p, end, &request.query) == -1)
+				goto skip_message;
+
+			if (spoe_decode_buffer(&p, end, &str, &sz) == -1)
+				goto stop_processing;
+			if (spoe_decode_data(&p, end, &request.version) == -1)
+				goto skip_message;
+
+			if (spoe_decode_buffer(&p, end, &str, &sz) == -1)
+				goto stop_processing;
+			if (spoe_decode_data(&p, end, &request.headers) == -1)
+				goto skip_message;
+
+			if (spoe_decode_buffer(&p, end, &str, &sz) == -1)
+				goto stop_processing;
+			if (spoe_decode_data(&p, end, &request.body) == -1)
+				goto skip_message;
+
+			frame->defender_status = defender_process_request(frame->worker, &request);
+		}
+		else {
+		  skip_message:
+			p = frame->buf + frame->offset; /* Restore index */
+
+			while (nbargs-- > 0) {
+				/* Silently ignore argument: its name and its value */
+				if (spoe_decode_buffer(&p, end, &str, &sz) == -1)
+					goto stop_processing;
+				if (spoe_skip_data(&p, end) == -1)
+					goto stop_processing;
+			}
+		}
+	}
+
+  stop_processing:
+	/* Prepare agent ACK frame */
+	frame->buf    = (char *)(frame->data) + 4;
+	frame->offset = 0;
+	frame->len    = 0;
+	frame->flags  = 0;
+
+	ret = prepare_agentack(frame);
+	p = frame->buf + ret;
+
+	if (frame->defender_status != -1) {
+		DEBUG(frame->worker, "Add action : set variable status=%u",
+		      frame->defender_status);
+
+		*p++ = SPOE_ACT_T_SET_VAR;                      /* Action type */
+		*p++ = 3;                                       /* Number of args */
+		*p++ = SPOE_SCOPE_SESS;                         /* Arg 1: the scope */
+		spoe_encode_buffer("status", 6, &p, end);       /* Arg 2: variable name */
+		*p++ = SPOE_DATA_T_UINT32;
+		encode_varint(frame->defender_status, &p, end); /* Arg 3: variable value */
+		frame->len = (p - frame->buf);
+	}
+	write_frame(NULL, frame);
+}
+
+static void
+read_frame_cb(evutil_socket_t fd, short events, void *arg)
+{
+	struct client     *client = arg;
+	struct spoe_frame *frame;
+	uint32_t           netint;
+	int                n;
+
+	DEBUG(client->worker, "<%lu> %s", client->id, __FUNCTION__);
+	if ((frame = acquire_incoming_frame(client)) == NULL)
+		goto close;
+
+	frame->type = SPOA_FRM_T_HAPROXY;
+	if (frame->buf == (char *)(frame->data)) {
+		/* Read the frame length: frame->buf points on length part (frame->data) */
+		n = read(client->fd, frame->buf+frame->offset, 4-frame->offset);
+		if (n <= 0) {
+			if (n < 0)
+				LOG(client->worker, "Failed to read frame length : %m");
+			goto close;
+		}
+		frame->offset += n;
+		if (frame->offset != 4)
+			return;
+		memcpy(&netint, frame->buf, 4);
+		frame->buf   += 4;
+		frame->offset = 0;
+		frame->len    = ntohl(netint);
+	}
+
+	/* Read the frame: frame->buf points on frame part (frame->data+4)*/
+	n = read(client->fd, frame->buf + frame->offset,
+		 frame->len - frame->offset);
+	if (n <= 0) {
+		if (n < 0) {
+			LOG(client->worker, "Frame to read frame : %m");
+			goto close;
+		}
+		return;
+	}
+	frame->offset += n;
+	if (frame->offset != frame->len)
+		return;
+	frame->offset = 0;
+
+	DEBUG(client->worker, "<%lu> New Frame of %u bytes received",
+	      client->id, frame->len);
+
+	switch (client->state) {
+		case SPOA_ST_CONNECTING:
+			if (handle_hahello(frame) < 0) {
+				LOG(client->worker, "Failed to decode HELLO frame");
+				goto disconnect;
+			}
+			prepare_agenthello(frame);
+			goto write_frame;
+
+		case SPOA_ST_PROCESSING:
+			if (frame->buf[0] == SPOE_FRM_T_HAPROXY_DISCON) {
+				client->state = SPOA_ST_DISCONNECTING;
+				goto disconnecting;
+			}
+			if (frame->buf[0] == SPOE_FRM_T_UNSET)
+				n = handle_hafrag(frame);
+			else
+				n = handle_hanotify(frame);
+
+			if (n < 0) {
+				LOG(client->worker, "Failed to decode frame: %s",
+				    spoe_frm_err_reasons[client->status_code]);
+				goto disconnect;
+			}
+			else if (n == 0) {
+				LOG(client->worker, "Ignore invalid/unknown/aborted frame");
+				goto ignore_frame;
+			}
+			else if (n == 1)
+				goto noop;
+			else
+				goto process_frame;
+
+		case SPOA_ST_DISCONNECTING:
+		  disconnecting:
+			if (handle_hadiscon(frame) < 0) {
+				LOG(client->worker, "Failed to decode DISCONNECT frame");
+				goto disconnect;
+			}
+			if (client->status_code != SPOE_FRM_ERR_NONE)
+				LOG(client->worker, "<%lu> Peer closed connection: %s",
+				    client->id, spoe_frm_err_reasons[client->status_code]);
+			client->status_code = SPOE_FRM_ERR_NONE;
+			goto disconnect;
+	}
+
+  noop:
+	return;
+
+  ignore_frame:
+	reset_frame(frame);
+	return;
+
+  process_frame:
+	process_incoming_frame(frame);
+	client->incoming_frame = NULL;
+	return;
+
+  write_frame:
+	write_frame(client, frame);
+	client->incoming_frame = NULL;
+	return;
+
+  disconnect:
+	client->state = SPOA_ST_DISCONNECTING;
+	if (prepare_agentdicon(frame) < 0) {
+		LOG(client->worker, "Failed to encode DISCONNECT frame");
+		goto close;
+	}
+	goto write_frame;
+
+  close:
+	release_client(client);
+}
+
+static void
+write_frame_cb(evutil_socket_t fd, short events, void *arg)
+{
+	struct client     *client = arg;
+	struct spoe_frame *frame;
+	int                n;
+
+	DEBUG(client->worker, "<%lu> %s", client->id, __FUNCTION__);
+	if ((frame = acquire_outgoing_frame(client)) == NULL) {
+		event_del(&client->write_frame_event);
+		return;
+	}
+
+	if (frame->buf == (char *)(frame->data)) {
+		/* Write the frame length: frame->buf points on length part (frame->data) */
+		n = write(client->fd, frame->buf+frame->offset, 4-frame->offset);
+		if (n <= 0) {
+			if (n < 0)
+				LOG(client->worker, "Failed to write frame length : %m");
+			goto close;
+		}
+		frame->offset += n;
+		if (frame->offset != 4)
+			return;
+		frame->buf   += 4;
+		frame->offset = 0;
+	}
+
+	/* Write the frame: frame->buf points on frame part (frame->data+4)*/
+	n = write(client->fd, frame->buf + frame->offset,
+		  frame->len - frame->offset);
+	if (n <= 0) {
+		if (n < 0) {
+			LOG(client->worker, "Failed to write frame : %m");
+			goto close;
+		}
+		return;
+	}
+	frame->offset += n;
+	if (frame->offset != frame->len)
+		return;
+
+	DEBUG(client->worker, "<%lu> Frame of %u bytes send",
+	      client->id, frame->len);
+
+	switch (client->state) {
+		case SPOA_ST_CONNECTING:
+			if (frame->hcheck == true) {
+				DEBUG(client->worker,
+				      "<%lu> Close client after healthcheck",
+				      client->id);
+				goto close;
+			}
+			client->state = SPOA_ST_PROCESSING;
+			break;
+
+		case SPOA_ST_PROCESSING:
+			break;
+
+		case SPOA_ST_DISCONNECTING:
+			goto close;
+	}
+
+	release_frame(frame);
+	client->outgoing_frame = NULL;
+	if (!client->async && !client->pipelining) {
+		event_del(&client->write_frame_event);
+		event_add(&client->read_frame_event, NULL);
+	}
+	return;
+
+  close:
+	release_client(client);
+}
+
+static void
+accept_cb(int listener, short event, void *arg)
+{
+	struct worker     *worker;
+	struct client     *client;
+	int                fd;
+
+	worker = &workers[clicount++ % num_workers];
+
+	if ((fd = accept(listener, NULL, NULL)) < 0) {
+		if (errno != EAGAIN && errno != EWOULDBLOCK)
+			LOG(worker, "Failed to accept client connection : %m");
+		return;
+	}
+
+	DEBUG(&null_worker,
+	      "<%lu> New Client connection accepted and assigned to worker %02d",
+	      clicount, worker->id);
+
+	if (evutil_make_socket_nonblocking(fd) < 0) {
+		LOG(&null_worker, "Failed to set client socket to non-blocking : %m");
+		close(fd);
+		return;
+	}
+
+	if ((client = calloc(1, sizeof(*client))) == NULL) {
+		LOG(&null_worker, "Failed to allocate memory for client state : %m");
+		close(fd);
+		return;
+	}
+
+	client->id             = clicount;
+	client->fd             = fd;
+	client->worker         = worker;
+	client->state          = SPOA_ST_CONNECTING;
+	client->status_code    = SPOE_FRM_ERR_NONE;
+	client->max_frame_size = max_frame_size;
+	client->engine         = NULL;
+	client->pipelining     = false;
+	client->async          = false;
+	client->incoming_frame = NULL;
+	client->outgoing_frame = NULL;
+	LIST_INIT(&client->processing_frames);
+	LIST_INIT(&client->outgoing_frames);
+
+	LIST_ADDQ(&worker->clients, &client->by_worker);
+
+	worker->nbclients++;
+
+	if (event_assign(&client->read_frame_event, worker->base, fd,
+			 EV_READ|EV_PERSIST, read_frame_cb, client) < 0     ||
+	    event_assign(&client->write_frame_event, worker->base, fd,
+			 EV_WRITE|EV_PERSIST, write_frame_cb, client) < 0) {
+		LOG(&null_worker, "Failed to create client events");
+		release_client(client);
+		return;
+	}
+	event_add(&client->read_frame_event,  NULL);
+}
+
+static void *
+worker_function(void *data)
+{
+	struct client     *client, *cback;
+	struct spoe_frame *frame, *fback;
+	struct worker     *worker = data;
+
+	DEBUG(worker, "Worker ready to process client messages");
+	event_base_dispatch(worker->base);
+
+	list_for_each_entry_safe(client, cback, &worker->clients, by_worker) {
+		release_client(client);
+	}
+
+	list_for_each_entry_safe(frame, fback, &worker->frames, list) {
+		LIST_DEL(&frame->list);
+		free(frame);
+	}
+
+	event_free(worker->monitor_event);
+	event_base_free(worker->base);
+	DEBUG(worker, "Worker is stopped");
+	pthread_exit(&null_worker);
+}
+
+
+static int
+parse_processing_delay(const char *str)
+{
+        unsigned long value;
+
+        value = 0;
+        while (1) {
+                unsigned int j;
+
+                j = *str - '0';
+                if (j > 9)
+                        break;
+                str++;
+                value *= 10;
+                value += j;
+        }
+
+        switch (*str) {
+		case '\0': /* no unit = millisecond */
+			value *= 1000;
+			break;
+		case 's': /* second */
+			value *= 1000000;
+			str++;
+			break;
+		case 'm': /* millisecond : "ms" */
+			if (str[1] != 's')
+				return -1;
+			value *= 1000;
+			str += 2;
+			break;
+		case 'u': /* microsecond : "us" */
+			if (str[1] != 's')
+				return -1;
+			str += 2;
+			break;
+		default:
+			return -1;
+        }
+	if (*str)
+		return -1;
+
+	processing_delay.tv_sec = (time_t)(value / 1000000);
+	processing_delay.tv_usec = (suseconds_t)(value % 1000000);
+        return 0;
+}
+
+
+static void
+usage(char *prog)
+{
+	fprintf(stderr,
+		"Usage : %s [OPTION]...\n"
+		"    -h                   Print this message\n"
+		"    -f <config-file>     Mod Defender configuration file\n"
+		"    -l <log-file>        Mod Defender log file\n"
+		"    -d                   Enable the debug mode\n"
+		"    -m <max-frame-size>  Specify the maximum frame size (default : %u)\n"
+		"    -p <port>            Specify the port to listen on (default : %d)\n"
+		"    -n <num-workers>     Specify the number of workers (default : %d)\n"
+		"    -c <capability>      Enable the support of the specified capability\n"
+		"    -t <time>            Set a delay to process a message (default: 0)\n"
+		"                           The value is specified in milliseconds by default,\n"
+		"                           but can be in any other unit if the number is suffixed\n"
+		"                           by a unit (us, ms, s)\n"
+		"\n"
+		"    Supported capabilities: fragmentation, pipelining, async\n",
+		prog, MAX_FRAME_SIZE, DEFAULT_PORT, NUM_WORKERS);
+}
+
+int
+main(int argc, char **argv)
+{
+	struct event_base *base = NULL;
+	struct event      *signal_event = NULL, *accept_event = NULL;
+	int                opt, i, fd = -1;
+	const char        *config_file = NULL;
+	const char        *log_file    = NULL;
+
+	// TODO: add '-t <processing-time>' option
+	while ((opt = getopt(argc, argv, "hf:l:dm:n:p:c:t:")) != -1) {
+		switch (opt) {
+			case 'h':
+				usage(argv[0]);
+				return EXIT_SUCCESS;
+			case 'f':
+				config_file = optarg;
+				break;
+			case 'l':
+				log_file = optarg;
+				break;
+			case 'd':
+				debug = true;
+				break;
+			case 'm':
+				max_frame_size = atoi(optarg);
+				break;
+			case 'n':
+				num_workers = atoi(optarg);
+				break;
+			case 'p':
+				server_port = atoi(optarg);
+				break;
+			case 'c':
+				if (!strcmp(optarg, "pipelining"))
+					pipelining = true;
+				else if (!strcmp(optarg, "async"))
+					async = true;
+				else if (!strcmp(optarg, "fragmentation"))
+					fragmentation = true;
+				else
+					fprintf(stderr, "WARNING: unsupported capability '%s'\n", optarg);
+				break;
+			case 't':
+				if (!parse_processing_delay(optarg))
+					break;
+				fprintf(stderr, "%s: failed to parse time '%s'.\n", argv[0], optarg);
+				fprintf(stderr, "Try '%s -h' for more information.\n", argv[0]);
+				return EXIT_FAILURE;
+			default:
+				usage(argv[0]);
+				return EXIT_FAILURE;
+		}
+	}
+
+	if (!defender_init(config_file, log_file))
+		goto error;
+
+	if (num_workers <= 0) {
+		LOG(&null_worker, "%s : Invalid number of workers '%d'\n",
+		    argv[0], num_workers);
+		goto error;
+	}
+
+	if (server_port <= 0) {
+		LOG(&null_worker, "%s : Invalid port '%d'\n",
+		    argv[0], server_port);
+		goto error;
+	}
+
+	if (evthread_use_pthreads() < 0) {
+		LOG(&null_worker, "No pthreads support for libevent");
+		goto error;
+	}
+
+	if ((base = event_base_new()) == NULL) {
+		LOG(&null_worker, "Failed to initialize libevent : %m");
+		goto error;
+	}
+
+	signal(SIGPIPE, SIG_IGN);
+
+	if ((fd = create_server_socket()) < 0) {
+		LOG(&null_worker, "Failed to create server socket");
+		goto error;
+	}
+	if (evutil_make_socket_nonblocking(fd) < 0) {
+		LOG(&null_worker, "Failed to set server socket to non-blocking");
+		goto error;
+	}
+
+	if ((workers = calloc(num_workers, sizeof(*workers))) == NULL) {
+		LOG(&null_worker, "Failed to set allocate memory for workers");
+		goto error;
+	}
+
+	for (i = 0; i < num_workers; ++i) {
+		struct worker *w = &workers[i];
+
+		w->id        = i+1;
+		w->nbclients = 0;
+		LIST_INIT(&w->engines);
+		LIST_INIT(&w->clients);
+		LIST_INIT(&w->frames);
+
+		if ((w->base = event_base_new()) == NULL) {
+			LOG(&null_worker,
+			    "Failed to initialize libevent for worker %02d : %m",
+			    w->id);
+			goto error;
+		}
+
+		w->monitor_event = event_new(w->base, fd, EV_PERSIST,
+					     worker_monitor_cb, (void *)w);
+		if (w->monitor_event == NULL ||
+		    event_add(w->monitor_event, (struct timeval[]){{5,0}}) < 0) {
+			LOG(&null_worker,
+			    "Failed to create monitor event for worker %02d",
+			    w->id);
+			goto error;
+		}
+
+		if (pthread_create(&w->thread, NULL, worker_function, (void *)w)) {
+			LOG(&null_worker,
+			    "Failed to start thread for worker %02d : %m",
+			    w->id);
+		}
+		DEBUG(&null_worker, "Worker %02d initialized", w->id);
+	}
+
+	accept_event = event_new(base, fd, EV_READ|EV_PERSIST, accept_cb,
+				 (void *)base);
+	if (accept_event == NULL || event_add(accept_event, NULL) < 0) {
+		LOG(&null_worker, "Failed to create accept event : %m");
+	}
+
+	signal_event = evsignal_new(base, SIGINT, signal_cb, (void *)base);
+	if (signal_event == NULL || event_add(signal_event, NULL) < 0) {
+		LOG(&null_worker, "Failed to create signal event : %m");
+	}
+
+	DEBUG(&null_worker,
+	      "Server is ready"
+	      " [fragmentation=%s - pipelining=%s - async=%s - debug=%s - max-frame-size=%u]",
+	      (fragmentation?"true":"false"), (pipelining?"true":"false"), (async?"true":"false"),
+	      (debug?"true":"false"), max_frame_size);
+	event_base_dispatch(base);
+
+	for (i = 0; i < num_workers; i++) {
+		struct worker *w = &workers[i];
+
+		pthread_join(w->thread, NULL);
+		DEBUG(&null_worker, "Worker %02d terminated", w->id);
+	}
+
+	free(workers);
+	event_free(signal_event);
+	event_free(accept_event);
+	event_base_free(base);
+	close(fd);
+	return EXIT_SUCCESS;
+
+  error:
+	if (workers != NULL)
+		free(workers);
+	if (signal_event != NULL)
+		event_free(signal_event);
+	if (accept_event != NULL)
+		event_free(accept_event);
+	if (base != NULL)
+		event_base_free(base);
+	if (fd != -1)
+		close(fd);
+	return EXIT_FAILURE;
+}
diff --git a/contrib/mod_defender/spoa.h b/contrib/mod_defender/spoa.h
new file mode 100644
index 0000000..332f387
--- /dev/null
+++ b/contrib/mod_defender/spoa.h
@@ -0,0 +1,50 @@
+/*
+ * Mod Defender for HAProxy
+ *
+ * Copyright 2017 HAProxy Technologies, Dragan Dosen <ddosen@haproxy.com>
+ *
+ * Based on "A Random IP reputation service acting as a Stream Processing Offload Agent"
+ * Copyright 2016 HAProxy Technologies, Christopher Faulet <cfaulet@haproxy.com>
+ *
+ * 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
+ * 3 of the License, or (at your option) any later version.
+ *
+ */
+#ifndef __SPOA_H__
+#define __SPOA_H__
+
+#include <sys/time.h>
+
+#include <event2/util.h>
+#include <event2/event.h>
+#include <event2/event_struct.h>
+#include <event2/thread.h>
+
+#define LOG(worker, fmt, args...)                                       \
+	do {								\
+		struct timeval  now;					\
+                                                                        \
+		gettimeofday(&now, NULL);				\
+		fprintf(stderr, "%ld.%06ld [%02d] " fmt "\n",		\
+			now.tv_sec, now.tv_usec, (worker)->id, ##args);	\
+	} while (0)
+
+struct worker {
+	pthread_t           thread;
+	int                 id;
+	struct event_base  *base;
+	struct event       *monitor_event;
+
+	struct list         engines;
+
+	unsigned int        nbclients;
+	struct list         clients;
+
+	struct list         frames;
+};
+
+extern struct worker null_worker;
+
+#endif /* __SPOA_H__ */
diff --git a/contrib/mod_defender/standalone.c b/contrib/mod_defender/standalone.c
new file mode 100644
index 0000000..f926ec1
--- /dev/null
+++ b/contrib/mod_defender/standalone.c
@@ -0,0 +1,1636 @@
+/*
+ * Mod Defender for HAProxy
+ *
+ * Support for the Mod Defender code on non-Apache platforms.
+ *
+ * Copyright 2017 HAProxy Technologies, Dragan Dosen <ddosen@haproxy.com>
+ *
+ * Parts of code based on Apache HTTP Server source
+ * Copyright 2015 The Apache Software Foundation (http://www.apache.org/)
+ *
+ * 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
+ * 3 of the License, or (at your option) any later version.
+ *
+ */
+#include <limits.h>
+
+#include <http_core.h>
+#include <http_main.h>
+#include <http_log.h>
+
+#include <apr_lib.h>
+#include <apr_strings.h>
+#include <apr_fnmatch.h>
+
+#include "standalone.h"
+
+#define MAX_ARGC 64
+#define MAX_INCLUDE_DIR_DEPTH 128
+
+#define SLASHES "/"
+
+#define FILTER_POOL apr_hook_global_pool
+#define TRIE_INITIAL_SIZE 4
+
+typedef struct filter_trie_node filter_trie_node;
+
+typedef struct {
+	int c;
+	filter_trie_node *child;
+} filter_trie_child_ptr;
+
+struct filter_trie_node {
+	ap_filter_rec_t *frec;
+	filter_trie_child_ptr *children;
+	int nchildren;
+	int size;
+};
+
+typedef struct {
+	const char *fname;
+} fnames;
+
+AP_DECLARE_DATA const char *ap_server_root = "/";
+
+void (*logger)(int level, char *str) = NULL;
+
+static void str_tolower(char *str)
+{
+	while (*str) {
+		*str = apr_tolower(*str);
+		++str;
+	}
+}
+
+static char x2c(const char *what)
+{
+	char digit;
+
+#if !APR_CHARSET_EBCDIC
+	digit = ((what[0] >= 'A') ? ((what[0] & 0xdf) - 'A') + 10
+	        : (what[0] - '0'));
+	digit *= 16;
+	digit += (what[1] >= 'A' ? ((what[1] & 0xdf) - 'A') + 10
+	         : (what[1] - '0'));
+#else /*APR_CHARSET_EBCDIC*/
+	char xstr[5];
+	xstr[0]='0';
+	xstr[1]='x';
+	xstr[2]=what[0];
+	xstr[3]=what[1];
+	xstr[4]='\0';
+	digit = apr_xlate_conv_byte(ap_hdrs_from_ascii,
+	                            0xFF & strtol(xstr, NULL, 16));
+#endif /*APR_CHARSET_EBCDIC*/
+	return (digit);
+}
+
+static int unescape_url(char *url, const char *forbid, const char *reserved)
+{
+	int badesc, badpath;
+	char *x, *y;
+
+	badesc = 0;
+	badpath = 0;
+	/* Initial scan for first '%'. Don't bother writing values before
+	 * seeing a '%' */
+	y = strchr(url, '%');
+	if (y == NULL) {
+		return OK;
+	}
+	for (x = y; *y; ++x, ++y) {
+		if (*y != '%') {
+			*x = *y;
+		}
+		else {
+			if (!apr_isxdigit(*(y + 1)) || !apr_isxdigit(*(y + 2))) {
+				badesc = 1;
+				*x = '%';
+			}
+			else {
+				char decoded;
+				decoded = x2c(y + 1);
+				if ((decoded == '\0')
+				    || (forbid && ap_strchr_c(forbid, decoded))) {
+					badpath = 1;
+					*x = decoded;
+					y += 2;
+				}
+				else if (reserved && ap_strchr_c(reserved, decoded)) {
+					*x++ = *y++;
+					*x++ = *y++;
+					*x = *y;
+				}
+				else {
+					*x = decoded;
+					y += 2;
+				}
+			}
+		}
+	}
+	*x = '\0';
+	if (badesc) {
+		return HTTP_BAD_REQUEST;
+	}
+	else if (badpath) {
+		return HTTP_NOT_FOUND;
+	}
+	else {
+		return OK;
+	}
+}
+
+AP_DECLARE(int) ap_unescape_url(char *url)
+{
+	/* Traditional */
+	return unescape_url(url, SLASHES, NULL);
+}
+
+AP_DECLARE(void) ap_get_server_revision(ap_version_t *version)
+{
+	version->major = AP_SERVER_MAJORVERSION_NUMBER;
+	version->minor = AP_SERVER_MINORVERSION_NUMBER;
+	version->patch = AP_SERVER_PATCHLEVEL_NUMBER;
+	version->add_string = AP_SERVER_ADD_STRING;
+}
+
+static void log_error_core(const char *file, int line, int module_index,
+                           int level,
+                           apr_status_t status, const server_rec *s,
+                           const conn_rec *c,
+                           const request_rec *r, apr_pool_t *pool,
+                           const char *fmt, va_list args)
+{
+	char errstr[MAX_STRING_LEN];
+
+	apr_vsnprintf(errstr, MAX_STRING_LEN, fmt, args);
+
+	if (logger != NULL)
+		logger(level, errstr);
+}
+
+AP_DECLARE(void) ap_log_error_(const char *file, int line, int module_index,
+                               int level, apr_status_t status,
+                               const server_rec *s, const char *fmt, ...)
+{
+	va_list args;
+
+	va_start(args, fmt);
+	log_error_core(file, line, module_index, level, status, s, NULL, NULL,
+	               NULL, fmt, args);
+	va_end(args);
+}
+
+AP_DECLARE(void) ap_log_rerror_(const char *file, int line, int module_index,
+                                int level, apr_status_t status,
+                                const request_rec *r, const char *fmt, ...)
+{
+	va_list args;
+
+	va_start(args, fmt);
+	log_error_core(file, line, module_index, level, status, r->server, NULL, r,
+	               NULL, fmt, args);
+	va_end(args);
+}
+
+AP_DECLARE(void) ap_log_cerror_(const char *file, int line, int module_index,
+                                int level, apr_status_t status,
+                                const conn_rec *c, const char *fmt, ...)
+{
+	va_list args;
+
+	va_start(args, fmt);
+	log_error_core(file, line, module_index, level, status, c->base_server, c,
+	               NULL, NULL, fmt, args);
+	va_end(args);
+}
+
+AP_DECLARE(piped_log *) ap_open_piped_log(apr_pool_t *p, const char *program)
+{
+	return NULL;
+}
+
+AP_DECLARE(apr_file_t *) ap_piped_log_write_fd(piped_log *pl)
+{
+	return NULL;
+}
+
+static cmd_parms default_parms =
+{NULL, 0, 0, NULL, -1, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL};
+
+AP_DECLARE(char *) ap_server_root_relative(apr_pool_t *p, const char *file)
+{
+	char *newpath = NULL;
+	apr_status_t rv;
+	rv = apr_filepath_merge(&newpath, ap_server_root, file,
+	                        APR_FILEPATH_TRUENAME, p);
+	if (newpath && (rv == APR_SUCCESS || APR_STATUS_IS_EPATHWILD(rv)
+	                                  || APR_STATUS_IS_ENOENT(rv)
+	                                  || APR_STATUS_IS_ENOTDIR(rv))) {
+		return newpath;
+	}
+	else {
+		return NULL;
+	}
+}
+
+AP_DECLARE(apr_status_t) ap_get_brigade(ap_filter_t *next,
+                                        apr_bucket_brigade *bb,
+                                        ap_input_mode_t mode,
+                                        apr_read_type_e block,
+                                        apr_off_t readbytes)
+{
+	if (next) {
+		return next->frec->filter_func.in_func(next, bb, mode, block,
+		                                       readbytes);
+	}
+	return AP_NOBODY_READ;
+}
+
+static void
+argstr_to_table(char *str, apr_table_t *parms)
+{
+	char *key;
+	char *value;
+	char *strtok_state;
+
+	if (str == NULL) {
+		return;
+	}
+
+	key = apr_strtok(str, "&", &strtok_state);
+	while (key) {
+		value = strchr(key, '=');
+		if (value) {
+			*value = '\0';      /* Split the string in two */
+			value++;            /* Skip passed the = */
+		}
+		else {
+			value = "1";
+		}
+		ap_unescape_url(key);
+		ap_unescape_url(value);
+		apr_table_set(parms, key, value);
+		key = apr_strtok(NULL, "&", &strtok_state);
+	}
+}
+
+AP_DECLARE(void) ap_args_to_table(request_rec *r, apr_table_t **table)
+{
+	apr_table_t *t = apr_table_make(r->pool, 10);
+	argstr_to_table(apr_pstrdup(r->pool, r->args), t);
+	*table = t;
+}
+
+/* Link a trie node to its parent
+ */
+static void trie_node_link(apr_pool_t *p, filter_trie_node *parent,
+                           filter_trie_node *child, int c)
+{
+	int i, j;
+
+	if (parent->nchildren == parent->size) {
+		filter_trie_child_ptr *new;
+		parent->size *= 2;
+		new = (filter_trie_child_ptr *)apr_palloc(p, parent->size *
+		                                          sizeof(filter_trie_child_ptr));
+		memcpy(new, parent->children, parent->nchildren *
+		       sizeof(filter_trie_child_ptr));
+		parent->children = new;
+	}
+
+	for (i = 0; i < parent->nchildren; i++) {
+		if (c == parent->children[i].c) {
+			return;
+		}
+		else if (c < parent->children[i].c) {
+			break;
+		}
+	}
+	for (j = parent->nchildren; j > i; j--) {
+		parent->children[j].c = parent->children[j - 1].c;
+		parent->children[j].child = parent->children[j - 1].child;
+	}
+	parent->children[i].c = c;
+	parent->children[i].child = child;
+
+	parent->nchildren++;
+}
+
+/* Allocate a new node for a trie.
+ * If parent is non-NULL, link the new node under the parent node with
+ * key 'c' (or, if an existing child node matches, return that one)
+ */
+static filter_trie_node *trie_node_alloc(apr_pool_t *p,
+                                         filter_trie_node *parent, char c)
+{
+	filter_trie_node *new_node;
+	if (parent) {
+		int i;
+		for (i = 0; i < parent->nchildren; i++) {
+			if (c == parent->children[i].c) {
+				return parent->children[i].child;
+			}
+			else if (c < parent->children[i].c) {
+				break;
+			}
+		}
+		new_node = (filter_trie_node *)apr_palloc(p, sizeof(filter_trie_node));
+		trie_node_link(p, parent, new_node, c);
+	}
+	else { /* No parent node */
+		new_node = (filter_trie_node *)apr_palloc(p,
+		           sizeof(filter_trie_node));
+	}
+
+	new_node->frec = NULL;
+	new_node->nchildren = 0;
+	new_node->size = TRIE_INITIAL_SIZE;
+	new_node->children = (filter_trie_child_ptr *)apr_palloc(p,
+	                     new_node->size * sizeof(filter_trie_child_ptr));
+	return new_node;
+}
+
+static filter_trie_node *registered_output_filters = NULL;
+static filter_trie_node *registered_input_filters = NULL;
+
+
+static apr_status_t filter_cleanup(void *ctx)
+{
+	registered_output_filters = NULL;
+	registered_input_filters = NULL;
+	return APR_SUCCESS;
+}
+
+static ap_filter_rec_t *register_filter(const char *name,
+                                        ap_filter_func filter_func,
+                                        ap_init_filter_func filter_init,
+                                        ap_filter_type ftype,
+                                        filter_trie_node **reg_filter_set)
+{
+	ap_filter_rec_t *frec;
+	char *normalized_name;
+	const char *n;
+	filter_trie_node *node;
+
+	if (!*reg_filter_set) {
+		*reg_filter_set = trie_node_alloc(FILTER_POOL, NULL, 0);
+	}
+
+	normalized_name = apr_pstrdup(FILTER_POOL, name);
+	str_tolower(normalized_name);
+
+	node = *reg_filter_set;
+	for (n = normalized_name; *n; n++) {
+		filter_trie_node *child = trie_node_alloc(FILTER_POOL, node, *n);
+		if (apr_isalpha(*n)) {
+			trie_node_link(FILTER_POOL, node, child, apr_toupper(*n));
+		}
+		node = child;
+	}
+	if (node->frec) {
+		frec = node->frec;
+	}
+	else {
+		frec = apr_pcalloc(FILTER_POOL, sizeof(*frec));
+		node->frec = frec;
+		frec->name = normalized_name;
+	}
+	frec->filter_func = filter_func;
+	frec->filter_init_func = filter_init;
+	frec->ftype = ftype;
+
+	apr_pool_cleanup_register(FILTER_POOL, NULL, filter_cleanup,
+	                          apr_pool_cleanup_null);
+	return frec;
+}
+
+AP_DECLARE(ap_filter_rec_t *) ap_register_input_filter(const char *name,
+                                                       ap_in_filter_func filter_func,
+                                                       ap_init_filter_func filter_init,
+                                                       ap_filter_type ftype)
+{
+	ap_filter_func f;
+	f.in_func = filter_func;
+	return register_filter(name, f, filter_init, ftype,
+	                       &registered_input_filters);
+}
+
+static ap_filter_t *add_any_filter_handle(ap_filter_rec_t *frec, void *ctx,
+                                          request_rec *r, conn_rec *c,
+                                          ap_filter_t **r_filters,
+                                          ap_filter_t **p_filters,
+                                          ap_filter_t **c_filters)
+{
+	apr_pool_t *p = frec->ftype < AP_FTYPE_CONNECTION && r ? r->pool : c->pool;
+	ap_filter_t *f = apr_palloc(p, sizeof(*f));
+	ap_filter_t **outf;
+
+	if (frec->ftype < AP_FTYPE_PROTOCOL) {
+		if (r) {
+			outf = r_filters;
+		}
+		else {
+			ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(00080)
+			              "a content filter was added without a request: %s", frec->name);
+			return NULL;
+		}
+	}
+	else if (frec->ftype < AP_FTYPE_CONNECTION) {
+		if (r) {
+			outf = p_filters;
+		}
+		else {
+			ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(00081)
+			              "a protocol filter was added without a request: %s", frec->name);
+			return NULL;
+		}
+	}
+	else {
+		outf = c_filters;
+	}
+
+	f->frec = frec;
+	f->ctx = ctx;
+	/* f->r must always be NULL for connection filters */
+	f->r = frec->ftype < AP_FTYPE_CONNECTION ? r : NULL;
+	f->c = c;
+	f->next = NULL;
+
+	if (INSERT_BEFORE(f, *outf)) {
+		f->next = *outf;
+
+		if (*outf) {
+			ap_filter_t *first = NULL;
+
+			if (r) {
+				/* If we are adding our first non-connection filter,
+				 * Then don't try to find the right location, it is
+				 * automatically first.
+				 */
+				if (*r_filters != *c_filters) {
+					first = *r_filters;
+					while (first && (first->next != (*outf))) {
+						first = first->next;
+					}
+				}
+			}
+			if (first && first != (*outf)) {
+				first->next = f;
+			}
+		}
+		*outf = f;
+	}
+	else {
+		ap_filter_t *fscan = *outf;
+		while (!INSERT_BEFORE(f, fscan->next))
+			fscan = fscan->next;
+
+		f->next = fscan->next;
+		fscan->next = f;
+	}
+
+	if (frec->ftype < AP_FTYPE_CONNECTION && (*r_filters == *c_filters)) {
+		*r_filters = *p_filters;
+	}
+	return f;
+}
+
+static ap_filter_t *add_any_filter(const char *name, void *ctx,
+                                   request_rec *r, conn_rec *c,
+                                   const filter_trie_node *reg_filter_set,
+                                   ap_filter_t **r_filters,
+                                   ap_filter_t **p_filters,
+                                   ap_filter_t **c_filters)
+{
+	if (reg_filter_set) {
+		const char *n;
+		const filter_trie_node *node;
+
+		node = reg_filter_set;
+		for (n = name; *n; n++) {
+			int start, end;
+			start = 0;
+			end = node->nchildren - 1;
+			while (end >= start) {
+				int middle = (end + start) / 2;
+				char ch = node->children[middle].c;
+				if (*n == ch) {
+					node = node->children[middle].child;
+					break;
+				}
+				else if (*n < ch) {
+					end = middle - 1;
+				}
+				else {
+					start = middle + 1;
+				}
+			}
+			if (end < start) {
+				node = NULL;
+				break;
+			}
+		}
+
+		if (node && node->frec) {
+			return add_any_filter_handle(node->frec, ctx, r, c, r_filters,
+			                             p_filters, c_filters);
+		}
+	}
+
+	ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, r ? r->connection : c, APLOGNO(00082)
+	              "an unknown filter was not added: %s", name);
+	return NULL;
+}
+
+AP_DECLARE(ap_filter_t *) ap_add_input_filter(const char *name, void *ctx,
+                                              request_rec *r, conn_rec *c)
+{
+	return add_any_filter(name, ctx, r, c, registered_input_filters,
+	                      r ? &r->input_filters : NULL,
+	                      r ? &r->proto_input_filters : NULL,
+	                      &c->input_filters);
+}
+
+static void remove_any_filter(ap_filter_t *f, ap_filter_t **r_filt, ap_filter_t **p_filt,
+                              ap_filter_t **c_filt)
+{
+	ap_filter_t **curr = r_filt ? r_filt : c_filt;
+	ap_filter_t *fscan = *curr;
+
+	if (p_filt && *p_filt == f)
+		*p_filt = (*p_filt)->next;
+
+	if (*curr == f) {
+		*curr = (*curr)->next;
+		return;
+	}
+
+	while (fscan->next != f) {
+		if (!(fscan = fscan->next)) {
+			return;
+		}
+	}
+
+	fscan->next = f->next;
+}
+
+AP_DECLARE(void) ap_remove_input_filter(ap_filter_t *f)
+{
+	remove_any_filter(f, f->r ? &f->r->input_filters : NULL,
+	                  f->r ? &f->r->proto_input_filters : NULL,
+	                  &f->c->input_filters);
+}
+
+static int cfg_closefile(ap_configfile_t *cfp)
+{
+#ifdef DEBUG
+	ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL,
+	             "Done with config file %s", cfp->name);
+#endif
+	return (cfp->close == NULL) ? 0 : cfp->close(cfp->param);
+}
+
+/* we can't use apr_file_* directly because of linking issues on Windows */
+static apr_status_t cfg_close(void *param)
+{
+	return apr_file_close(param);
+}
+
+static apr_status_t cfg_getch(char *ch, void *param)
+{
+	return apr_file_getc(ch, param);
+}
+
+static apr_status_t cfg_getstr(void *buf, apr_size_t bufsiz, void *param)
+{
+	return apr_file_gets(buf, bufsiz, param);
+}
+
+/* Read one line from open ap_configfile_t, strip LF, increase line number */
+/* If custom handler does not define a getstr() function, read char by char */
+static apr_status_t cfg_getline_core(char *buf, apr_size_t bufsize,
+                                     apr_size_t offset, ap_configfile_t *cfp)
+{
+	apr_status_t rc;
+	/* If a "get string" function is defined, use it */
+	if (cfp->getstr != NULL) {
+		char *cp;
+		char *cbuf = buf + offset;
+		apr_size_t cbufsize = bufsize - offset;
+
+		while (1) {
+			++cfp->line_number;
+			rc = cfp->getstr(cbuf, cbufsize, cfp->param);
+			if (rc == APR_EOF) {
+				if (cbuf != buf + offset) {
+					*cbuf = '\0';
+					break;
+				}
+				else {
+					return APR_EOF;
+				}
+			}
+			if (rc != APR_SUCCESS) {
+				return rc;
+			}
+
+			/*
+			 *  check for line continuation,
+			 *  i.e. match [^\\]\\[\r]\n only
+			 */
+			cp = cbuf;
+			cp += strlen(cp);
+			if (cp > buf && cp[-1] == LF) {
+				cp--;
+				if (cp > buf && cp[-1] == CR)
+					cp--;
+				if (cp > buf && cp[-1] == '\\') {
+					cp--;
+					/*
+					 * line continuation requested -
+					 * then remove backslash and continue
+					 */
+					cbufsize -= (cp-cbuf);
+					cbuf = cp;
+					continue;
+				}
+			}
+			else if (cp - buf >= bufsize - 1) {
+				return APR_ENOSPC;
+			}
+			break;
+		}
+	} else {
+		/* No "get string" function defined; read character by character */
+		apr_size_t i = offset;
+
+		if (bufsize < 2) {
+			/* too small, assume caller is crazy */
+			return APR_EINVAL;
+		}
+		buf[offset] = '\0';
+
+		while (1) {
+			char c;
+			rc = cfp->getch(&c, cfp->param);
+			if (rc == APR_EOF) {
+				if (i > offset)
+					break;
+				else
+					return APR_EOF;
+			}
+			if (rc != APR_SUCCESS)
+				return rc;
+			if (c == LF) {
+				++cfp->line_number;
+				/* check for line continuation */
+				if (i > 0 && buf[i-1] == '\\') {
+					i--;
+					continue;
+				}
+				else {
+					break;
+				}
+			}
+			buf[i] = c;
+			++i;
+			if (i >= bufsize - 1) {
+				return APR_ENOSPC;
+			}
+		}
+		buf[i] = '\0';
+	}
+	return APR_SUCCESS;
+}
+
+static int cfg_trim_line(char *buf)
+{
+	char *start, *end;
+	/*
+	 * Leading and trailing white space is eliminated completely
+	 */
+	start = buf;
+	while (apr_isspace(*start))
+		++start;
+	/* blast trailing whitespace */
+	end = &start[strlen(start)];
+	while (--end >= start && apr_isspace(*end))
+		*end = '\0';
+	/* Zap leading whitespace by shifting */
+	if (start != buf)
+		memmove(buf, start, end - start + 2);
+#ifdef DEBUG_CFG_LINES
+	ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, NULL, APLOGNO(00555) "Read config: '%s'", buf);
+#endif
+	return end - start + 1;
+}
+
+/* Read one line from open ap_configfile_t, strip LF, increase line number */
+/* If custom handler does not define a getstr() function, read char by char */
+static apr_status_t cfg_getline(char *buf, apr_size_t bufsize,
+                                ap_configfile_t *cfp)
+{
+	apr_status_t rc = cfg_getline_core(buf, bufsize, 0, cfp);
+	if (rc == APR_SUCCESS)
+		cfg_trim_line(buf);
+	return rc;
+}
+
+static char *substring_conf(apr_pool_t *p, const char *start, int len,
+                            char quote)
+{
+	char *result = apr_palloc(p, len + 1);
+	char *resp = result;
+	int i;
+
+	for (i = 0; i < len; ++i) {
+		if (start[i] == '\\' && (start[i + 1] == '\\'
+		                         || (quote && start[i + 1] == quote)))
+			*resp++ = start[++i];
+		else
+			*resp++ = start[i];
+	}
+
+	*resp++ = '\0';
+#if RESOLVE_ENV_PER_TOKEN
+	return (char *)ap_resolve_env(p,result);
+#else
+	return result;
+#endif
+}
+
+static char *getword_conf(apr_pool_t *p, const char **line)
+{
+	const char *str = *line, *strend;
+	char *res;
+	char quote;
+
+	while (apr_isspace(*str))
+		++str;
+
+	if (!*str) {
+		*line = str;
+		return "";
+	}
+
+	if ((quote = *str) == '"' || quote == '\'') {
+		strend = str + 1;
+		while (*strend && *strend != quote) {
+			if (*strend == '\\' && strend[1] &&
+			    (strend[1] == quote || strend[1] == '\\')) {
+				strend += 2;
+			}
+			else {
+				++strend;
+			}
+		}
+		res = substring_conf(p, str + 1, strend - str - 1, quote);
+
+		if (*strend == quote)
+			++strend;
+	}
+	else {
+		strend = str;
+		while (*strend && !apr_isspace(*strend))
+			++strend;
+
+		res = substring_conf(p, str, strend - str, 0);
+	}
+
+	while (apr_isspace(*strend))
+		++strend;
+	*line = strend;
+	return res;
+}
+
+/* Open a ap_configfile_t as FILE, return open ap_configfile_t struct pointer */
+static apr_status_t pcfg_openfile(ap_configfile_t **ret_cfg,
+                                  apr_pool_t *p, const char *name)
+{
+	ap_configfile_t *new_cfg;
+	apr_file_t *file = NULL;
+	apr_finfo_t finfo;
+	apr_status_t status;
+#ifdef DEBUG
+	char buf[120];
+#endif
+
+	if (name == NULL) {
+		ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, APLOGNO(00552)
+		             "Internal error: pcfg_openfile() called with NULL filename");
+		return APR_EBADF;
+	}
+
+	status = apr_file_open(&file, name, APR_READ | APR_BUFFERED,
+	                       APR_OS_DEFAULT, p);
+#ifdef DEBUG
+	ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL, APLOGNO(00553)
+	             "Opening config file %s (%s)",
+	             name, (status != APR_SUCCESS) ?
+	             apr_strerror(status, buf, sizeof(buf)) : "successful");
+#endif
+	if (status != APR_SUCCESS)
+		return status;
+
+	status = apr_file_info_get(&finfo, APR_FINFO_TYPE, file);
+	if (status != APR_SUCCESS)
+		return status;
+
+	if (finfo.filetype != APR_REG &&
+		strcmp(name, "/dev/null") != 0) {
+		ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, APLOGNO(00554)
+		             "Access to file %s denied by server: not a regular file",
+		             name);
+		apr_file_close(file);
+		return APR_EBADF;
+	}
+
+	new_cfg = apr_palloc(p, sizeof(*new_cfg));
+	new_cfg->param = file;
+	new_cfg->name = apr_pstrdup(p, name);
+	new_cfg->getch = cfg_getch;
+	new_cfg->getstr = cfg_getstr;
+	new_cfg->close = cfg_close;
+	new_cfg->line_number = 0;
+	*ret_cfg = new_cfg;
+	return APR_SUCCESS;
+}
+
+static const command_rec *find_command(const char *name,
+                                       const command_rec *cmds)
+{
+	while (cmds->name) {
+		if (!strcasecmp(name, cmds->name))
+			return cmds;
+		++cmds;
+	}
+
+	return NULL;
+}
+
+static const char *invoke_cmd(const command_rec *cmd, cmd_parms *parms,
+                              void *mconfig, const char *args)
+{
+	int override_list_ok = 0;
+	char *w, *w2, *w3;
+	const char *errmsg = NULL;
+
+	/** Have we been provided a list of acceptable directives? */
+	if (parms->override_list != NULL) {
+		if (apr_table_get(parms->override_list, cmd->name) != NULL) {
+			override_list_ok = 1;
+		}
+	}
+
+	if ((parms->override & cmd->req_override) == 0 && !override_list_ok) {
+		return apr_pstrcat(parms->pool, cmd->name,
+		                   " not allowed here", NULL);
+	}
+
+	parms->info = cmd->cmd_data;
+	parms->cmd = cmd;
+
+	switch (cmd->args_how) {
+	case RAW_ARGS:
+#ifdef RESOLVE_ENV_PER_TOKEN
+		args = ap_resolve_env(parms->pool,args);
+#endif
+		return cmd->AP_RAW_ARGS(parms, mconfig, args);
+
+	case TAKE_ARGV:
+		{
+			char *argv[MAX_ARGC];
+			int argc = 0;
+
+			do {
+				w = getword_conf(parms->pool, &args);
+				if (*w == '\0' && *args == '\0') {
+					break;
+				}
+				argv[argc] = w;
+				argc++;
+			} while (argc < MAX_ARGC && *args != '\0');
+
+			return cmd->AP_TAKE_ARGV(parms, mconfig, argc, argv);
+		}
+
+	case NO_ARGS:
+		if (*args != 0)
+			return apr_pstrcat(parms->pool, cmd->name, " takes no arguments",
+			                   NULL);
+
+		return cmd->AP_NO_ARGS(parms, mconfig);
+
+	case TAKE1:
+		w = getword_conf(parms->pool, &args);
+
+		if (*w == '\0' || *args != 0)
+			return apr_pstrcat(parms->pool, cmd->name, " takes one argument",
+			                   cmd->errmsg ? ", " : NULL, cmd->errmsg, NULL);
+
+		return cmd->AP_TAKE1(parms, mconfig, w);
+
+	case TAKE2:
+		w = getword_conf(parms->pool, &args);
+		w2 = getword_conf(parms->pool, &args);
+
+		if (*w == '\0' || *w2 == '\0' || *args != 0)
+			return apr_pstrcat(parms->pool, cmd->name, " takes two arguments",
+			                   cmd->errmsg ? ", " : NULL, cmd->errmsg, NULL);
+
+		return cmd->AP_TAKE2(parms, mconfig, w, w2);
+
+	case TAKE12:
+		w = getword_conf(parms->pool, &args);
+		w2 = getword_conf(parms->pool, &args);
+
+		if (*w == '\0' || *args != 0)
+			return apr_pstrcat(parms->pool, cmd->name, " takes 1-2 arguments",
+			                   cmd->errmsg ? ", " : NULL, cmd->errmsg, NULL);
+
+		return cmd->AP_TAKE2(parms, mconfig, w, *w2 ? w2 : NULL);
+
+	case TAKE3:
+		w = getword_conf(parms->pool, &args);
+		w2 = getword_conf(parms->pool, &args);
+		w3 = getword_conf(parms->pool, &args);
+
+		if (*w == '\0' || *w2 == '\0' || *w3 == '\0' || *args != 0)
+			return apr_pstrcat(parms->pool, cmd->name, " takes three arguments",
+			                   cmd->errmsg ? ", " : NULL, cmd->errmsg, NULL);
+
+		return cmd->AP_TAKE3(parms, mconfig, w, w2, w3);
+
+	case TAKE23:
+		w = getword_conf(parms->pool, &args);
+		w2 = getword_conf(parms->pool, &args);
+		w3 = *args ? getword_conf(parms->pool, &args) : NULL;
+
+		if (*w == '\0' || *w2 == '\0' || *args != 0)
+			return apr_pstrcat(parms->pool, cmd->name,
+			                   " takes two or three arguments",
+			                   cmd->errmsg ? ", " : NULL, cmd->errmsg, NULL);
+
+		return cmd->AP_TAKE3(parms, mconfig, w, w2, w3);
+
+	case TAKE123:
+		w = getword_conf(parms->pool, &args);
+		w2 = *args ? getword_conf(parms->pool, &args) : NULL;
+		w3 = *args ? getword_conf(parms->pool, &args) : NULL;
+
+		if (*w == '\0' || *args != 0)
+			return apr_pstrcat(parms->pool, cmd->name,
+			                   " takes one, two or three arguments",
+			                   cmd->errmsg ? ", " : NULL, cmd->errmsg, NULL);
+
+		return cmd->AP_TAKE3(parms, mconfig, w, w2, w3);
+
+	case TAKE13:
+		w = getword_conf(parms->pool, &args);
+		w2 = *args ? getword_conf(parms->pool, &args) : NULL;
+		w3 = *args ? getword_conf(parms->pool, &args) : NULL;
+
+		if (*w == '\0' || (w2 && *w2 && !w3) || *args != 0)
+			return apr_pstrcat(parms->pool, cmd->name,
+			                   " takes one or three arguments",
+			                   cmd->errmsg ? ", " : NULL, cmd->errmsg, NULL);
+
+		return cmd->AP_TAKE3(parms, mconfig, w, w2, w3);
+
+	case ITERATE:
+		w = getword_conf(parms->pool, &args);
+
+		if (*w == '\0')
+			return apr_pstrcat(parms->pool, cmd->name,
+			                   " requires at least one argument",
+			                   cmd->errmsg ? ", " : NULL, cmd->errmsg, NULL);
+
+		while (*w != '\0') {
+			errmsg = cmd->AP_TAKE1(parms, mconfig, w);
+
+			if (errmsg && strcmp(errmsg, DECLINE_CMD) != 0)
+				return errmsg;
+
+			w = getword_conf(parms->pool, &args);
+		}
+
+		return errmsg;
+
+	case ITERATE2:
+		w = getword_conf(parms->pool, &args);
+
+		if (*w == '\0' || *args == 0)
+			return apr_pstrcat(parms->pool, cmd->name,
+			                   " requires at least two arguments",
+			                   cmd->errmsg ? ", " : NULL, cmd->errmsg, NULL);
+
+		while (*(w2 = getword_conf(parms->pool, &args)) != '\0') {
+
+			errmsg = cmd->AP_TAKE2(parms, mconfig, w, w2);
+
+			if (errmsg && strcmp(errmsg, DECLINE_CMD) != 0)
+				return errmsg;
+		}
+
+		return errmsg;
+
+	case FLAG:
+		/*
+		 * This is safe to use temp_pool here, because the 'flag' itself is not
+		 * forwarded as-is
+		 */
+		w = getword_conf(parms->temp_pool, &args);
+
+		if (*w == '\0' || (strcasecmp(w, "on") && strcasecmp(w, "off")))
+			return apr_pstrcat(parms->pool, cmd->name, " must be On or Off",
+			                   NULL);
+
+		return cmd->AP_FLAG(parms, mconfig, strcasecmp(w, "off") != 0);
+
+	default:
+		return apr_pstrcat(parms->pool, cmd->name,
+		                   " is improperly configured internally (server bug)",
+		                   NULL);
+	}
+}
+
+static int is_directory(apr_pool_t *p, const char *path)
+{
+	apr_finfo_t finfo;
+
+	if (apr_stat(&finfo, path, APR_FINFO_TYPE, p) != APR_SUCCESS)
+		return 0;                /* in error condition, just return no */
+
+	return (finfo.filetype == APR_DIR);
+}
+
+static char *make_full_path(apr_pool_t *a, const char *src1,
+                            const char *src2)
+{
+	apr_size_t len1, len2;
+	char *path;
+
+	len1 = strlen(src1);
+	len2 = strlen(src2);
+	/* allocate +3 for '/' delimiter, trailing NULL and overallocate
+	 * one extra byte to allow the caller to add a trailing '/'
+	 */
+	path = (char *)apr_palloc(a, len1 + len2 + 3);
+	if (len1 == 0) {
+		*path = '/';
+		memcpy(path + 1, src2, len2 + 1);
+	}
+	else {
+		char *next;
+		memcpy(path, src1, len1);
+		next = path + len1;
+		if (next[-1] != '/') {
+			*next++ = '/';
+		}
+		memcpy(next, src2, len2 + 1);
+	}
+	return path;
+}
+
+static int fname_alphasort(const void *fn1, const void *fn2)
+{
+	const fnames *f1 = fn1;
+	const fnames *f2 = fn2;
+
+	return strcmp(f1->fname,f2->fname);
+}
+
+static const char *process_resource_config(const char *fname,
+                                           apr_array_header_t *ari,
+                                           apr_pool_t *p,
+                                           apr_pool_t *ptemp)
+{
+	*(char **)apr_array_push(ari) = (char *)fname;
+	return NULL;
+}
+
+static const char *process_resource_config_nofnmatch(const char *fname,
+                                                     apr_array_header_t *ari,
+                                                     apr_pool_t *p,
+                                                     apr_pool_t *ptemp,
+                                                     unsigned depth,
+                                                     int optional)
+{
+	const char *error;
+	apr_status_t rv;
+
+	if (is_directory(ptemp, fname)) {
+		apr_dir_t *dirp;
+		apr_finfo_t dirent;
+		int current;
+		apr_array_header_t *candidates = NULL;
+		fnames *fnew;
+		char *path = apr_pstrdup(ptemp, fname);
+
+		if (++depth > MAX_INCLUDE_DIR_DEPTH) {
+			return apr_psprintf(p, "Directory %s exceeds the maximum include "
+			                    "directory nesting level of %u. You have "
+			                    "probably a recursion somewhere.", path,
+			                    MAX_INCLUDE_DIR_DEPTH);
+		}
+
+		/*
+		 * first course of business is to grok all the directory
+		 * entries here and store 'em away. Recall we need full pathnames
+		 * for this.
+		 */
+		rv = apr_dir_open(&dirp, path, ptemp);
+		if (rv != APR_SUCCESS) {
+			return apr_psprintf(p, "Could not open config directory %s: %pm",
+			                    path, &rv);
+		}
+
+		candidates = apr_array_make(ptemp, 1, sizeof(fnames));
+		while (apr_dir_read(&dirent, APR_FINFO_DIRENT, dirp) == APR_SUCCESS) {
+			/* strip out '.' and '..' */
+			if (strcmp(dirent.name, ".")
+			    && strcmp(dirent.name, "..")) {
+				fnew = (fnames *) apr_array_push(candidates);
+				fnew->fname = make_full_path(ptemp, path, dirent.name);
+			}
+		}
+
+		apr_dir_close(dirp);
+		if (candidates->nelts != 0) {
+			qsort((void *) candidates->elts, candidates->nelts,
+			      sizeof(fnames), fname_alphasort);
+
+			/*
+			 * Now recurse these... we handle errors and subdirectories
+			 * via the recursion, which is nice
+			 */
+			for (current = 0; current < candidates->nelts; ++current) {
+				fnew = &((fnames *) candidates->elts)[current];
+				error = process_resource_config_nofnmatch(fnew->fname,
+				                                          ari, p, ptemp,
+				                                          depth, optional);
+				if (error) {
+					return error;
+				}
+			}
+		}
+
+		return NULL;
+	}
+
+	return process_resource_config(fname, ari, p, ptemp);
+}
+
+static const char *process_resource_config_fnmatch(const char *path,
+                                                   const char *fname,
+                                                   apr_array_header_t *ari,
+                                                   apr_pool_t *p,
+                                                   apr_pool_t *ptemp,
+                                                   unsigned depth,
+                                                   int optional)
+{
+	const char *rest;
+	apr_status_t rv;
+	apr_dir_t *dirp;
+	apr_finfo_t dirent;
+	apr_array_header_t *candidates = NULL;
+	fnames *fnew;
+	int current;
+
+	/* find the first part of the filename */
+	rest = ap_strchr_c(fname, '/');
+	if (rest) {
+		fname = apr_pstrndup(ptemp, fname, rest - fname);
+		rest++;
+	}
+
+	/* optimisation - if the filename isn't a wildcard, process it directly */
+	if (!apr_fnmatch_test(fname)) {
+		path = make_full_path(ptemp, path, fname);
+		if (!rest) {
+			return process_resource_config_nofnmatch(path,
+			                                         ari, p,
+			                                         ptemp, 0, optional);
+		}
+		else {
+			return process_resource_config_fnmatch(path, rest,
+			                                       ari, p,
+			                                       ptemp, 0, optional);
+		}
+	}
+
+	/*
+	 * first course of business is to grok all the directory
+	 * entries here and store 'em away. Recall we need full pathnames
+	 * for this.
+	 */
+	rv = apr_dir_open(&dirp, path, ptemp);
+	if (rv != APR_SUCCESS) {
+		return apr_psprintf(p, "Could not open config directory %s: %pm",
+		                    path, &rv);
+	}
+
+	candidates = apr_array_make(ptemp, 1, sizeof(fnames));
+	while (apr_dir_read(&dirent, APR_FINFO_DIRENT | APR_FINFO_TYPE, dirp) == APR_SUCCESS) {
+		/* strip out '.' and '..' */
+		if (strcmp(dirent.name, ".")
+		    && strcmp(dirent.name, "..")
+		    && (apr_fnmatch(fname, dirent.name,
+			                APR_FNM_PERIOD) == APR_SUCCESS)) {
+			const char *full_path = make_full_path(ptemp, path, dirent.name);
+			/* If matching internal to path, and we happen to match something
+			 * other than a directory, skip it
+			 */
+			if (rest && (rv == APR_SUCCESS) && (dirent.filetype != APR_DIR)) {
+				continue;
+			}
+			fnew = (fnames *) apr_array_push(candidates);
+			fnew->fname = full_path;
+		}
+	}
+
+	apr_dir_close(dirp);
+	if (candidates->nelts != 0) {
+		const char *error;
+
+		qsort((void *) candidates->elts, candidates->nelts,
+		      sizeof(fnames), fname_alphasort);
+
+		/*
+		 * Now recurse these... we handle errors and subdirectories
+		 * via the recursion, which is nice
+		 */
+		for (current = 0; current < candidates->nelts; ++current) {
+			fnew = &((fnames *) candidates->elts)[current];
+			if (!rest) {
+				error = process_resource_config_nofnmatch(fnew->fname,
+				                                          ari, p,
+				                                          ptemp, 0, optional);
+			}
+			else {
+				error = process_resource_config_fnmatch(fnew->fname, rest,
+				                                        ari, p,
+				                                        ptemp, 0, optional);
+			}
+			if (error) {
+				return error;
+			}
+		}
+	}
+	else {
+
+		if (!optional) {
+			return apr_psprintf(p, "No matches for the wildcard '%s' in '%s', failing "
+			                    "(use IncludeOptional if required)", fname, path);
+		}
+	}
+
+	return NULL;
+}
+
+static const char *process_fnmatch_configs(const char *fname,
+                                           apr_array_header_t *ari,
+                                           apr_pool_t *p,
+                                           apr_pool_t *ptemp,
+                                           int optional)
+{
+	if (!apr_fnmatch_test(fname)) {
+		return process_resource_config_nofnmatch(fname, ari, p, ptemp, 0, optional);
+	}
+	else {
+		apr_status_t status;
+		const char *rootpath, *filepath = fname;
+
+		/* locate the start of the directories proper */
+		status = apr_filepath_root(&rootpath, &filepath, APR_FILEPATH_TRUENAME, ptemp);
+
+		/* we allow APR_SUCCESS and APR_EINCOMPLETE */
+		if (APR_ERELATIVE == status) {
+			return apr_pstrcat(p, "Include must have an absolute path, ", fname, NULL);
+		}
+		else if (APR_EBADPATH == status) {
+			return apr_pstrcat(p, "Include has a bad path, ", fname, NULL);
+		}
+
+		/* walk the filepath */
+		return process_resource_config_fnmatch(rootpath, filepath, ari, p, ptemp,
+		                                       0, optional);
+	}
+}
+
+const char *read_module_config(server_rec *s, void *mconfig,
+                               const command_rec *cmds,
+                               apr_pool_t *p, apr_pool_t *ptemp,
+                               const char *filename)
+{
+	apr_array_header_t *ari, *arr;
+	ap_directive_t *newdir;
+	cmd_parms *parms;
+
+	char line[MAX_STRING_LEN];
+	const char *errmsg;
+	const char *err = NULL;
+
+	ari = apr_array_make(p, 1, sizeof(char *));
+	arr = apr_array_make(p, 1, sizeof(cmd_parms));
+
+	errmsg = process_fnmatch_configs(filename, ari, p, ptemp, 0);
+
+	if (errmsg != NULL)
+		goto out;
+
+	while (ari->nelts || arr->nelts) {
+
+		/* similar to process_command_config() */
+		if (ari->nelts) {
+			char *inc = *(char **)apr_array_pop(ari);
+
+			parms = (cmd_parms *)apr_array_push(arr);
+			*parms = default_parms;
+			parms->pool = p;
+			parms->temp_pool = ptemp;
+			parms->server = s;
+			parms->override = (RSRC_CONF | ACCESS_CONF);
+			parms->override_opts = OPT_ALL | OPT_SYM_OWNER | OPT_MULTI;
+
+			if (pcfg_openfile(&parms->config_file, p, inc) != APR_SUCCESS) {
+				apr_array_pop(arr);
+				errmsg = apr_pstrcat(p, "Cannot open file: ", inc, NULL);
+				goto out;
+			}
+		}
+
+		if (arr->nelts > MAX_INCLUDE_DIR_DEPTH) {
+			errmsg = apr_psprintf(p, "Exceeded the maximum include "
+			                      "directory nesting level of %u. You have "
+			                      "probably a recursion somewhere.",
+			                      MAX_INCLUDE_DIR_DEPTH);
+			goto out;
+		}
+
+		if (!(parms = (cmd_parms *)apr_array_pop(arr)))
+			break;
+
+		while (!(cfg_getline(line, MAX_STRING_LEN, parms->config_file))) {
+
+			const command_rec *cmd;
+			char *cmd_name;
+			const char *args = line;
+			int optional = 0;
+
+			if (*line == '#' || *line == '\0')
+				continue;
+
+			if (!(cmd_name = getword_conf(p, &args)))
+				continue;
+
+			/* similar to invoke_cmd() */
+			if (!strcasecmp(cmd_name, "IncludeOptional") ||
+			    !strcasecmp(cmd_name, "Include"))
+			{
+				char *w, *fullname;
+
+				if (!strcasecmp(cmd_name, "IncludeOptional"))
+					optional = 1;
+
+				w = getword_conf(parms->pool, &args);
+
+				if (*w == '\0' || *args != 0) {
+					errmsg = apr_pstrcat(parms->pool, cmd_name, " takes one argument", NULL);
+					goto out;
+				}
+
+				fullname = ap_server_root_relative(ptemp, w);
+				errmsg = process_fnmatch_configs(fullname, ari, p, ptemp, optional);
+
+				*(cmd_parms *)apr_array_push(arr) = *parms;
+
+				if(errmsg != NULL)
+					goto out;
+
+				parms = NULL;
+				break;
+			}
+
+			if (!(cmd = find_command(cmd_name, cmds))) {
+				errmsg = apr_pstrcat(parms->pool, "Invalid command '",
+				                     cmd_name, "'", NULL);
+				goto out;
+			}
+
+			newdir = apr_pcalloc(p, sizeof(ap_directive_t));
+			newdir->filename = parms->config_file->name;
+			newdir->line_num = parms->config_file->line_number;
+			newdir->directive = cmd_name;
+			newdir->args = apr_pstrdup(p, args);
+
+			parms->directive = newdir;
+
+			if ((errmsg = invoke_cmd(cmd, parms, mconfig, args)) != NULL)
+				break;
+		}
+
+		if (parms != NULL)
+			cfg_closefile(parms->config_file);
+
+		if (errmsg != NULL)
+			break;
+	}
+
+	if (errmsg) {
+		if (parms) {
+			err = apr_psprintf(p, "Syntax error on line %d of %s: %s",
+			                   parms->config_file->line_number,
+			                   parms->config_file->name,
+			                   errmsg);
+			errmsg = err;
+		}
+	}
+
+out:
+
+	while ((parms = (cmd_parms *)apr_array_pop(arr)) != NULL)
+		cfg_closefile(parms->config_file);
+
+	return errmsg;
+}
+
+int lookup_builtin_method(const char *method, apr_size_t len)
+{
+	/* Note: from Apache 2 HTTP Server source. */
+
+	/* Note: the following code was generated by the "shilka" tool from
+	   the "cocom" parsing/compilation toolkit. It is an optimized lookup
+	   based on analysis of the input keywords. Postprocessing was done
+	   on the shilka output, but the basic structure and analysis is
+	   from there. Should new HTTP methods be added, then manual insertion
+	   into this code is fine, or simply re-running the shilka tool on
+	   the appropriate input. */
+
+	/* Note: it is also quite reasonable to just use our method_registry,
+	   but I'm assuming (probably incorrectly) we want more speed here
+	   (based on the optimizations the previous code was doing). */
+
+	switch (len)
+	{
+	case 3:
+		switch (method[0])
+		{
+		case 'P':
+			return (method[1] == 'U'
+			        && method[2] == 'T'
+			        ? M_PUT : UNKNOWN_METHOD);
+		case 'G':
+			return (method[1] == 'E'
+			        && method[2] == 'T'
+			        ? M_GET : UNKNOWN_METHOD);
+		default:
+			return UNKNOWN_METHOD;
+		}
+
+	case 4:
+		switch (method[0])
+		{
+		case 'H':
+			return (method[1] == 'E'
+			        && method[2] == 'A'
+			        && method[3] == 'D'
+			        ? M_GET : UNKNOWN_METHOD);
+		case 'P':
+			return (method[1] == 'O'
+			        && method[2] == 'S'
+			        && method[3] == 'T'
+			        ? M_POST : UNKNOWN_METHOD);
+		case 'M':
+			return (method[1] == 'O'
+			        && method[2] == 'V'
+			        && method[3] == 'E'
+			        ? M_MOVE : UNKNOWN_METHOD);
+		case 'L':
+			return (method[1] == 'O'
+			        && method[2] == 'C'
+			        && method[3] == 'K'
+			        ? M_LOCK : UNKNOWN_METHOD);
+		case 'C':
+			return (method[1] == 'O'
+			        && method[2] == 'P'
+			        && method[3] == 'Y'
+			        ? M_COPY : UNKNOWN_METHOD);
+		default:
+			return UNKNOWN_METHOD;
+		}
+
+	case 5:
+		switch (method[2])
+		{
+		case 'T':
+			return (memcmp(method, "PATCH", 5) == 0
+			        ? M_PATCH : UNKNOWN_METHOD);
+		case 'R':
+			return (memcmp(method, "MERGE", 5) == 0
+			        ? M_MERGE : UNKNOWN_METHOD);
+		case 'C':
+			return (memcmp(method, "MKCOL", 5) == 0
+			        ? M_MKCOL : UNKNOWN_METHOD);
+		case 'B':
+			return (memcmp(method, "LABEL", 5) == 0
+			        ? M_LABEL : UNKNOWN_METHOD);
+		case 'A':
+			return (memcmp(method, "TRACE", 5) == 0
+			        ? M_TRACE : UNKNOWN_METHOD);
+		default:
+			return UNKNOWN_METHOD;
+		}
+
+	case 6:
+		switch (method[0])
+		{
+		case 'U':
+			switch (method[5])
+			{
+			case 'K':
+				return (memcmp(method, "UNLOCK", 6) == 0
+				        ? M_UNLOCK : UNKNOWN_METHOD);
+			case 'E':
+				return (memcmp(method, "UPDATE", 6) == 0
+				        ? M_UPDATE : UNKNOWN_METHOD);
+			default:
+				return UNKNOWN_METHOD;
+			}
+		case 'R':
+			return (memcmp(method, "REPORT", 6) == 0
+			        ? M_REPORT : UNKNOWN_METHOD);
+		case 'D':
+			return (memcmp(method, "DELETE", 6) == 0
+			        ? M_DELETE : UNKNOWN_METHOD);
+		default:
+			return UNKNOWN_METHOD;
+		}
+
+	case 7:
+		switch (method[1])
+		{
+		case 'P':
+			return (memcmp(method, "OPTIONS", 7) == 0
+			        ? M_OPTIONS : UNKNOWN_METHOD);
+		case 'O':
+			return (memcmp(method, "CONNECT", 7) == 0
+			        ? M_CONNECT : UNKNOWN_METHOD);
+		case 'H':
+			return (memcmp(method, "CHECKIN", 7) == 0
+			        ? M_CHECKIN : UNKNOWN_METHOD);
+		default:
+			return UNKNOWN_METHOD;
+		}
+
+	case 8:
+		switch (method[0])
+		{
+		case 'P':
+			return (memcmp(method, "PROPFIND", 8) == 0
+			        ? M_PROPFIND : UNKNOWN_METHOD);
+		case 'C':
+			return (memcmp(method, "CHECKOUT", 8) == 0
+			        ? M_CHECKOUT : UNKNOWN_METHOD);
+		default:
+			return UNKNOWN_METHOD;
+		}
+
+	case 9:
+		return (memcmp(method, "PROPPATCH", 9) == 0
+                ? M_PROPPATCH : UNKNOWN_METHOD);
+
+	case 10:
+		switch (method[0])
+		{
+		case 'U':
+			return (memcmp(method, "UNCHECKOUT", 10) == 0
+			        ? M_UNCHECKOUT : UNKNOWN_METHOD);
+		case 'M':
+			return (memcmp(method, "MKACTIVITY", 10) == 0
+			        ? M_MKACTIVITY : UNKNOWN_METHOD);
+		default:
+			return UNKNOWN_METHOD;
+		}
+
+	case 11:
+		return (memcmp(method, "MKWORKSPACE", 11) == 0
+		        ? M_MKWORKSPACE : UNKNOWN_METHOD);
+
+	case 15:
+		return (memcmp(method, "VERSION-CONTROL", 15) == 0
+		        ? M_VERSION_CONTROL : UNKNOWN_METHOD);
+
+	case 16:
+		return (memcmp(method, "BASELINE-CONTROL", 16) == 0
+		        ? M_BASELINE_CONTROL : UNKNOWN_METHOD);
+
+	default:
+		return UNKNOWN_METHOD;
+	}
+
+	/* NOTREACHED */
+}
diff --git a/contrib/mod_defender/standalone.h b/contrib/mod_defender/standalone.h
new file mode 100644
index 0000000..9c9ccdd
--- /dev/null
+++ b/contrib/mod_defender/standalone.h
@@ -0,0 +1,52 @@
+/*
+ * Mod Defender for HAProxy
+ *
+ * Support for the Mod Defender code on non-Apache platforms.
+ *
+ * Copyright 2017 HAProxy Technologies, Dragan Dosen <ddosen@haproxy.com>
+ *
+ * Parts of code based on Apache HTTP Server source
+ * Copyright 2015 The Apache Software Foundation (http://www.apache.org/)
+ *
+ * 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
+ * 3 of the License, or (at your option) any later version.
+ *
+ */
+#ifndef __STANDALONE_H__
+#define __STANDALONE_H__
+
+#include <http_core.h>
+#include <http_main.h>
+#include <http_config.h>
+
+#include <apr_pools.h>
+#include <apr_hooks.h>
+
+#define INSERT_BEFORE(f, before_this) ((before_this) == NULL                \
+                           || (before_this)->frec->ftype > (f)->frec->ftype \
+                           || (before_this)->r != (f)->r)
+
+#define DECLARE_EXTERNAL_HOOK(ns,link,ret,name,args)                           \
+ns##_HOOK_##name##_t *run_##ns##_hook_##name = NULL;                           \
+link##_DECLARE(void) ns##_hook_##name(ns##_HOOK_##name##_t *pf,                \
+                                      const char * const *aszPre,              \
+                                      const char * const *aszSucc, int nOrder) \
+{                                                                              \
+	run_##ns##_hook_##name = pf;                                           \
+}
+
+#define DECLARE_HOOK(ret,name,args) \
+	DECLARE_EXTERNAL_HOOK(ap,AP,ret,name,args)
+
+#define UNKNOWN_METHOD (-1)
+
+extern void (*logger)(int level, char *str);
+extern const char *read_module_config(server_rec *s, void *mconfig,
+                                      const command_rec *cmds,
+                                      apr_pool_t *p, apr_pool_t *ptemp,
+                                      const char *filename);
+extern int lookup_builtin_method(const char *method, apr_size_t len);
+
+#endif /* __STANDALONE_H__ */