diff --git a/nginx/modsecurity/apr_bucket_nginx.c b/nginx/modsecurity/apr_bucket_nginx.c new file mode 100644 index 0000000000..d9b0155486 --- /dev/null +++ b/nginx/modsecurity/apr_bucket_nginx.c @@ -0,0 +1,232 @@ + +#include + +static apr_status_t nginx_bucket_read(apr_bucket *b, const char **str, + apr_size_t *len, apr_read_type_e block); +static void nginx_bucket_destroy(void *data); + +static const apr_bucket_type_t apr_bucket_type_nginx = { + "NGINX", 5, APR_BUCKET_DATA, + nginx_bucket_destroy, + nginx_bucket_read, + apr_bucket_setaside_noop, + apr_bucket_shared_split, + apr_bucket_shared_copy +}; + + +typedef struct apr_bucket_nginx { + apr_bucket_refcount refcount; + ngx_buf_t *buf; +} apr_bucket_nginx; + +/* ngx_buf_t to apr_bucket */ +apr_bucket * apr_bucket_nginx_create(ngx_buf_t *buf, + apr_pool_t *p, + apr_bucket_alloc_t *list) +{ + + apr_bucket *b = apr_bucket_alloc(sizeof(*b), list); + + APR_BUCKET_INIT(b); /* link */ + b->free = apr_bucket_free; + b->list = list; + return apr_bucket_nginx_make(b, buf, p); +} + +apr_bucket * apr_bucket_nginx_make(apr_bucket *b, ngx_buf_t *buf, + apr_pool_t *pool) +{ + apr_bucket_nginx *n; + + n = apr_bucket_alloc(sizeof(*n), b->list); + + n->buf = buf; + + b = apr_bucket_shared_make(b, n, 0, ngx_buf_size(buf)); + b->type = &apr_bucket_type_nginx; + return b; +} + +static apr_status_t nginx_bucket_read(apr_bucket *b, const char **str, + apr_size_t *len, apr_read_type_e block) +{ + apr_bucket_nginx *n = b->data; + ngx_buf_t *buf = n->buf; + u_char *data; + ssize_t size; + + if (buf->pos == NULL && ngx_buf_size(buf) != 0) { + data = apr_bucket_alloc(ngx_buf_size(buf), b->list); + if (data == NULL) { + return APR_EGENERAL; + } + + size = ngx_read_file(buf->file, data, ngx_buf_size(buf), buf->file_pos); + if (size != ngx_buf_size(buf)) { + apr_bucket_free(data); + return APR_EGENERAL; + } + buf->pos = data; + } + + *str = (char *)buf->pos + b->start; + *len = b->length; + + return APR_SUCCESS; +} + + +static void nginx_bucket_destroy(void *data) +{ + apr_bucket_nginx *n = data; + ngx_buf_t *buf = n->buf; + + if (apr_bucket_shared_destroy(n)) { + if (!ngx_buf_in_memory(buf) && buf->pos != NULL) { + apr_bucket_free(buf->pos); + buf->pos = NULL; + } + apr_bucket_free(n); + } +} + +ngx_buf_t * apr_bucket_to_ngx_buf(apr_bucket *e, ngx_pool_t *pool) { + ngx_buf_t *buf, *b; + apr_bucket_nginx *n; + ngx_uint_t len; + u_char *data; + + if (e->type->is_metadata) { + return NULL; + } + + if (e->type == &apr_bucket_type_nginx) { + n = e->data; + b = n->buf; + + /* whole buf */ + if (e->length == (apr_size_t)ngx_buf_size(b)) { + b->last_buf = 0; + return b; + } + + buf = ngx_palloc(pool, sizeof(ngx_buf_t)); + if (buf == NULL) { + return NULL; + } + ngx_memcpy(buf, b, sizeof(ngx_buf_t)); + + if (ngx_buf_in_memory(buf)) { + buf->start = buf->pos = buf->pos + e->start; + buf->end = buf->last = buf->pos + e->length; + } else { + buf->pos = NULL; + buf->file_pos += e->start; + buf->file_last = buf->file_pos + e->length; + } + + buf->last_buf = 0; + return buf; + } + + if (apr_bucket_read(e, (const char **)&data, + &len, APR_BLOCK_READ) != APR_SUCCESS) { + return NULL; + } + + buf = ngx_calloc_buf(pool); + if (buf == NULL) { + return NULL; + } + + if (e->type == &apr_bucket_type_pool) { + buf->start = data; + } else if (len != 0) { + buf->start = ngx_palloc(pool, len); + ngx_memcpy(buf->start, data, len); + } + + buf->pos = buf->start; + buf->end = buf->last = buf->start + len; + buf->temporary = 1; + return buf; +} + +ngx_int_t +move_chain_to_brigade(ngx_chain_t *chain, apr_bucket_brigade *bb, ngx_pool_t *pool) { + apr_bucket *e; + ngx_chain_t *cl; + + while (chain) { + e = ngx_buf_to_apr_bucket(chain->buf, bb->p, bb->bucket_alloc); + if (e == NULL) { + return NGX_ERROR; + } + + APR_BRIGADE_INSERT_TAIL(bb, e); + if (chain->buf->last_buf) { + e = apr_bucket_eos_create(bb->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(bb, e); + chain->buf->last_buf = 0; + return NGX_OK; + } + cl = chain; + chain = chain->next; + ngx_free_chain(pool, cl); + } + return NGX_AGAIN; +} + +ngx_int_t +move_brigade_to_chain(apr_bucket_brigade *bb, ngx_chain_t **ll, ngx_pool_t *pool) { + apr_bucket *e; + ngx_buf_t *buf; + ngx_chain_t *cl; + + cl = NULL; + + if (APR_BRIGADE_EMPTY(bb)) { + *ll = NULL; + return NGX_OK; + } + + for (e = APR_BRIGADE_FIRST(bb); + e != APR_BRIGADE_SENTINEL(bb); + e = APR_BUCKET_NEXT(e)) { + + if (APR_BUCKET_IS_EOS(e)) { + if (cl == NULL) { + *ll = cl; + } else { + cl->buf->last_buf = 1; + } + apr_brigade_cleanup(bb); + return NGX_OK; + } + + if (APR_BUCKET_IS_METADATA(e)) { + continue; + } + + buf = apr_bucket_to_ngx_buf(e, pool); + if (buf == NULL) { + break; + } + + cl = ngx_alloc_chain_link(pool); + if (cl == NULL) { + break; + } + + cl->buf = buf; + cl->next = NULL; + *ll = cl; + ll = &cl->next; + } + + apr_brigade_cleanup(bb); + /* no eos or error */ + return NGX_ERROR; +} + diff --git a/nginx/modsecurity/apr_bucket_nginx.h b/nginx/modsecurity/apr_bucket_nginx.h new file mode 100644 index 0000000000..4dd1ca0ff1 --- /dev/null +++ b/nginx/modsecurity/apr_bucket_nginx.h @@ -0,0 +1,18 @@ +#pragma once +#include +#include "apr_buckets.h" + +apr_bucket * apr_bucket_nginx_create(ngx_buf_t *buf, + apr_pool_t *p, + apr_bucket_alloc_t *list); + +apr_bucket * apr_bucket_nginx_make(apr_bucket *e, ngx_buf_t *buf, + apr_pool_t *pool); + +#define ngx_buf_to_apr_bucket apr_bucket_nginx_create + +ngx_buf_t * apr_bucket_to_ngx_buf(apr_bucket *e, ngx_pool_t *pool); + +ngx_int_t move_chain_to_brigade(ngx_chain_t *chain, apr_bucket_brigade *bb, ngx_pool_t *pool); +ngx_int_t move_brigade_to_chain(apr_bucket_brigade *bb, ngx_chain_t **chain, ngx_pool_t *pool); + diff --git a/nginx/modsecurity/config b/nginx/modsecurity/config index 1cf004cd1c..8140002ed2 100644 --- a/nginx/modsecurity/config +++ b/nginx/modsecurity/config @@ -1,6 +1,9 @@ ngx_addon_name=ngx_http_modsecurity -HTTP_MODULES="$HTTP_MODULES ngx_http_modsecurity" -NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_modsecurity.c" +# HTTP_MODULES="$HTTP_MODULES ngx_http_modsecurity" +HTTP_HEADERS_FILTER_MODULE="ngx_http_modsecurity $HTTP_HEADERS_FILTER_MODULE" +NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_modsecurity.c $ngx_addon_dir/apr_bucket_nginx.c" NGX_ADDON_DEPS="$NGX_ADDON_DEPS" CORE_LIBS="$CORE_LIBS $ngx_addon_dir/../../standalone/.libs/standalone.a -lapr-1 -laprutil-1 -lxml2 -lm" -CORE_INCS="$CORE_INCS /usr/include/apache2 /usr/include/apr-1.0 /usr/include/httpd /usr/include/apr-1 $ngx_addon_dir/../../standalone $ngx_addon_dir/../../apache2 /usr/include/libxml2" +CORE_INCS="$CORE_INCS /usr/include/apache2 /usr/include/apr-1.0 /usr/include/httpd /usr/include/apr-1 $ngx_addon_dir $ngx_addon_dir/../../standalone $ngx_addon_dir/../../apache2 /usr/include/libxml2" +have=REQUEST_EARLY . auto/have + diff --git a/nginx/modsecurity/ngx_http_modsecurity.c b/nginx/modsecurity/ngx_http_modsecurity.c index a78a153524..668a961663 100644 --- a/nginx/modsecurity/ngx_http_modsecurity.c +++ b/nginx/modsecurity/ngx_http_modsecurity.c @@ -20,7 +20,7 @@ #include #include #include - +#include #undef CR #undef LF #undef CRLF @@ -32,17 +32,18 @@ typedef struct { ngx_flag_t enable; directory_config *config; + + ngx_str_t *file; + ngx_uint_t line; } ngx_http_modsecurity_loc_conf_t; typedef struct { ngx_http_request_t *r; conn_rec *connection; request_rec *req; - ngx_chain_t *chain; - ngx_buf_t buf; - void **loc_conf; - unsigned request_body_in_single_buf:1; - unsigned request_body_in_file_only:1; + + apr_bucket_brigade *brigade; + unsigned complete; } ngx_http_modsecurity_ctx_t; @@ -50,7 +51,9 @@ typedef struct { ** Module's registred function/handlers. */ static ngx_int_t ngx_http_modsecurity_handler(ngx_http_request_t *r); -static void ngx_http_modsecurity_request_body_handler(ngx_http_request_t *r); +static void ngx_http_modsecurity_body_handler(ngx_http_request_t *r); +static ngx_int_t ngx_http_modsecurity_header_filter(ngx_http_request_t *r); +static ngx_int_t ngx_http_modsecurity_body_filter(ngx_http_request_t *r, ngx_chain_t *in); static ngx_int_t ngx_http_modsecurity_preconfiguration(ngx_conf_t *cf); static ngx_int_t ngx_http_modsecurity_init(ngx_conf_t *cf); static ngx_int_t ngx_http_modsecurity_init_process(ngx_cycle_t *cycle); @@ -58,13 +61,12 @@ static void ngx_http_modsecurity_exit_process(ngx_cycle_t *cycle); static void *ngx_http_modsecurity_create_loc_conf(ngx_conf_t *cf); static char *ngx_http_modsecurity_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child); static char *ngx_http_modsecurity_config(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); -apr_status_t modsecurity_read_body_cb(request_rec *r, char *buf, unsigned int length, - unsigned int *readcnt, int *is_eos); -apr_status_t modsecurity_write_body_cb(request_rec *rec, char *buf, unsigned int length); +static char *ngx_http_modsecurity_enable(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static ngx_http_modsecurity_ctx_t * ngx_http_modsecurity_create_ctx(ngx_http_request_t *r); static int ngx_http_modsecurity_drop_action(request_rec *r); static void ngx_http_modsecurity_cleanup(void *data); +static char *ConvertNgxStringToUTF8(ngx_str_t str, apr_pool_t *pool); /* command handled by the module */ static ngx_command_t ngx_http_modsecurity_commands[] = { @@ -77,7 +79,7 @@ static ngx_command_t ngx_http_modsecurity_commands[] = { { ngx_string("ModSecurityEnabled"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_SIF_CONF |NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_CONF_TAKE1, - ngx_conf_set_flag_slot, + ngx_http_modsecurity_enable, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_modsecurity_loc_conf_t, enable), NULL }, @@ -118,6 +120,40 @@ ngx_module_t ngx_http_modsecurity = { NGX_MODULE_V1_PADDING }; +static inline ngx_int_t +ngx_list_copy_to_apr_table(ngx_list_t *list, apr_table_t *table, apr_pool_t *pool) { + ngx_list_part_t *part; + ngx_table_elt_t *h; + ngx_uint_t i; + char *key, *value; + + part = &list->part; + h = part->elts; + + for (i = 0; ; i++) { + if (i >= part->nelts) { + if (part->next == NULL) + break; + + part = part->next; + h = part->elts; + i = 0; + } + + key = ConvertNgxStringToUTF8(h[i].key, pool); + if (key == NULL) { + return NGX_ERROR; + } + + value = ConvertNgxStringToUTF8(h[i].value, pool); + if (value == NULL) { + return NGX_ERROR; + } + + apr_table_setn(table, key, value); + } + return NGX_OK; +} /* create loc conf struct */ static void * @@ -125,10 +161,11 @@ ngx_http_modsecurity_create_loc_conf(ngx_conf_t *cf) { ngx_http_modsecurity_loc_conf_t *conf; - conf = (ngx_http_modsecurity_loc_conf_t *) ngx_pcalloc(cf->pool, sizeof(ngx_http_modsecurity_loc_conf_t)); + conf = (ngx_http_modsecurity_loc_conf_t *) ngx_palloc(cf->pool, sizeof(ngx_http_modsecurity_loc_conf_t)); if (conf == NULL) return NULL; + conf->config = NGX_CONF_UNSET_PTR; conf->enable = NGX_CONF_UNSET; return conf; @@ -142,16 +179,20 @@ ngx_http_modsecurity_merge_loc_conf(ngx_conf_t *cf, void *parent, ngx_http_modsecurity_loc_conf_t *prev = parent; ngx_http_modsecurity_loc_conf_t *conf = child; - if (conf->config == NULL) { - conf->config = prev->config; - } - ngx_conf_merge_value(conf->enable, prev->enable, 0); + ngx_conf_merge_ptr_value(conf->config, prev->config, NULL); + + if (conf->enable && conf->config == NULL) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "ModSecurity: enabled in %V:%ui while no config file is specified ", + conf->file, conf->line); + return NGX_CONF_ERROR; + } return NGX_CONF_OK; } -void +static void modsecLog(void *obj, int level, char *str) { if (obj != NULL) { @@ -169,13 +210,13 @@ modsecLog(void *obj, int level, char *str) */ extern apr_pool_t *pool; -void * +static void * modsec_pcre_malloc(size_t size) { return apr_palloc(pool, size); } -void +static void modsec_pcre_free(void *ptr) { } @@ -190,8 +231,6 @@ ngx_http_modsecurity_preconfiguration(ngx_conf_t *cf) modsecSetLogHook(cf->log, modsecLog); modsecSetDropAction(ngx_http_modsecurity_drop_action); - modsecSetReadBody(modsecurity_read_body_cb); - modsecSetWriteBody(modsecurity_write_body_cb); modsecInit(); modsecStartConfig(); @@ -199,6 +238,9 @@ ngx_http_modsecurity_preconfiguration(ngx_conf_t *cf) return NGX_OK; } +static ngx_http_output_header_filter_pt ngx_http_next_header_filter; +static ngx_http_output_body_filter_pt ngx_http_next_body_filter; + static ngx_int_t ngx_http_modsecurity_init(ngx_conf_t *cf) { @@ -218,20 +260,11 @@ ngx_http_modsecurity_init(ngx_conf_t *cf) } *h = ngx_http_modsecurity_handler; -#ifdef PROCESS_RESPONSE - /* - ** This function sets up handlers for CONTENT_PHASE, - ** XXX: not implemented yet - */ - - /* Register for CONTENT phase ?? */ - h = ngx_array_push(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers); - if (h == NULL) { - return NGX_ERROR; - } - *h = ngx_http_modsecurity_content_handler; -#endif + ngx_http_next_header_filter = ngx_http_top_header_filter; + ngx_http_top_header_filter = ngx_http_modsecurity_header_filter; + ngx_http_next_body_filter = ngx_http_top_body_filter; + ngx_http_top_body_filter = ngx_http_modsecurity_body_filter; return NGX_OK; } @@ -251,10 +284,14 @@ ngx_http_modsecurity_exit_process(ngx_cycle_t *cycle) } -char * +static char * ConvertNgxStringToUTF8(ngx_str_t str, apr_pool_t *pool) { char *t = (char *) apr_palloc(pool, str.len + 1); + + if (!t) { + return NULL; + } ngx_memcpy(t, str.data, str.len); t[str.len] = 0; @@ -262,114 +299,7 @@ ConvertNgxStringToUTF8(ngx_str_t str, apr_pool_t *pool) return t; } -/* -** request body callback, passing body to mod security -*/ -apr_status_t -modsecurity_read_body_cb(request_rec *r, char *outpos, unsigned int length, - unsigned int *outlen, int *is_eos) -{ - size_t len, rest; - ssize_t size; - ngx_http_modsecurity_ctx_t *ctx; - - ctx = (ngx_http_modsecurity_ctx_t *) apr_table_get(r->notes, NOTE_NGINX_REQUEST_CTX); - if (ctx == NULL) { - return APR_EINVAL; - } - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ctx->r->connection->log, 0, "modSecurity: read_body_cb"); - - ngx_buf_t *buf = &ctx->buf; - rest = length; - *is_eos = 0; - - while (rest) { - - if (ngx_buf_size(buf) == 0) { - if (ctx->chain == NULL) { - *outlen = length - rest; - *is_eos = 1; - // END - return APR_SUCCESS; - } - - ngx_memcpy(buf, ctx->chain->buf, sizeof(ngx_buf_t)); - ctx->chain = ctx->chain->next; - } - - len = (size_t) ngx_min((size_t)ngx_buf_size(buf), rest); - - if (ngx_buf_in_memory(buf)) { - - outpos = (char *) ngx_cpymem(outpos, buf->pos, len); - rest -= len; - buf->pos += len; - } else if (buf->in_file) { - - size = ngx_read_file(buf->file, (u_char*)outpos, len, buf->file_pos); - - if (size < 0) { - return NGX_ERROR; - } - outpos += size; - rest -= size; - buf->file_pos += size; - } else { - return -1; - } - } - - *outlen = length - rest; - return APR_SUCCESS; -} - -apr_status_t -modsecurity_write_body_cb(request_rec *rec, char *buf, unsigned int length) -{ - ngx_buf_t *b; - ngx_http_modsecurity_ctx_t *ctx; - ngx_http_request_t *r; - ngx_str_t *str; - - ctx = (ngx_http_modsecurity_ctx_t *) apr_table_get(rec->notes, NOTE_NGINX_REQUEST_CTX); - if (ctx == NULL) { - return APR_EINVAL; - } - - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ctx->r->connection->log, 0, "modSecurity: write_body_cb"); - - r = ctx->r; - - /* set request body */ - b = r->header_in; - - if (b->end < b->pos + length) { - b->start = ngx_palloc(ctx->r->pool, length); - if (b->start == NULL) { - return APR_EINVAL; - } - b->end = b->start + length; - b->pos = b->start; - } - - b->last = ngx_cpymem(b->pos, buf, length); - - /* set content_length_n */ - r->headers_in.content_length_n = length; - - /* set headers_in.content_length */ - str = &r->headers_in.content_length->value; - str->data = ngx_palloc(r->pool, NGX_OFF_T_LEN); - if (str->data == NULL) { - return NGX_ERROR; - } - - str->len = ngx_snprintf(str->data, NGX_OFF_T_LEN, "%O", length) - str->data; - - return APR_SUCCESS; -} - -apr_sockaddr_t *CopySockAddr(apr_pool_t *pool, struct sockaddr *pAddr) { +static apr_sockaddr_t *CopySockAddr(apr_pool_t *pool, struct sockaddr *pAddr) { apr_sockaddr_t *addr = (apr_sockaddr_t *)apr_palloc(pool, sizeof(apr_sockaddr_t)); int adrlen = 16, iplen = 4; @@ -411,21 +341,15 @@ static ngx_int_t ngx_http_modsecurity_handler(ngx_http_request_t *r) { ngx_http_modsecurity_loc_conf_t *cf; - ngx_http_core_loc_conf_t *clcf, *lcf; ngx_http_modsecurity_ctx_t *ctx; ngx_int_t rc; - void **loc_conf; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "modSecurity: handler"); - /* Process only main request */ - if (r != r->main || r->internal) { - return NGX_DECLINED; - } - cf = ngx_http_get_module_loc_conf(r, ngx_http_modsecurity); - if (!cf->enable) { + /* Process only main request */ + if (r != r->main || r->internal || !cf->enable) { return NGX_DECLINED; } @@ -435,98 +359,252 @@ ngx_http_modsecurity_handler(ngx_http_request_t *r) } ngx_http_set_ctx(r, ctx, ngx_http_modsecurity); - if (r->method == NGX_HTTP_POST) { - /* Processing POST request body, should we process PUT? */ - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "modSecurity: method POST"); + /* processing request headers */ + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "modSecurity: process request headers"); + + rc = modsecProcessRequestHeaders(ctx->req); - clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + if (rc == DECLINED) { + if (r->method == NGX_HTTP_POST) { + /* Processing POST request body, should we process PUT? */ + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "modSecurity: method POST"); - if (clcf == NULL) { + rc = ngx_http_read_client_request_body(r, ngx_http_modsecurity_body_handler); + if (rc >= NGX_HTTP_SPECIAL_RESPONSE) { + return rc; + } + + return NGX_DONE; + } + /* other method */ + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "modSecurity: process request body"); + rc = modsecProcessRequestBody(ctx->req); + } + + if (rc != DECLINED) { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "ModSecurity: status: %d, need action", rc); + + /* Nginx and Apache share same response code */ + if (rc < NGX_HTTP_SPECIAL_RESPONSE || rc >= 600) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } + return rc; + } - ctx->loc_conf = r->loc_conf; - /* hijack loc_conf so that we can receive any body length - * TODO: nonblocking process & chuncked body - */ - if (clcf->client_body_buffer_size < (size_t)r->headers_in.content_length_n) { - - loc_conf = ngx_pcalloc(r->pool, sizeof(void *) * ngx_http_max_module); - if (loc_conf == NULL) { - return NGX_HTTP_INTERNAL_SERVER_ERROR; - } + return NGX_DECLINED; +} - lcf = ngx_pcalloc(r->pool, sizeof(ngx_http_core_loc_conf_t)); - if (lcf == NULL) { - return NGX_HTTP_INTERNAL_SERVER_ERROR; - } + +static void +ngx_http_modsecurity_body_handler(ngx_http_request_t *r) +{ + ngx_http_modsecurity_ctx_t *ctx; + ngx_int_t rc; + apr_off_t content_length; + ngx_str_t *str; + ngx_buf_t *buf; + + if (r->request_body == NULL || r->request_body->bufs == NULL) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "modSecurity: request body empty"); + r->phase_handler++; + ngx_http_core_run_phases(r); + ngx_http_finalize_request(r, NGX_DONE); + return; + } + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "modSecurity: process request body"); + + ctx = ngx_http_get_module_ctx(r, ngx_http_modsecurity); + + rc = move_chain_to_brigade(r->request_body->bufs, ctx->brigade, r->pool); + if (rc == NGX_ERROR) { + return ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + } + + r->request_body = NULL; + + modsecSetBodyBrigade(ctx->req, ctx->brigade); - ngx_memcpy(loc_conf, r->loc_conf, sizeof(void *) * ngx_http_max_module); - ngx_memcpy(lcf, clcf, sizeof(ngx_http_core_loc_conf_t)); - - ctx->loc_conf = r->loc_conf; - r->loc_conf = loc_conf; + rc = modsecProcessRequestBody(ctx->req); - ngx_http_get_module_loc_conf(r, ngx_http_core_module) = lcf; - clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); - clcf->client_body_buffer_size = r->headers_in.content_length_n; + if (rc != DECLINED) { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "ModSecurity: status: %d, need action", rc); + + /* Nginx and Apache share same response code */ + if (rc < NGX_HTTP_SPECIAL_RESPONSE || rc >= 600) { + rc = NGX_HTTP_INTERNAL_SERVER_ERROR; } - - ctx->request_body_in_single_buf = r->request_body_in_single_buf; - ctx->request_body_in_file_only = r->request_body_in_file_only; - r->request_body_in_single_buf = 1; - r->request_body_in_file_only = 0; + return ngx_http_finalize_request(r, rc); + } - rc = ngx_http_read_client_request_body(r, ngx_http_modsecurity_request_body_handler); - if (rc >= NGX_HTTP_SPECIAL_RESPONSE) { - return rc; + apr_brigade_length(ctx->brigade, 0, &content_length); + buf = ngx_create_temp_buf(ctx->r->pool, (size_t) content_length); + if (buf == NULL){ + return ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + } + + if (apr_brigade_flatten(ctx->brigade, (char *)buf->pos, (apr_size_t *)&content_length) != APR_SUCCESS) { + return ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + } + + apr_brigade_cleanup(ctx->brigade); + + buf->last += content_length; + r->header_in = buf; + + if (r->headers_in.content_length) { + str = &r->headers_in.content_length->value; + str->data = ngx_palloc(r->pool, NGX_OFF_T_LEN); + if (str->data == NULL) { + return ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); } + str->len = ngx_snprintf(str->data, NGX_OFF_T_LEN, "%O", content_length) - str->data; + } + + r->headers_in.content_length_n = content_length; + + r->phase_handler++; + ngx_http_core_run_phases(r); + ngx_http_finalize_request(r, NGX_DONE); +} + + +static ngx_int_t +ngx_http_modsecurity_header_filter(ngx_http_request_t *r) { + ngx_http_modsecurity_loc_conf_t *cf; + ngx_http_modsecurity_ctx_t *ctx; + const char *lang; + + cf = ngx_http_get_module_loc_conf(r, ngx_http_modsecurity); + ctx = ngx_http_get_module_ctx(r, ngx_http_modsecurity); - return NGX_DONE; + if (r != r->main || r->internal || !cf->enable || ctx->complete) { + return ngx_http_next_header_filter(r); + } + + if (r->method == NGX_HTTP_HEAD || r->header_only + || !modsecIsResponseBodyAccessEnabled(ctx->req) ) { + /* TODO: RESPONSE HEADERS PHASE + */ + return ngx_http_next_header_filter(r); + } + + /* copy headers_out */ + if (ngx_list_copy_to_apr_table(&r->headers_out.headers, + ctx->req->headers_out, + ctx->req->pool) != NGX_OK) { + + return NGX_ERROR; + } + + ctx->req->content_type = ConvertNgxStringToUTF8(r->headers_out.content_type, ctx->req->pool); + ctx->req->content_encoding = apr_table_get(ctx->req->headers_out, "Content-Encoding"); + lang = apr_table_get(ctx->req->headers_out, "Content-Languages"); + + if(lang != NULL) + { + ctx->req->content_languages = apr_array_make(ctx->req->pool, 1, sizeof(const char *)); + *(const char **)apr_array_push(ctx->req->content_languages) = lang; + } - } else { - /* processing all the other methods */ - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "modSecurity: method is not POST"); + return NGX_OK; +} + +static ngx_int_t +ngx_http_modsecurity_body_filter(ngx_http_request_t *r, ngx_chain_t *in) +{ + ngx_http_modsecurity_loc_conf_t *cf; + ngx_http_modsecurity_ctx_t *ctx; + ngx_int_t rc; + apr_off_t content_length; + + cf = ngx_http_get_module_loc_conf(r, ngx_http_modsecurity); + ctx = ngx_http_get_module_ctx(r, ngx_http_modsecurity); + + if (r != r->main || r->internal || !cf->enable || ctx->complete) { + return ngx_http_next_body_filter(r, in); + } + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "modSecurity: body filter"); + + if (in == NULL) { + return NGX_AGAIN; + } + + rc = move_chain_to_brigade(in, ctx->brigade, r->pool); + if (rc == NGX_ERROR) { + return NGX_ERROR; + } else if (rc == NGX_AGAIN) { + return NGX_AGAIN; + } + + /* last buf has been saved */ + + ctx->complete = 1; + modsecSetResponseBrigade(ctx->req, ctx->brigade); + + rc = modsecProcessResponse(ctx->req); + + if (rc == DECLINED || rc == APR_SUCCESS) { + + in = NULL; + + apr_brigade_length(ctx->brigade, 0, &content_length); - rc = modsecProcessRequest(ctx->req); + rc = move_brigade_to_chain(ctx->brigade, &in, ctx->r->pool); + if (rc == NGX_ERROR) { + return NGX_ERROR; + } - if (rc != DECLINED) { - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "ModSecurity: status: %d, need action", rc); + if (r->headers_out.content_length_n != -1) { + + r->headers_out.content_length_n = content_length; + r->headers_out.content_length = NULL; /* header filter will set this */ + } + + rc = ngx_http_next_header_filter(r); - ngx_http_clear_accept_ranges(r); - ngx_http_clear_last_modified(r); - ngx_http_clear_content_length(r); + if (rc == NGX_ERROR || rc > NGX_OK) { + return rc; + } - /* Nginx and Apache share same response code */ - if (rc >= NGX_HTTP_SPECIAL_RESPONSE) { - return rc; - } - return NGX_HTTP_INTERNAL_SERVER_ERROR; - } + rc = ngx_http_next_body_filter(r, in); + if (rc == NGX_ERROR || rc >= NGX_HTTP_SPECIAL_RESPONSE) { + return rc; + } + + return NGX_OK; + } + + if (rc < NGX_HTTP_SPECIAL_RESPONSE || rc >= 600) { + rc = NGX_HTTP_INTERNAL_SERVER_ERROR; } - return NGX_DECLINED; + return rc; /* ngx_http_filter_finalize_request(r, &ngx_http_modsecurity, rc); */ } + static ngx_http_modsecurity_ctx_t * ngx_http_modsecurity_create_ctx(ngx_http_request_t *r) { ngx_http_modsecurity_loc_conf_t *cf; ngx_pool_cleanup_t *cln; ngx_http_modsecurity_ctx_t *ctx; - ngx_list_part_t *part; - ngx_table_elt_t *h; - ngx_uint_t i; + const char *lang; + ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_modsecurity_ctx_t)); + if (ctx == NULL) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "modSecurity: ctx memory allocation error"); + return NULL; + } cln = ngx_pool_cleanup_add(r->pool, sizeof(ngx_http_modsecurity_ctx_t)); if (cln == NULL) { ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "modSecurity: ctx memory allocation error"); return NULL; } cln->handler = ngx_http_modsecurity_cleanup; - ngx_memzero(cln->data, sizeof(ngx_http_modsecurity_ctx_t)); + cln->data = ctx; - ctx = cln->data; ctx->r = r; if (r->connection->requests == 0 || ctx->connection == NULL) { @@ -565,27 +643,31 @@ ngx_http_modsecurity_create_ctx(ngx_http_request_t *r) ctx->req->parsed_uri.user = NULL; ctx->req->parsed_uri.fragment = ConvertNgxStringToUTF8(r->exten, ctx->req->pool); - part = &r->headers_in.headers.part; - h = part->elts; + if (ngx_list_copy_to_apr_table(&r->headers_in.headers, + ctx->req->headers_in, + ctx->req->pool) != NGX_OK) { + return NULL; + } - for (i = 0; ; i++) { - if (i >= part->nelts) { - if (part->next == NULL) - break; + /* XXX: if mod_uniqid enabled - use it's value */ + apr_table_setn(ctx->req->subprocess_env, "UNIQUE_ID", "12345"); - part = part->next; - h = part->elts; - i = 0; - } + ctx->req->content_encoding = apr_table_get(ctx->req->headers_in, "Content-Encoding"); + ctx->req->content_type = apr_table_get(ctx->req->headers_in, "Content-Type"); + + lang = apr_table_get(ctx->req->headers_in, "Content-Languages"); + if(lang != NULL) + { + ctx->req->content_languages = apr_array_make(ctx->req->pool, 1, sizeof(const char *)); - apr_table_setn(ctx->req->headers_in, ConvertNgxStringToUTF8(h[i].key, ctx->req->pool), - ConvertNgxStringToUTF8(h[i].value, ctx->req->pool)); + *(const char **)apr_array_push(ctx->req->content_languages) = lang; } - /* XXX: if mod_uniqid enabled - use it's value */ - apr_table_setn(ctx->req->subprocess_env, "UNIQUE_ID", "12345"); - /* actually, we need ctx only for POST request body handling - don't like this part */ - apr_table_setn(ctx->req->notes, NOTE_NGINX_REQUEST_CTX, (const char *) ctx); + ctx->brigade = apr_brigade_create(ctx->req->pool, ctx->req->connection->bucket_alloc); + + if (ctx->brigade == NULL) { + return NULL; + } return ctx; } @@ -599,52 +681,6 @@ ngx_http_modsecurity_cleanup(void *data) } } - -static void -ngx_http_modsecurity_request_body_handler(ngx_http_request_t *r) -{ - ngx_http_modsecurity_ctx_t *ctx; - ngx_int_t rc; - - ctx = ngx_http_get_module_ctx(r, ngx_http_modsecurity); - - if (ctx == NULL - || r->request_body->bufs == NULL - || r->request_body->bufs->next != NULL) { - ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); - return; - } - - r->request_body_in_single_buf = ctx->request_body_in_single_buf; - r->request_body_in_file_only = ctx->request_body_in_file_only; - r->header_in = r->request_body->bufs->buf; - ctx->chain = r->request_body->bufs; - r->request_body = NULL; - r->loc_conf = ctx->loc_conf; - - rc = modsecProcessRequest(ctx->req); - - if (rc != DECLINED) { - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "ModSecurity: status: %d, need action", rc); - - ngx_http_clear_accept_ranges(r); - ngx_http_clear_last_modified(r); - ngx_http_clear_content_length(r); - - /* Nginx and Apache share same response code */ - if (rc < NGX_HTTP_SPECIAL_RESPONSE) { - rc = NGX_HTTP_INTERNAL_SERVER_ERROR; - } - - ngx_http_finalize_request(r, rc); - } - - r->phase_handler++; - ngx_http_core_run_phases(r); - ngx_http_finalize_request(r, NGX_DONE); -} - - static char * ngx_http_modsecurity_config(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { @@ -652,7 +688,7 @@ ngx_http_modsecurity_config(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) ngx_str_t *value; const char *msg; - if (mscf->config != NULL) { + if (mscf->config != NGX_CONF_UNSET_PTR) { return "is duplicate"; } @@ -664,14 +700,35 @@ ngx_http_modsecurity_config(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) mscf->config = modsecGetDefaultConfig(); - msg = modsecProcessConfig(mscf->config, (const char *)value[1].data, ""); + if (mscf->config == NULL) { + return NGX_CONF_ERROR; + } + + msg = modsecProcessConfig(mscf->config, (const char *)value[1].data, NULL); if (msg != NULL) { - ngx_conf_log_error(NGX_LOG_INFO, cf, 0, "modSecurity: modsecProcessConfig() %s", msg); + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "modSecurity: modsecProcessConfig() %s", msg); return NGX_CONF_ERROR; } return NGX_CONF_OK; } +static char * +ngx_http_modsecurity_enable(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_http_modsecurity_loc_conf_t *mscf = conf; + char *rc; + + rc = ngx_conf_set_flag_slot(cf, cmd, conf); + if (rc != NGX_CONF_OK) { + return rc; + } + if (mscf->enable) { + mscf->file = &cf->conf_file->file.name; + mscf->line = cf->conf_file->line; + } + return NGX_CONF_OK; +} + static int ngx_http_modsecurity_drop_action(request_rec *r) @@ -685,3 +742,4 @@ ngx_http_modsecurity_drop_action(request_rec *r) ctx->r->connection->error = 1; return 0; } + diff --git a/standalone/Makefile.in b/standalone/Makefile.in index aa6e8acf7c..0d23ac0823 100644 --- a/standalone/Makefile.in +++ b/standalone/Makefile.in @@ -1,9 +1,9 @@ -# Makefile.in generated by automake 1.11.1 from Makefile.am. +# Makefile.in generated by automake 1.11.3 from Makefile.am. # @configure_input@ # Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, -# 2003, 2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation, -# Inc. +# 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 Free Software +# Foundation, Inc. # This Makefile.in is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. @@ -73,6 +73,12 @@ am__nobase_list = $(am__nobase_strip_setup); \ am__base_list = \ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } am__installdirs = "$(DESTDIR)$(pkglibdir)" LTLIBRARIES = $(pkglib_LTLIBRARIES) standalone_la_DEPENDENCIES = @@ -166,6 +172,7 @@ CURL_VERSION = @CURL_VERSION@ CYGPATH_W = @CYGPATH_W@ DEFS = @DEFS@ DEPDIR = @DEPDIR@ +DLLTOOL = @DLLTOOL@ DSYMUTIL = @DSYMUTIL@ DUMPBIN = @DUMPBIN@ ECHO_C = @ECHO_C@ @@ -200,6 +207,7 @@ LUA_CFLAGS = @LUA_CFLAGS@ LUA_LDADD = @LUA_LDADD@ LUA_LDFLAGS = @LUA_LDFLAGS@ MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ MKDIR_P = @MKDIR_P@ MODSEC_APXS_EXTRA_CFLAGS = @MODSEC_APXS_EXTRA_CFLAGS@ MODSEC_EXTRA_CFLAGS = @MODSEC_EXTRA_CFLAGS@ @@ -230,6 +238,7 @@ PCRE_CONFIG = @PCRE_CONFIG@ PCRE_CPPFLAGS = @PCRE_CPPFLAGS@ PCRE_LDADD = @PCRE_LDADD@ PCRE_LDFLAGS = @PCRE_LDFLAGS@ +PCRE_LD_PATH = @PCRE_LD_PATH@ PCRE_VERSION = @PCRE_VERSION@ PERL = @PERL@ PKG_CONFIG = @PKG_CONFIG@ @@ -246,6 +255,7 @@ abs_builddir = @abs_builddir@ abs_srcdir = @abs_srcdir@ abs_top_builddir = @abs_top_builddir@ abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ ac_ct_CC = @ac_ct_CC@ ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ am__include = @am__include@ @@ -278,7 +288,6 @@ libdir = @libdir@ libexecdir = @libexecdir@ localedir = @localedir@ localstatedir = @localstatedir@ -lt_ECHO = @lt_ECHO@ mandir = @mandir@ mkdir_p = @mkdir_p@ oldincludedir = @oldincludedir@ @@ -415,7 +424,7 @@ clean-pkglibLTLIBRARIES: echo "rm -f \"$${dir}/so_locations\""; \ rm -f "$${dir}/so_locations"; \ done -standalone.la: $(standalone_la_OBJECTS) $(standalone_la_DEPENDENCIES) +standalone.la: $(standalone_la_OBJECTS) $(standalone_la_DEPENDENCIES) $(EXTRA_standalone_la_DEPENDENCIES) $(standalone_la_LINK) -rpath $(pkglibdir) $(standalone_la_OBJECTS) $(standalone_la_LIBADD) $(LIBS) mostlyclean-compile: @@ -815,10 +824,15 @@ install-am: all-am installcheck: installcheck-am install-strip: - $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ - install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ - `test -z '$(STRIP)' || \ - echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi mostlyclean-generic: clean-generic: diff --git a/standalone/api.c b/standalone/api.c index 10cd65f080..85a224132a 100644 --- a/standalone/api.c +++ b/standalone/api.c @@ -1,578 +1,639 @@ -/* -* ModSecurity for Apache 2.x, http://www.modsecurity.org/ -* Copyright (c) 2004-2011 Trustwave Holdings, Inc. (http://www.trustwave.com/) -* -* You may not use this file except in compliance with -* the License.  You may obtain a copy of the License at -* -*     http://www.apache.org/licenses/LICENSE-2.0 -* -* If any of the files related to licensing are missing or if you have any -* other questions related to licensing please contact Trustwave Holdings, Inc. -* directly using the email address security@modsecurity.org. -*/ - -#include -#include - -#include "http_core.h" -#include "http_request.h" - -#include "modsecurity.h" -#include "apache2.h" -#include "http_main.h" -#include "http_connection.h" - -#include "apr_optional.h" -#include "mod_log_config.h" - -#include "msc_logging.h" -#include "msc_util.h" - -#include "ap_mpm.h" -#include "scoreboard.h" - -#include "apr_version.h" - -#include "apr_lib.h" -#include "ap_config.h" -#include "http_config.h" - - -extern void *modsecLogObj; -extern void (*modsecLogHook)(void *obj, int level, char *str); -extern int (*modsecDropAction)(request_rec *r); -apr_status_t (*modsecReadBody)(request_rec *r, char *buf, unsigned int length, unsigned int *readcnt, int *is_eos); -apr_status_t (*modsecReadResponse)(request_rec *r, char *buf, unsigned int length, unsigned int *readcnt, int *is_eos); -apr_status_t (*modsecWriteBody)(request_rec *r, char *buf, unsigned int length); -apr_status_t (*modsecWriteResponse)(request_rec *r, char *buf, unsigned int length); - -extern const char *process_command_config(server_rec *s, - void *mconfig, - apr_pool_t *p, - apr_pool_t *ptemp, - const char *filename); - -#define DECLARE_EXTERNAL_HOOK(ns,link,ret,name,args) \ -extern ns##_HOOK_##name##_t *hookfn_##name; - -#define DECLARE_HOOK(ret,name,args) \ - DECLARE_EXTERNAL_HOOK(ap,AP,ret,name,args) - -DECLARE_HOOK(int,pre_config,(apr_pool_t *pconf,apr_pool_t *plog, apr_pool_t *ptemp)) -DECLARE_HOOK(int,post_config,(apr_pool_t *pconf,apr_pool_t *plog, apr_pool_t *ptemp,server_rec *s)) -DECLARE_HOOK(void,child_init,(apr_pool_t *pchild, server_rec *s)) -DECLARE_HOOK(int,process_connection,(conn_rec *c)) -DECLARE_HOOK(int,post_read_request,(request_rec *r)) -DECLARE_HOOK(int,fixups,(request_rec *r)) -DECLARE_HOOK(void, error_log, (const char *file, int line, int level, - apr_status_t status, const server_rec *s, - const request_rec *r, apr_pool_t *pool, - const char *errstr)) -DECLARE_HOOK(int,log_transaction,(request_rec *r)) -DECLARE_HOOK(void,insert_filter,(request_rec *r)) -DECLARE_HOOK(void,insert_error_filter,(request_rec *r)) - -char *sa_name = "standalone"; -server_rec *server; -apr_pool_t *pool = NULL; - -apr_status_t ap_http_in_filter(ap_filter_t *f, apr_bucket_brigade *b, - ap_input_mode_t mode, apr_read_type_e block, - apr_off_t readbytes); -apr_status_t ap_http_out_filter(ap_filter_t *f, apr_bucket_brigade *b); - -server_rec *modsecInit() { - apr_initialize(); - - apr_pool_create(&pool, NULL); - - apr_hook_global_pool = pool; - - server = apr_palloc(pool, sizeof(server_rec)); - - server->addrs = apr_palloc(pool, sizeof(server_addr_rec)); - server->addrs->host_addr = apr_palloc(pool, sizeof(apr_sockaddr_t)); - server->addrs->host_addr->addr_str_len = 16; - server->addrs->host_addr->family = AF_INET; - server->addrs->host_addr->hostname = sa_name; -#ifdef WIN32 - server->addrs->host_addr->ipaddr_len = sizeof(IN_ADDR); -#else - server->addrs->host_addr->ipaddr_len = sizeof(struct in_addr); -#endif - server->addrs->host_addr->ipaddr_ptr = &server->addrs->host_addr->sa.sin.sin_addr; - server->addrs->host_addr->pool = pool; - server->addrs->host_addr->port = 80; -#ifdef WIN32 - server->addrs->host_addr->sa.sin.sin_addr.S_un.S_addr = 0x0100007f; -#else - server->addrs->host_addr->sa.sin.sin_addr.s_addr = 0x0100007f; -#endif - server->addrs->host_addr->sa.sin.sin_family = AF_INET; - server->addrs->host_addr->sa.sin.sin_port = 80; - server->addrs->host_addr->salen = sizeof(server->addrs->host_addr->sa); - server->addrs->host_addr->servname = sa_name; - server->addrs->host_port = 80; - server->error_fname = "error.log"; - server->error_log = NULL; - server->limit_req_fields = 1024; - server->limit_req_fieldsize = 1024; - server->limit_req_line = 1024; -#if AP_SERVER_MAJORVERSION_NUMBER > 1 && AP_SERVER_MINORVERSION_NUMBER < 3 - server->loglevel = APLOG_DEBUG; -#endif - server->lookup_defaults = NULL; - server->module_config = NULL; - server->names = NULL; -#ifdef WIN32 - server->path = "c:\\inetpub\\wwwroot"; -#else - server->path = "/var/www"; -#endif - server->pathlen = strlen(server->path); - server->port = 80; - server->process = apr_palloc(pool, sizeof(process_rec)); - server->process->argc = 1; - server->process->argv = &sa_name; - server->process->pconf = pool; - server->process->pool = pool; - server->process->short_name = sa_name; - server->server_admin = sa_name; - server->server_hostname = sa_name; - server->server_scheme = ""; - server->timeout = 60 * 1000000;// 60 seconds - server->wild_names = NULL; - server->is_virtual = 0; - - ap_server_config_defines = apr_array_make(pool, 1, sizeof(char *)); - - // here we should add scoreboard handling for multiple processes and threads - // - ap_scoreboard_image = (scoreboard *)apr_palloc(pool, sizeof(scoreboard)); - - memset(ap_scoreboard_image, 0, sizeof(scoreboard)); - - // ---------- - - security2_module.module_index = 0; - - security2_module.register_hooks(pool); - - ap_register_input_filter("HTTP_IN", ap_http_in_filter, NULL, AP_FTYPE_RESOURCE); - ap_register_output_filter("HTTP_OUT", ap_http_out_filter, NULL, AP_FTYPE_CONTENT_SET); - - return server; -} - -apr_status_t ap_http_in_filter(ap_filter_t *f, apr_bucket_brigade *b, - ap_input_mode_t mode, apr_read_type_e block, - apr_off_t readbytes) { - char *tmp = NULL; - apr_bucket *e = NULL; - unsigned int readcnt = 0; - int is_eos = 0; - - if(modsecReadBody == NULL) - return AP_NOBODY_READ; - - tmp = (char *)apr_palloc(f->r->pool, readbytes); - modsecReadBody(f->r, tmp, readbytes, &readcnt, &is_eos); - - e = apr_bucket_pool_create(tmp, readcnt, f->r->pool, f->c->bucket_alloc); - APR_BRIGADE_INSERT_TAIL(b, e); - - if(is_eos) { - e = apr_bucket_eos_create(f->c->bucket_alloc); - APR_BRIGADE_INSERT_TAIL(b, e); - } - - return APR_SUCCESS; -} - -apr_status_t ap_http_out_filter(ap_filter_t *f, apr_bucket_brigade *b) { - modsec_rec *msr = (modsec_rec *)f->ctx; - apr_status_t rc; - - // is there a way to tell whether the response body was modified or not? - // - if((msr->txcfg->content_injection_enabled || msr->content_prepend_len != 0 || msr->content_append_len != 0) - && modsecWriteResponse != NULL && msr->txcfg->resbody_access) { - char *data = NULL; - apr_size_t length; - - rc = apr_brigade_pflatten(msr->of_brigade, &data, &length, msr->mp); - - if (rc != APR_SUCCESS) { - msr_log(msr, 1, "Output filter: Failed to flatten brigade (%d): %s", rc, - get_apr_error(msr->mp, rc)); - return -1; - } - - modsecWriteResponse(msr->r, data, msr->stream_output_length); - } - - return APR_SUCCESS; -} - -void modsecTerminate() { - apr_pool_destroy(pool); - pool = NULL; - apr_terminate(); -} - -void modsecStartConfig() { - apr_pool_t *ptemp = NULL; - - apr_pool_create(&ptemp, pool); - - hookfn_pre_config(pool, pool, ptemp); - - apr_pool_destroy(ptemp); -} - -directory_config *modsecGetDefaultConfig() { - return (directory_config *)security2_module.create_dir_config(pool, NULL); -} - -const char *modsecProcessConfig(directory_config *config, const char *file, const char *dir) { - apr_pool_t *ptemp = NULL; - const char *err; - apr_status_t status; - const char *rootpath, *incpath; - - if(dir == NULL || strlen(dir) == 0) -#ifdef WIN32 - dir = "\\"; -#else - dir = "/"; -#endif - - incpath = file; - - /* locate the start of the directories proper */ - status = apr_filepath_root(&rootpath, &incpath, APR_FILEPATH_TRUENAME | APR_FILEPATH_NATIVE, pool); - - /* we allow APR_SUCCESS and APR_EINCOMPLETE */ - if (APR_ERELATIVE == status) { - int li = strlen(dir) - 1; - - if(dir[li] != '/' && dir[li] != '\\') -#ifdef WIN32 - file = apr_pstrcat(pool, dir, "\\", file, NULL); -#else - file = apr_pstrcat(pool, dir, "/", file, NULL); -#endif - else - file = apr_pstrcat(pool, dir, file, NULL); - } - else if (APR_EBADPATH == status) { - return apr_pstrcat(pool, "Config file has a bad path, ", file, NULL); - } - - apr_pool_create(&ptemp, pool); - - err = process_command_config(server, config, pool, ptemp, file); - - apr_pool_destroy(ptemp); - - return err; -} - -void modsecFinalizeConfig() { - apr_pool_t *ptemp = NULL; - - apr_pool_create(&ptemp, pool); - - hookfn_post_config(pool, pool, ptemp, server); - hookfn_post_config(pool, pool, ptemp, server); - - apr_pool_destroy(ptemp); -} - -void modsecInitProcess() { - hookfn_child_init(pool, server); -} - -conn_rec *modsecNewConnection() { - conn_rec *c; - apr_pool_t *pc = NULL; - - apr_pool_create(&pc, pool); - - c = apr_pcalloc(pc, sizeof(conn_rec)); - - c->base_server = server; - c->id = 1; - c->local_addr = server->addrs->host_addr; - c->local_host = sa_name; - c->local_ip = "127.0.0.1"; - c->pool = pc; - c->remote_host = sa_name; -#if AP_SERVER_MAJORVERSION_NUMBER > 1 && AP_SERVER_MINORVERSION_NUMBER < 3 - c->remote_ip = "127.0.0.1"; - c->remote_addr = server->addrs->host_addr; -#else - c->client_ip = "127.0.0.1"; - c->client_addr = server->addrs->host_addr; -#endif - c->input_filters = NULL; - c->output_filters = NULL; - c->bucket_alloc = apr_bucket_alloc_create(pc); - - return c; -} - -void modsecProcessConnection(conn_rec *c) { - hookfn_process_connection(c); -} - -request_rec *modsecNewRequest(conn_rec *connection, directory_config *config) { - request_rec *r; - apr_pool_t *pr = NULL; - - apr_pool_create(&pr, connection->pool); - - r = apr_pcalloc(pr, sizeof(request_rec)); - - r->connection = connection; - r->server = server; - r->pool = pr; - r->main = NULL; - r->next = NULL; - r->notes = apr_table_make(pr, 10); - r->per_dir_config = apr_palloc(pr, sizeof(void *)); - ((void **)r->per_dir_config)[0] = config; - r->prev = NULL; - r->subprocess_env = apr_table_make(pr, 10); - apr_table_setn(r->subprocess_env, "UNIQUE_ID", "unique_id"); - r->user = NULL; - - r->headers_in = apr_table_make(pr, 10); - r->headers_out = apr_table_make(pr, 10); - r->err_headers_out = apr_table_make(pr, 10); - //apr_table_setn(r->headers_in, "Host", "www.google.com"); - //apr_table_setn(r->headers_in, "", ""); - - r->the_request = "GET /../../index.html HTTP/1.1"; - r->method = "GET"; - r->method_number = M_GET; - r->protocol = "HTTP/1.1"; - r->uri = "http://www.google.com/../../index.html"; - r->args = ""; - r->filename = "/../../index.html"; - r->handler = "IIS"; - - r->parsed_uri.scheme = "http"; - r->parsed_uri.path = "/../../index.html"; - r->parsed_uri.hostname = "www.google.com"; - r->parsed_uri.is_initialized = 1; - r->parsed_uri.port = 1234; - r->parsed_uri.port_str = "1234"; - r->parsed_uri.query = ""; - r->parsed_uri.dns_looked_up = 0; - r->parsed_uri.dns_resolved = 0; - r->parsed_uri.password = NULL; - r->parsed_uri.user = NULL; - r->parsed_uri.fragment = ""; - - r->input_filters = NULL; - r->output_filters = NULL; - - return r; -} - -static modsec_rec *retrieve_msr(request_rec *r) { - modsec_rec *msr = NULL; - request_rec *rx = NULL; - - /* Look in the current request first. */ - msr = (modsec_rec *)apr_table_get(r->notes, NOTE_MSR); - if (msr != NULL) { - msr->r = r; - return msr; - } - - /* If this is a subrequest then look in the main request. */ - if (r->main != NULL) { - msr = (modsec_rec *)apr_table_get(r->main->notes, NOTE_MSR); - if (msr != NULL) { - msr->r = r; - return msr; - } - } - - /* If the request was redirected then look in the previous requests. */ - rx = r->prev; - while(rx != NULL) { - msr = (modsec_rec *)apr_table_get(rx->notes, NOTE_MSR); - if (msr != NULL) { - msr->r = r; - return msr; - } - rx = rx->prev; - } - - return NULL; -} - -int modsecProcessRequest(request_rec *r) { - int status = DECLINED; - modsec_rec *msr = NULL; - - ap_filter_t *f = ap_add_input_filter("HTTP_IN", NULL, r, r->connection); - - status = hookfn_post_read_request(r); - status = hookfn_fixups(r); - - ap_remove_input_filter(f); - - hookfn_insert_filter(r); - - /* Find the transaction context first. */ - msr = retrieve_msr(r); - - if (msr == NULL) - return status; - - if(msr->stream_input_data != NULL && modsecWriteBody != NULL) - { - // target is responsible for copying the data into correctly managed buffer - // - modsecWriteBody(r, msr->stream_input_data, msr->stream_input_length); - - free(msr->stream_input_data); - - msr->stream_input_data = NULL; - } - - // leftover code possibly for future use - // - //if(r->input_filters != NULL && r->input_filters->frec->filter_init_func != NULL) - //r->input_filters->frec->filter_init_func(r->input_filters); - //if(r->input_filters != NULL && r->input_filters->frec->filter_func.in_func != NULL) - //r->input_filters->frec->filter_func.in_func(r->input_filters, NULL, 0, 0, 0); - - return status; -} - -void modsecSetConfigForIISRequestBody(request_rec *r) -{ - modsec_rec *msr = retrieve_msr(r); - - if(msr == NULL || msr->txcfg == NULL) - return; - - if(msr->txcfg->reqbody_access) - msr->txcfg->stream_inbody_inspection = 1; -} - -int modsecIsResponseBodyAccessEnabled(request_rec *r) -{ - modsec_rec *msr = retrieve_msr(r); - - if(msr == NULL || msr->txcfg == NULL) - return 0; - - return msr->txcfg->resbody_access; -} - -int modsecProcessResponse(request_rec *r) { - int status = DECLINED; - - if(r->output_filters != NULL) { - modsec_rec *msr = (modsec_rec *)r->output_filters->ctx; - char buf[8192]; - char *tmp = NULL; - apr_bucket *e = NULL; - unsigned int readcnt = 0; - int is_eos = 0; - ap_filter_t *f = NULL; - apr_bucket_brigade *bb = NULL; - - if (msr == NULL) { - ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r->server, - "ModSecurity: Internal Error: msr is null in output filter."); - ap_remove_output_filter(r->output_filters); - return send_error_bucket(msr, r->output_filters, HTTP_INTERNAL_SERVER_ERROR); - } - - bb = apr_brigade_create(msr->mp, r->connection->bucket_alloc); - - if (bb == NULL) { - msr_log(msr, 1, "Process response: Failed to create brigade."); - return -1; - } - - msr->r = r; - - if(modsecReadResponse == NULL) - return AP_NOBODY_WROTE; - - f = ap_add_output_filter("HTTP_OUT", msr, r, r->connection); - - while(!is_eos) { - modsecReadResponse(r, buf, 8192, &readcnt, &is_eos); - - if(readcnt > 0) { - tmp = (char *)apr_palloc(r->pool, readcnt); - memcpy(tmp, buf, readcnt); - - e = apr_bucket_pool_create(tmp, readcnt, r->pool, r->connection->bucket_alloc); - APR_BRIGADE_INSERT_TAIL(bb, e); - } - - if(is_eos) { - e = apr_bucket_eos_create(r->connection->bucket_alloc); - - APR_BRIGADE_INSERT_TAIL(bb, e); - } - } - - status = ap_pass_brigade(r->output_filters, bb); - - ap_remove_output_filter(f); - } - - return status; -} - -int modsecFinishRequest(request_rec *r) { - // run output filter - //if(r->output_filters != NULL && r->output_filters->frec->filter_init_func != NULL) - //r->output_filters->frec->filter_init_func(r->output_filters); - - hookfn_log_transaction(r); - - // make sure you cleanup before calling apr_terminate() - // otherwise double-free might occur, because of the request body pool cleanup function - // - apr_pool_destroy(r->connection->pool); - - return DECLINED; -} - -void modsecSetLogHook(void *obj, void (*hook)(void *obj, int level, char *str)) { - modsecLogObj = obj; - modsecLogHook = hook; -} - -void modsecSetReadBody(apr_status_t (*func)(request_rec *r, char *buf, unsigned int length, unsigned int *readcnt, int *is_eos)) { - modsecReadBody = func; -} - -void modsecSetReadResponse(apr_status_t (*func)(request_rec *r, char *buf, unsigned int length, unsigned int *readcnt, int *is_eos)) { - modsecReadResponse = func; -} - -void modsecSetWriteBody(apr_status_t (*func)(request_rec *r, char *buf, unsigned int length)) { - modsecWriteBody = func; -} - -void modsecSetWriteResponse(apr_status_t (*func)(request_rec *r, char *buf, unsigned int length)) { - modsecWriteResponse = func; -} - -void modsecSetDropAction(int (*func)(request_rec *r)) { - modsecDropAction = func; -} +/* +* ModSecurity for Apache 2.x, http://www.modsecurity.org/ +* Copyright (c) 2004-2011 Trustwave Holdings, Inc. (http://www.trustwave.com/) +* +* You may not use this file except in compliance with +* the License.  You may obtain a copy of the License at +* +*     http://www.apache.org/licenses/LICENSE-2.0 +* +* If any of the files related to licensing are missing or if you have any +* other questions related to licensing please contact Trustwave Holdings, Inc. +* directly using the email address security@modsecurity.org. +*/ + +#include +#include + +#include "http_core.h" +#include "http_request.h" + +#include "modsecurity.h" +#include "apache2.h" +#include "http_main.h" +#include "http_connection.h" + +#include "apr_optional.h" +#include "mod_log_config.h" + +#include "msc_logging.h" +#include "msc_util.h" + +#include "ap_mpm.h" +#include "scoreboard.h" + +#include "apr_version.h" + +#include "apr_lib.h" +#include "ap_config.h" +#include "http_config.h" + +#include "api.h" + +extern void *modsecLogObj; +extern void (*modsecLogHook)(void *obj, int level, char *str); +extern int (*modsecDropAction)(request_rec *r); +apr_status_t (*modsecReadBody)(request_rec *r, char *buf, unsigned int length, unsigned int *readcnt, int *is_eos); +apr_status_t (*modsecReadResponse)(request_rec *r, char *buf, unsigned int length, unsigned int *readcnt, int *is_eos); +apr_status_t (*modsecWriteBody)(request_rec *r, char *buf, unsigned int length); +apr_status_t (*modsecWriteResponse)(request_rec *r, char *buf, unsigned int length); + +extern const char *process_command_config(server_rec *s, + void *mconfig, + apr_pool_t *p, + apr_pool_t *ptemp, + const char *filename); + +#define DECLARE_EXTERNAL_HOOK(ns,link,ret,name,args) \ +extern ns##_HOOK_##name##_t *hookfn_##name; + +#define DECLARE_HOOK(ret,name,args) \ + DECLARE_EXTERNAL_HOOK(ap,AP,ret,name,args) + +DECLARE_HOOK(int,pre_config,(apr_pool_t *pconf,apr_pool_t *plog, apr_pool_t *ptemp)) +DECLARE_HOOK(int,post_config,(apr_pool_t *pconf,apr_pool_t *plog, apr_pool_t *ptemp,server_rec *s)) +DECLARE_HOOK(void,child_init,(apr_pool_t *pchild, server_rec *s)) +DECLARE_HOOK(int,process_connection,(conn_rec *c)) +DECLARE_HOOK(int,post_read_request,(request_rec *r)) +DECLARE_HOOK(int,fixups,(request_rec *r)) +DECLARE_HOOK(void, error_log, (const char *file, int line, int level, + apr_status_t status, const server_rec *s, + const request_rec *r, apr_pool_t *pool, + const char *errstr)) +DECLARE_HOOK(int,log_transaction,(request_rec *r)) +DECLARE_HOOK(void,insert_filter,(request_rec *r)) +DECLARE_HOOK(void,insert_error_filter,(request_rec *r)) + +char *sa_name = "standalone"; +server_rec *server; +apr_pool_t *pool = NULL; + +apr_status_t ap_http_in_filter(ap_filter_t *f, apr_bucket_brigade *b, + ap_input_mode_t mode, apr_read_type_e block, + apr_off_t readbytes); +apr_status_t ap_http_out_filter(ap_filter_t *f, apr_bucket_brigade *b); + +server_rec *modsecInit() { + apr_initialize(); + + apr_pool_create(&pool, NULL); + + apr_hook_global_pool = pool; + + server = apr_palloc(pool, sizeof(server_rec)); + + server->addrs = apr_palloc(pool, sizeof(server_addr_rec)); + server->addrs->host_addr = apr_palloc(pool, sizeof(apr_sockaddr_t)); + server->addrs->host_addr->addr_str_len = 16; + server->addrs->host_addr->family = AF_INET; + server->addrs->host_addr->hostname = sa_name; +#ifdef WIN32 + server->addrs->host_addr->ipaddr_len = sizeof(IN_ADDR); +#else + server->addrs->host_addr->ipaddr_len = sizeof(struct in_addr); +#endif + server->addrs->host_addr->ipaddr_ptr = &server->addrs->host_addr->sa.sin.sin_addr; + server->addrs->host_addr->pool = pool; + server->addrs->host_addr->port = 80; +#ifdef WIN32 + server->addrs->host_addr->sa.sin.sin_addr.S_un.S_addr = 0x0100007f; +#else + server->addrs->host_addr->sa.sin.sin_addr.s_addr = 0x0100007f; +#endif + server->addrs->host_addr->sa.sin.sin_family = AF_INET; + server->addrs->host_addr->sa.sin.sin_port = 80; + server->addrs->host_addr->salen = sizeof(server->addrs->host_addr->sa); + server->addrs->host_addr->servname = sa_name; + server->addrs->host_port = 80; + server->error_fname = "error.log"; + server->error_log = NULL; + server->limit_req_fields = 1024; + server->limit_req_fieldsize = 1024; + server->limit_req_line = 1024; +#if AP_SERVER_MAJORVERSION_NUMBER > 1 && AP_SERVER_MINORVERSION_NUMBER < 3 + server->loglevel = APLOG_DEBUG; +#endif + server->lookup_defaults = NULL; + server->module_config = NULL; + server->names = NULL; +#ifdef WIN32 + server->path = "c:\\inetpub\\wwwroot"; +#else + server->path = "/var/www"; +#endif + server->pathlen = strlen(server->path); + server->port = 80; + server->process = apr_palloc(pool, sizeof(process_rec)); + server->process->argc = 1; + server->process->argv = &sa_name; + server->process->pconf = pool; + server->process->pool = pool; + server->process->short_name = sa_name; + server->server_admin = sa_name; + server->server_hostname = sa_name; + server->server_scheme = ""; + server->timeout = 60 * 1000000;// 60 seconds + server->wild_names = NULL; + server->is_virtual = 0; + + ap_server_config_defines = apr_array_make(pool, 1, sizeof(char *)); + + // here we should add scoreboard handling for multiple processes and threads + // + ap_scoreboard_image = (scoreboard *)apr_palloc(pool, sizeof(scoreboard)); + + memset(ap_scoreboard_image, 0, sizeof(scoreboard)); + + // ---------- + + security2_module.module_index = 0; + + security2_module.register_hooks(pool); + + ap_register_input_filter("HTTP_IN", ap_http_in_filter, NULL, AP_FTYPE_RESOURCE); + ap_register_output_filter("HTTP_OUT", ap_http_out_filter, NULL, AP_FTYPE_CONTENT_SET); + + return server; +} + +apr_status_t ap_http_in_filter(ap_filter_t *f, apr_bucket_brigade *bb_out, + ap_input_mode_t mode, apr_read_type_e block, + apr_off_t readbytes) { + char *tmp = NULL; + apr_bucket *e = NULL; + unsigned int readcnt = 0; + int is_eos = 0; + apr_bucket_brigade *bb_in; + apr_bucket *after; + apr_status_t rv; + + bb_in = modsecGetBodyBrigade(f->r); + + /* use request brigade */ + if (bb_in != NULL) { + if (!APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(bb_in))) { + e = apr_bucket_eos_create(f->c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(bb_in, e); + } + + rv = apr_brigade_partition(bb_in, readbytes, &after); + if (rv != APR_SUCCESS && rv != APR_INCOMPLETE) { + return rv; + } + + for (e = APR_BRIGADE_FIRST(bb_in); e != after; e = APR_BRIGADE_FIRST(bb_in)) { + APR_BUCKET_REMOVE(e); + APR_BRIGADE_INSERT_TAIL(bb_out, e); + } + + return APR_SUCCESS; + } + + /* call the callback */ + if(modsecReadBody != NULL) { + + tmp = (char *)apr_palloc(f->r->pool, readbytes); + modsecReadBody(f->r, tmp, readbytes, &readcnt, &is_eos); + + e = apr_bucket_pool_create(tmp, readcnt, f->r->pool, f->c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(bb_out, e); + + if(is_eos) { + e = apr_bucket_eos_create(f->c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(bb_out, e); + } + return APR_SUCCESS; + } + + return AP_NOBODY_READ; +} + +apr_status_t ap_http_out_filter(ap_filter_t *f, apr_bucket_brigade *b) { + modsec_rec *msr = (modsec_rec *)f->ctx; + apr_status_t rc; + apr_bucket_brigade *bb_out; + + bb_out = modsecGetResponseBrigade(f->r); + + + if (bb_out) { + APR_BRIGADE_CONCAT(bb_out, b); + return APR_SUCCESS; + } + + // is there a way to tell whether the response body was modified or not? + // + if((msr->txcfg->content_injection_enabled || msr->content_prepend_len != 0 || msr->content_append_len != 0) + && msr->txcfg->resbody_access) { + + if (modsecWriteResponse != NULL) { + char *data = NULL; + apr_size_t length; + + rc = apr_brigade_pflatten(msr->of_brigade, &data, &length, msr->mp); + + if (rc != APR_SUCCESS) { + msr_log(msr, 1, "Output filter: Failed to flatten brigade (%d): %s", rc, + get_apr_error(msr->mp, rc)); + return -1; + } + + /* TODO: return ?*/ + modsecWriteResponse(msr->r, data, msr->stream_output_length); + } + } + + return APR_SUCCESS; +} + +void modsecTerminate() { + apr_pool_destroy(pool); + pool = NULL; + apr_terminate(); +} + +void modsecStartConfig() { + apr_pool_t *ptemp = NULL; + + apr_pool_create(&ptemp, pool); + + hookfn_pre_config(pool, pool, ptemp); + + apr_pool_destroy(ptemp); +} + +directory_config *modsecGetDefaultConfig() { + return (directory_config *)security2_module.create_dir_config(pool, NULL); +} + +const char *modsecProcessConfig(directory_config *config, const char *file, const char *dir) { + apr_pool_t *ptemp = NULL; + const char *err; + apr_status_t status; + const char *rootpath, *incpath; + + if(dir == NULL || strlen(dir) == 0) +#ifdef WIN32 + dir = "\\"; +#else + dir = "/"; +#endif + + incpath = file; + + /* locate the start of the directories proper */ + status = apr_filepath_root(&rootpath, &incpath, APR_FILEPATH_TRUENAME | APR_FILEPATH_NATIVE, pool); + + /* we allow APR_SUCCESS and APR_EINCOMPLETE */ + if (APR_ERELATIVE == status) { + int li = strlen(dir) - 1; + + if(dir[li] != '/' && dir[li] != '\\') +#ifdef WIN32 + file = apr_pstrcat(pool, dir, "\\", file, NULL); +#else + file = apr_pstrcat(pool, dir, "/", file, NULL); +#endif + else + file = apr_pstrcat(pool, dir, file, NULL); + } + else if (APR_EBADPATH == status) { + return apr_pstrcat(pool, "Config file has a bad path, ", file, NULL); + } + + apr_pool_create(&ptemp, pool); + + err = process_command_config(server, config, pool, ptemp, file); + + apr_pool_destroy(ptemp); + + return err; +} + +void modsecFinalizeConfig() { + apr_pool_t *ptemp = NULL; + + apr_pool_create(&ptemp, pool); + + hookfn_post_config(pool, pool, ptemp, server); + hookfn_post_config(pool, pool, ptemp, server); + + apr_pool_destroy(ptemp); +} + +void modsecInitProcess() { + hookfn_child_init(pool, server); +} + +conn_rec *modsecNewConnection() { + conn_rec *c; + apr_pool_t *pc = NULL; + + apr_pool_create(&pc, pool); + + c = apr_pcalloc(pc, sizeof(conn_rec)); + + c->base_server = server; + c->id = 1; + c->local_addr = server->addrs->host_addr; + c->local_host = sa_name; + c->local_ip = "127.0.0.1"; + c->pool = pc; + c->remote_host = sa_name; +#if AP_SERVER_MAJORVERSION_NUMBER > 1 && AP_SERVER_MINORVERSION_NUMBER < 3 + c->remote_ip = "127.0.0.1"; + c->remote_addr = server->addrs->host_addr; +#else + c->client_ip = "127.0.0.1"; + c->client_addr = server->addrs->host_addr; +#endif + c->input_filters = NULL; + c->output_filters = NULL; + c->bucket_alloc = apr_bucket_alloc_create(pc); + + return c; +} + +void modsecProcessConnection(conn_rec *c) { + hookfn_process_connection(c); +} + +request_rec *modsecNewRequest(conn_rec *connection, directory_config *config) { + request_rec *r; + apr_pool_t *pr = NULL; + + apr_pool_create(&pr, connection->pool); + + r = apr_pcalloc(pr, sizeof(request_rec)); + + r->connection = connection; + r->server = server; + r->pool = pr; + r->main = NULL; + r->next = NULL; + r->notes = apr_table_make(pr, 10); + r->per_dir_config = apr_palloc(pr, sizeof(void *)); + ((void **)r->per_dir_config)[0] = config; + r->prev = NULL; + r->subprocess_env = apr_table_make(pr, 10); + apr_table_setn(r->subprocess_env, "UNIQUE_ID", "unique_id"); + r->user = NULL; + + r->headers_in = apr_table_make(pr, 10); + r->headers_out = apr_table_make(pr, 10); + r->err_headers_out = apr_table_make(pr, 10); + //apr_table_setn(r->headers_in, "Host", "www.google.com"); + //apr_table_setn(r->headers_in, "", ""); + + r->the_request = "GET /../../index.html HTTP/1.1"; + r->method = "GET"; + r->method_number = M_GET; + r->protocol = "HTTP/1.1"; + r->uri = "http://www.google.com/../../index.html"; + r->args = ""; + r->filename = "/../../index.html"; + r->handler = "IIS"; + + r->parsed_uri.scheme = "http"; + r->parsed_uri.path = "/../../index.html"; + r->parsed_uri.hostname = "www.google.com"; + r->parsed_uri.is_initialized = 1; + r->parsed_uri.port = 1234; + r->parsed_uri.port_str = "1234"; + r->parsed_uri.query = ""; + r->parsed_uri.dns_looked_up = 0; + r->parsed_uri.dns_resolved = 0; + r->parsed_uri.password = NULL; + r->parsed_uri.user = NULL; + r->parsed_uri.fragment = ""; + + r->input_filters = NULL; + r->output_filters = NULL; + + return r; +} + +static modsec_rec *retrieve_msr(request_rec *r) { + modsec_rec *msr = NULL; + request_rec *rx = NULL; + + /* Look in the current request first. */ + msr = (modsec_rec *)apr_table_get(r->notes, NOTE_MSR); + if (msr != NULL) { + msr->r = r; + return msr; + } + + /* If this is a subrequest then look in the main request. */ + if (r->main != NULL) { + msr = (modsec_rec *)apr_table_get(r->main->notes, NOTE_MSR); + if (msr != NULL) { + msr->r = r; + return msr; + } + } + + /* If the request was redirected then look in the previous requests. */ + rx = r->prev; + while(rx != NULL) { + msr = (modsec_rec *)apr_table_get(rx->notes, NOTE_MSR); + if (msr != NULL) { + msr->r = r; + return msr; + } + rx = rx->prev; + } + + return NULL; +} + +int modsecProcessRequestHeaders(request_rec *r) { + return hookfn_post_read_request(r); +} + +int modsecProcessRequestBody(request_rec *r) { + int status = DECLINED; + modsec_rec *msr = NULL; + + ap_filter_t *f = ap_add_input_filter("HTTP_IN", NULL, r, r->connection); + apr_bucket_brigade* bb_out; + + status = hookfn_fixups(r); + + ap_remove_input_filter(f); + + hookfn_insert_filter(r); + + /* Find the transaction context first. */ + msr = retrieve_msr(r); + + if (msr == NULL) + return status; + + bb_out = modsecGetBodyBrigade(r); + if (bb_out) { + (void) apr_brigade_cleanup(bb_out); + status = ap_get_brigade(r->input_filters, bb_out, AP_MODE_READBYTES, APR_BLOCK_READ, -1); + if (status == APR_SUCCESS) { + return DECLINED; + } + return status; + } + + if(msr->stream_input_data != NULL && modsecWriteBody != NULL) + { + // target is responsible for copying the data into correctly managed buffer + // + modsecWriteBody(r, msr->stream_input_data, msr->stream_input_length); + + free(msr->stream_input_data); + + msr->stream_input_data = NULL; + } + + // leftover code possibly for future use + // + //if(r->input_filters != NULL && r->input_filters->frec->filter_init_func != NULL) + //r->input_filters->frec->filter_init_func(r->input_filters); + //if(r->input_filters != NULL && r->input_filters->frec->filter_func.in_func != NULL) + //r->input_filters->frec->filter_func.in_func(r->input_filters, NULL, 0, 0, 0); + + return status; +} + +void modsecSetConfigForIISRequestBody(request_rec *r) +{ + modsec_rec *msr = retrieve_msr(r); + + if(msr == NULL || msr->txcfg == NULL) + return; + + if(msr->txcfg->reqbody_access) + msr->txcfg->stream_inbody_inspection = 1; +} + +int modsecIsResponseBodyAccessEnabled(request_rec *r) +{ + modsec_rec *msr = retrieve_msr(r); + + if(msr == NULL || msr->txcfg == NULL) + return 0; + + return msr->txcfg->resbody_access; +} + +int modsecProcessResponse(request_rec *r) { + int status = DECLINED; + + if(r->output_filters != NULL) { + modsec_rec *msr = (modsec_rec *)r->output_filters->ctx; + char buf[8192]; + char *tmp = NULL; + apr_bucket *e = NULL; + unsigned int readcnt = 0; + int is_eos = 0; + ap_filter_t *f = NULL; + apr_bucket_brigade *bb_in, *bb = NULL; + + if (msr == NULL) { + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r->server, + "ModSecurity: Internal Error: msr is null in output filter."); + ap_remove_output_filter(r->output_filters); + return send_error_bucket(msr, r->output_filters, HTTP_INTERNAL_SERVER_ERROR); + } + + bb = apr_brigade_create(msr->mp, r->connection->bucket_alloc); + + if (bb == NULL) { + msr_log(msr, 1, "Process response: Failed to create brigade."); + return -1; + } + + msr->r = r; + + bb_in = modsecGetResponseBrigade(r); + + if (bb_in != NULL) { + APR_BRIGADE_CONCAT(bb, bb_in); + if (!APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(bb))) { + e = apr_bucket_eos_create(bb->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(bb, e); + } + } else if (modsecReadResponse != NULL) { + while(!is_eos) { + modsecReadResponse(r, buf, 8192, &readcnt, &is_eos); + + if(readcnt > 0) { + tmp = (char *)apr_palloc(r->pool, readcnt); + memcpy(tmp, buf, readcnt); + e = apr_bucket_pool_create(tmp, readcnt, r->pool, r->connection->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(bb, e); + } + + if(is_eos) { + e = apr_bucket_eos_create(r->connection->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(bb, e); + } + } + } else { + return AP_NOBODY_WROTE; + } + + f = ap_add_output_filter("HTTP_OUT", msr, r, r->connection); + status = ap_pass_brigade(r->output_filters, bb); + ap_remove_output_filter(f); + return status; + } + + return status; +} + +int modsecFinishRequest(request_rec *r) { + // run output filter + //if(r->output_filters != NULL && r->output_filters->frec->filter_init_func != NULL) + //r->output_filters->frec->filter_init_func(r->output_filters); + + hookfn_log_transaction(r); + + // make sure you cleanup before calling apr_terminate() + // otherwise double-free might occur, because of the request body pool cleanup function + // + apr_pool_destroy(r->connection->pool); + + return DECLINED; +} + +void modsecSetLogHook(void *obj, void (*hook)(void *obj, int level, char *str)) { + modsecLogObj = obj; + modsecLogHook = hook; +} + +void modsecSetReadBody(apr_status_t (*func)(request_rec *r, char *buf, unsigned int length, unsigned int *readcnt, int *is_eos)) { + modsecReadBody = func; +} + +void modsecSetReadResponse(apr_status_t (*func)(request_rec *r, char *buf, unsigned int length, unsigned int *readcnt, int *is_eos)) { + modsecReadResponse = func; +} + +void modsecSetWriteBody(apr_status_t (*func)(request_rec *r, char *buf, unsigned int length)) { + modsecWriteBody = func; +} + +void modsecSetWriteResponse(apr_status_t (*func)(request_rec *r, char *buf, unsigned int length)) { + modsecWriteResponse = func; +} + +void modsecSetDropAction(int (*func)(request_rec *r)) { + modsecDropAction = func; +} diff --git a/standalone/api.h b/standalone/api.h index 7ba998d562..d70e76cd4f 100644 --- a/standalone/api.h +++ b/standalone/api.h @@ -60,12 +60,49 @@ conn_rec *modsecNewConnection(); void modsecProcessConnection(conn_rec *c); request_rec *modsecNewRequest(conn_rec *connection, directory_config *config); -int modsecProcessRequest(request_rec *r); + +int modsecProcessRequestBody(request_rec *r); +int modsecProcessRequestHeaders(request_rec *r); + +static inline int modsecProcessRequest(request_rec *r) { + int status; + status = modsecProcessRequestHeaders(r); + if (status != DECLINED) { + return status; + } + + return modsecProcessRequestBody(r); +} + + int modsecProcessResponse(request_rec *r); int modsecFinishRequest(request_rec *r); void modsecSetLogHook(void *obj, void (*hook)(void *obj, int level, char *str)); +#define NOTE_MSR_BRIGADE_REQUEST "modsecurity-brigade-request" +#define NOTE_MSR_BRIGADE_RESPONSE "modsecurity-brigade-response" + +static inline void +modsecSetBodyBrigade(request_rec *r, apr_bucket_brigade *b) { + apr_table_setn(r->notes, NOTE_MSR_BRIGADE_REQUEST, (char *)b); +}; + +static inline apr_bucket_brigade * +modsecGetBodyBrigade(request_rec *r) { + return (apr_bucket_brigade *)apr_table_get(r->notes, NOTE_MSR_BRIGADE_REQUEST); +}; + +static inline void +modsecSetResponseBrigade(request_rec *r, apr_bucket_brigade *b) { + apr_table_setn(r->notes, NOTE_MSR_BRIGADE_RESPONSE, (char *)b); +}; + +static inline apr_bucket_brigade * +modsecGetResponseBrigade(request_rec *r) { + return (apr_bucket_brigade *)apr_table_get(r->notes, NOTE_MSR_BRIGADE_RESPONSE); +}; + void modsecSetReadBody(apr_status_t (*func)(request_rec *r, char *buf, unsigned int length, unsigned int *readcnt, int *is_eos)); void modsecSetReadResponse(apr_status_t (*func)(request_rec *r, char *buf, unsigned int length, unsigned int *readcnt, int *is_eos)); void modsecSetWriteBody(apr_status_t (*func)(request_rec *r, char *buf, unsigned int length));