/* * Mod Defender for HAProxy * * Copyright 2017 HAProxy Technologies, Dragan Dosen * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #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; }