haproxy/contrib/mod_defender/defender.c

634 lines
15 KiB
C

/*
* 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;
}