--- /dev/null
+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 $@ $<
--- /dev/null
+ --------------------------
+ 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.
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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__ */
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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__ */
--- /dev/null
+/*
+ * 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,
+ ®istered_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 */
+}
--- /dev/null
+/*
+ * 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__ */