diff --git a/nginx/modsecurity/ngx_http_modsecurity.c b/nginx/modsecurity/ngx_http_modsecurity.c new file mode 100644 index 00000000..18e42b23 --- /dev/null +++ b/nginx/modsecurity/ngx_http_modsecurity.c @@ -0,0 +1,1036 @@ +/* +* 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 +#include +#include +#include +#include +#include + +#undef CR +#undef LF +#undef CRLF + +#include "api.h" + +#define NOTE_NGINX_REQUEST_CTX "nginx-ctx" + +extern ngx_module_t ngx_http_modsecurity; + +struct ngx_http_modsecurity_ctx_s; + +typedef ngx_int_t (*ngx_http_request_body_data_handler_pt) + (struct ngx_http_modsecurity_ctx_s*, u_char *, u_char*); + +typedef struct { + ngx_uint_t enable; + char *config_path; + directory_config *config; + ngx_str_t url; + ngx_http_complex_value_t *url_cv; +} ngx_http_modsecurity_loc_conf_t; + +typedef struct ngx_http_modsecurity_ctx_s { + ngx_http_request_t *r; + conn_rec *connection; + request_rec *req; + ngx_chain_t *chain; + ngx_chain_t *last; + /* used in modSecurity body handler */ + ssize_t received; + ssize_t processed; + ngx_chain_t *body_last; + u_char *body_pos; + ngx_http_request_body_data_handler_pt data_handler; +} ngx_http_modsecurity_ctx_t; + + +/* +** Module's registred function/handlers. +*/ +static ngx_int_t ngx_http_modsecurity_handler(ngx_http_request_t *r); +//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); +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_set_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); + +static ngx_int_t ngx_http_process_request_body(ngx_http_request_t *r, ngx_chain_t *body); +ngx_int_t ngx_http_read_upload_client_request_body(ngx_http_request_t *r); +static void ngx_http_read_upload_client_request_body_handler(ngx_http_request_t *r); +static ngx_int_t upload_process_buf(ngx_http_modsecurity_ctx_t *ctx, u_char *start, u_char *end); +static ngx_int_t ngx_http_do_read_upload_client_request_body(ngx_http_request_t *r); +static ngx_int_t ngx_http_upload_body_handler(ngx_http_request_t *r); +static char *ngx_http_modsecurity_add_handler(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); +static char *ngx_http_modsecurity_pass(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); +static ngx_int_t ngx_http_modsecurity_pass_to_backend(ngx_http_request_t *r); + +/* command handled by the module */ +static ngx_command_t ngx_http_modsecurity_commands[] = { + { ngx_string("ModSecurityConfig"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, + ngx_http_modsecurity_set_config, + NGX_HTTP_LOC_CONF_OFFSET, + 0, + NULL }, + { 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_http_modsecurity_add_handler, + NGX_HTTP_LOC_CONF_OFFSET, + 0, + NULL }, + { ngx_string("ModSecurityPass"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, + ngx_http_modsecurity_pass, + NGX_HTTP_LOC_CONF_OFFSET, + 0, + NULL }, + ngx_null_command +}; + +/* +** handlers for configuration phases of the module +*/ + +static ngx_http_module_t ngx_http_modsecurity_ctx = { + NULL, /* preconfiguration */ +// ngx_http_modsecurity_init, /* postconfiguration */ + NULL, + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + + ngx_http_modsecurity_create_loc_conf, /* create location configuration */ + ngx_http_modsecurity_merge_loc_conf /* merge location configuration */ +}; + + +ngx_module_t ngx_http_modsecurity = { + NGX_MODULE_V1, + &ngx_http_modsecurity_ctx, /* module context */ + ngx_http_modsecurity_commands, /* module directives */ + NGX_HTTP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + ngx_http_modsecurity_init_process, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + ngx_http_modsecurity_exit_process, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + +/* create loc conf struct */ +static void * +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)); + if (conf == NULL) + return NULL; + + conf->enable = NGX_CONF_UNSET; + + return conf; +} + +/* merge loc conf */ +static char * +ngx_http_modsecurity_merge_loc_conf(ngx_conf_t *cf, void *parent, + void *child) +{ + ngx_http_modsecurity_loc_conf_t *prev = parent; + ngx_http_modsecurity_loc_conf_t *conf = child; + + if (conf->config == NULL) { + conf->config = prev->config; + } + + if (conf->config_path == NULL) { + conf->config_path = prev->config_path; + } + + if ((conf->url.len == 0) && (conf->url_cv == NULL)) { + conf->url = prev->url; + conf->url_cv = prev->url_cv; + } + + ngx_conf_merge_uint_value(conf->enable, prev->enable, 0); + + return NGX_CONF_OK; +} + +void +modsecLog(void *obj, int level, char *str) +{ + if (obj != NULL) + ngx_log_error(NGX_LOG_INFO, (ngx_log_t *)obj, 0, "%s", str); +} + +#ifdef PROCESS_RESPONSE +/* +** This function sets up handlers for CONTENT_PHASE, +** XXX: not implemented yet +*/ +static ngx_int_t +ngx_http_modsecurity_init(ngx_conf_t *cf) +{ + ngx_http_handler_pt *h; + ngx_http_core_main_conf_t *cmcf; + + cmcf = (ngx_http_core_main_conf_t *) ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module); + if (cmcf == NULL) { + return NGX_ERROR; + } + + /* 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; + + return NGX_OK; +} +#endif + +static ngx_int_t +ngx_http_modsecurity_init_process(ngx_cycle_t *cycle) +{ + cycle->log->log_level = NGX_LOG_INFO; + + modsecSetLogHook(cycle->log, modsecLog); + + modsecInit(); + modsecStartConfig(); + modsecFinalizeConfig(); + modsecInitProcess(); + + return NGX_OK; +} + +static void +ngx_http_modsecurity_exit_process(ngx_cycle_t *cycle) +{ + modsecTerminate(); +} + +/* +** This is a temporary hack to make PCRE work with ModSecurity +** nginx hijacks pcre_malloc and pcre_free, so we have to re-hijack them +*/ +extern apr_pool_t *pool; + +void * +modsec_pcre_malloc(size_t size) +{ + return apr_palloc(pool, size); +} + +void +modsec_pcre_free(void *ptr) +{ +} + +char * +ConvertNgxStringToUTF8(ngx_str_t str, apr_pool_t *pool) +{ + char *t = (char *) apr_palloc(pool, str.len + 1); + + ngx_memcpy(t, str.data, str.len); + t[str.len] = 0; + + return t; +} + +ngx_int_t +ngx_http_read_upload_client_request_body(ngx_http_request_t *r) { + ssize_t size, preread; + ngx_buf_t *b; + ngx_chain_t *cl, **next; + ngx_http_request_body_t *rb; + ngx_http_core_loc_conf_t *clcf; + ngx_http_modsecurity_ctx_t *ctx; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "modSecurity: ngx_http_read_upload_client_request_body"); + +#if defined nginx_version && nginx_version >= 8011 + r->main->count++; +#endif + + if (r->request_body || r->discard_body) { + return NGX_OK; + } + + rb = ngx_pcalloc(r->pool, sizeof(ngx_http_request_body_t)); + if (rb == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + r->request_body = rb; + + if (r->headers_in.content_length_n <= 0) { + return NGX_HTTP_BAD_REQUEST; + } + + ctx = ngx_http_get_module_ctx(r, ngx_http_modsecurity); + if (ctx == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + /* + * set by ngx_pcalloc(): + * + * rb->bufs = NULL; + * rb->buf = NULL; + * rb->rest = 0; + */ + + preread = r->header_in->last - r->header_in->pos; + + if (preread) { + + /* there is the pre-read part of the request body */ + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "modSecurity: http client request body preread %uz", preread); + + ctx->received = preread; + + b = ngx_calloc_buf(r->pool); + if (b == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + b->temporary = 1; + b->start = r->header_in->pos; + b->pos = r->header_in->pos; + b->last = r->header_in->last; + b->end = r->header_in->end; + + rb->bufs = ngx_alloc_chain_link(r->pool); + if (rb->bufs == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + rb->bufs->buf = b; + rb->bufs->next = NULL; + rb->buf = b; + + if (preread >= r->headers_in.content_length_n) { + + /* the whole request body was pre-read */ + + r->header_in->pos += r->headers_in.content_length_n; + r->request_length += r->headers_in.content_length_n; + + if (ngx_http_process_request_body(r, rb->bufs) != NGX_OK) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + return ngx_http_upload_body_handler(r); + } + + /* + * to not consider the body as pipelined request in + * ngx_http_set_keepalive() + */ + r->header_in->pos = r->header_in->last; + + r->request_length += preread; + + rb->rest = r->headers_in.content_length_n - preread; + + if (rb->rest <= (off_t) (b->end - b->last)) { + + /* the whole request body may be placed in r->header_in */ + + rb->to_write = rb->bufs; + + r->read_event_handler = ngx_http_read_upload_client_request_body_handler; + + return ngx_http_do_read_upload_client_request_body(r); + } + + next = &rb->bufs->next; + + } else { + b = NULL; + rb->rest = r->headers_in.content_length_n; + next = &rb->bufs; + } + + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + + size = clcf->client_body_buffer_size; + size += size >> 2; + + if (rb->rest < (ssize_t) size) { + size = rb->rest; + + if (r->request_body_in_single_buf) { + size += preread; + } + + } else { + size = clcf->client_body_buffer_size; + + /* disable copying buffer for r->request_body_in_single_buf */ + b = NULL; + } + + rb->buf = ngx_create_temp_buf(r->pool, size); + if (rb->buf == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + cl = ngx_alloc_chain_link(r->pool); + if (cl == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + cl->buf = rb->buf; + cl->next = NULL; + + if (b && r->request_body_in_single_buf) { + size = b->last - b->pos; + ngx_memcpy(rb->buf->pos, b->pos, size); + rb->buf->last += size; + + next = &rb->bufs; + } + + *next = cl; + + rb->to_write = rb->bufs; + + r->read_event_handler = ngx_http_read_upload_client_request_body_handler; + + return ngx_http_do_read_upload_client_request_body(r); +} + +static void +ngx_http_read_upload_client_request_body_handler(ngx_http_request_t *r) +{ + ngx_int_t rc; + ngx_event_t *rev = r->connection->read; + ngx_http_core_loc_conf_t *clcf; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "modSecurity: ngx_http_read_upload_client_request_body_handler"); + + if (rev->timedout) { + rev->timedout = 0; + rev->delayed = 0; + + if (!rev->ready) { + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + ngx_add_timer(rev, clcf->client_body_timeout); + + if (ngx_handle_read_event(rev, clcf->send_lowat) != NGX_OK) { + ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + } + return; + } + } + + rc = ngx_http_do_read_upload_client_request_body(r); + if (rc >= NGX_HTTP_SPECIAL_RESPONSE) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "modSecurity: ngx_http_read_upload_client_request_body_handler, finalizing"); + ngx_http_finalize_request(r, rc); + } +} + + +static ngx_int_t +ngx_http_do_read_upload_client_request_body(ngx_http_request_t *r) +{ + ssize_t size, n; + ngx_connection_t *c; + ngx_http_request_body_t *rb; + ngx_int_t rc; + ngx_http_core_loc_conf_t *clcf; + ngx_http_modsecurity_ctx_t *ctx; + + c = r->connection; + rb = r->request_body; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "modSecurity: http read client request body"); + + ctx = ngx_http_get_module_ctx(r, ngx_http_modsecurity); + if (ctx == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + for ( ;; ) { + for ( ;; ) { + if (rb->buf->last == rb->buf->end) { + + rc = ngx_http_process_request_body(r, rb->to_write); + + rb->to_write = rb->bufs->next ? rb->bufs->next : rb->bufs; + rb->buf->last = rb->buf->start; + } + + size = rb->buf->end - rb->buf->last; + + if ((off_t)size > rb->rest) { + size = (size_t)rb->rest; + } + + n = c->recv(c, rb->buf->last, size); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "modSecurity: http client request body recv %z", n); + + if (n == NGX_AGAIN) { + break; + } + + if (n == 0) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "modSecurity: client closed prematurely connection"); + } + + if (n == 0 || n == NGX_ERROR) { + c->error = 1; + return NGX_HTTP_BAD_REQUEST; + } + + rb->buf->last += n; + rb->rest -= n; + r->request_length += n; + ctx->received += n; + + if (rb->rest == 0) { + break; + } + + if (rb->buf->last < rb->buf->end) { + break; + } + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "modSecurity: http client request body rest %uz", rb->rest); + + if (rb->rest == 0) { + break; + } + + if (!c->read->ready) { + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + ngx_add_timer(c->read, clcf->client_body_timeout); + + if (ngx_handle_read_event(c->read, 0) != NGX_OK) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + return NGX_AGAIN; + } + } + + if (c->read->timer_set) { + ngx_del_timer(c->read); + } + + ngx_http_process_request_body(r, rb->to_write); + + return ngx_http_upload_body_handler(r); +} + +static ngx_int_t +ngx_http_process_request_body(ngx_http_request_t *r, ngx_chain_t *body) +{ + ngx_int_t rc; + ngx_http_modsecurity_ctx_t *ctx; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "modSecurity: ngx_http_process_request_body"); + + ctx = ngx_http_get_module_ctx(r, ngx_http_modsecurity); + if (ctx == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + /* Feed all the buffers into data handler */ + while (body) { + rc = ctx->data_handler(ctx, body->buf->pos, body->buf->last); + + if(rc != NGX_OK) + return rc; + + body = body->next; + } + + /* Signal end of body */ + if (r->request_body->rest == 0) { + rc = ctx->data_handler(ctx, NULL, NULL); + + if(rc != NGX_OK) + return rc; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_upload_body_handler(ngx_http_request_t *r) +{ + ngx_int_t rc; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "modSecurity: ngx_http_upload_body_handler"); + + rc = ngx_http_modsecurity_pass_to_backend(r); + if (rc >= NGX_HTTP_SPECIAL_RESPONSE) { + return rc; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_modsecurity_pass_to_backend(ngx_http_request_t *r) +{ + ngx_str_t uri; + ngx_str_t args; + ngx_uint_t flags; + ngx_http_modsecurity_ctx_t *ctx; + ngx_http_modsecurity_loc_conf_t *cf; + ngx_int_t rc; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "modSecurity: pass_to_backend"); + + cf = ngx_http_get_module_loc_conf(r, ngx_http_modsecurity); + if (!cf) { + return NGX_ERROR; + } + + ctx = ngx_http_get_module_ctx(r, ngx_http_modsecurity); + if (ctx == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + if (cf->enable) { + int status = modsecProcessRequest(ctx->req); + +// modsecFinishRequest(r); + + if (status != DECLINED) { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "ModSecurity: status: %d, need action", status); + + ngx_http_clear_accept_ranges(r); + ngx_http_clear_last_modified(r); + ngx_http_clear_content_length(r); + + /* XXX: return correct status */ + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + } + + args = r->args; /* forward the query args */ + flags = 0; + +/* +#if defined nginx_version && nginx_version >= 8011 + r->main->count--; +#endif +*/ + /* XXX: this looks ugly, should we process PUT also? */ + if (r->method == NGX_HTTP_POST && r->request_body) { + r->request_body->bufs = ctx->chain; + } + + if (cf->url_cv) { + /* complex value */ + if (ngx_http_complex_value(r, cf->url_cv, &uri) != NGX_OK) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "modSecurity: uri parsing error #2"); + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + if (uri.len == 0) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "empty \"upload_pass\" (was: \"%V\")", + &cf->url_cv->value); + + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + } else { + /* simple value */ + uri = cf->url; + } + + if (ngx_http_parse_unsafe_uri(r, &uri, &args, &flags) != NGX_OK) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "modSecurity: uri parsing error"); + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + + if (uri.len != 0 && uri.data[0] == '/') { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "modSecurity: using internal redirect"); + rc = ngx_http_internal_redirect(r, &uri, &args); + } else { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "modSecurity: using named location"); + rc = ngx_http_named_location(r, &uri); + } + + return rc; +} + + +static ngx_int_t +upload_process_buf(ngx_http_modsecurity_ctx_t *ctx, u_char *start, u_char *end) +{ + ngx_http_request_t *r = ctx->r; + ngx_buf_t *b; + ngx_chain_t *cl; + + /* No more data? */ + if (start == end) { + return NGX_OK; /* confirm end of stream */ + } + + b = ngx_create_temp_buf(r->pool, (size_t)(end - start)); + if (b == NULL) { + return NGX_ERROR; + } + + cl = ngx_alloc_chain_link(r->pool); + if (cl == NULL) { + return NGX_ERROR; + } + + b->last_in_chain = 0; + + cl->buf = b; + cl->next = NULL; + + b->last = ngx_cpymem(b->last, start, (size_t)(end - start)); + + if (ctx->chain == NULL) { + ctx->chain = cl; + ctx->last = cl; + } else { + ctx->last->next = cl; + ctx->last = cl; + } + + return NGX_OK; +} + +/* +** request body callback, passing body to mod security +*/ +apr_status_t +modsecurity_read_body_cb(request_rec *r, char *buf, unsigned int length, + unsigned int *readcnt, int *is_eos) +{ + ngx_chain_t *body; + ngx_http_modsecurity_ctx_t *ctx; + ngx_buf_t *b; + + 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"); + + if (ctx->processed >= ctx->received) { + *is_eos = 1; + return APR_SUCCESS; + } + + if (ctx->body_last == NULL) { + body = ctx->chain; + } else { + body = ctx->body_last; + } + + if (!body) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ctx->r->connection->log, 0, "modSecurity: no more body left"); + } + + if (body) { + b = body->buf; + if (!ctx->body_pos) { + ctx->body_pos = b->start; + } + if ((b->end - ctx->body_pos) > length) { + ngx_memcpy(buf, (char *) ctx->body_pos, length); + ctx->processed += length; + ctx->body_pos += length; + *readcnt = length; + ctx->body_last = body; + } else { + ngx_memcpy(buf, (char *) ctx->body_pos, (b->end - ctx->body_pos)); + ctx->processed += (b->end - ctx->body_pos); + *readcnt = (b->end - ctx->body_pos); + ctx->body_last = body->next; + ctx->body_pos = NULL; + } + } + + return APR_SUCCESS; +} + +/* +** [ENTRY POINT] does : this function called by nginx from the request handler +*/ +static ngx_int_t +ngx_http_modsecurity_handler(ngx_http_request_t *r) +{ + ngx_http_modsecurity_loc_conf_t *cf; + ngx_http_modsecurity_ctx_t *ctx; + ngx_list_part_t *part; + ngx_table_elt_t *h; + ngx_uint_t i; + ngx_int_t rc; + const char *msg; + + 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) { + return NGX_ERROR; + } + + /* XXX: temporary hack, nginx uses pcre as well and hijacks these two */ + pcre_malloc = modsec_pcre_malloc; + pcre_free = modsec_pcre_free; + + ctx = ngx_http_get_module_ctx(r, ngx_http_modsecurity); + if (ctx == NULL) { + ctx = (ngx_http_modsecurity_ctx_t *) 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 NGX_ERROR; + } + ctx->r = r; + ctx->data_handler = upload_process_buf; + ctx->chain = ctx->last = NULL; + ctx->body_last = NULL; + ctx->body_pos = NULL; + ctx->received = ctx->processed = 0; + ngx_http_set_ctx(r, ctx, ngx_http_modsecurity); + } + + /* do all modsecurity related work only if handler is enabled */ + if (cf->enable) { + if (cf->config == NULL) { + cf->config = modsecGetDefaultConfig(); + + msg = modsecProcessConfig(cf->config, cf->config_path); + if (msg != NULL) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "modSecurity: modsecProcessConfig() %s", msg); + return NGX_ERROR; + } + } + + if (r->connection->requests == 0 || ctx->connection == NULL) { + ctx->connection = modsecNewConnection(); + modsecProcessConnection(ctx->connection); + } + + ctx->req = modsecNewRequest(ctx->connection, cf->config); + ctx->req->request_time = apr_time_now(); + ctx->req->method = ConvertNgxStringToUTF8(r->method_name, ctx->req->pool); + ctx->req->path_info = ConvertNgxStringToUTF8(r->unparsed_uri, ctx->req->pool); + ctx->req->unparsed_uri = ConvertNgxStringToUTF8(r->unparsed_uri, ctx->req->pool); + ctx->req->uri = ctx->req->unparsed_uri; + ctx->req->the_request = ConvertNgxStringToUTF8(r->request_line, ctx->req->pool); + ctx->req->args = ConvertNgxStringToUTF8(r->args, ctx->req->pool); + ctx->req->filename = ctx->req->path_info; + + ctx->req->parsed_uri.scheme = "http"; + ctx->req->parsed_uri.path = ctx->req->path_info; + ctx->req->parsed_uri.is_initialized = 1; + ctx->req->parsed_uri.port = 80; + ctx->req->parsed_uri.port_str = "80"; + ctx->req->parsed_uri.query = ctx->req->args; + ctx->req->parsed_uri.dns_looked_up = 0; + ctx->req->parsed_uri.dns_resolved = 0; + ctx->req->parsed_uri.password = NULL; + 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; + + for (i = 0; ; i++) { + if (i >= part->nelts) { + if (part->next == NULL) + break; + + part = part->next; + h = part->elts; + i = 0; + } + + apr_table_setn(ctx->req->headers_in, ConvertNgxStringToUTF8(h[i].key, ctx->req->pool), + ConvertNgxStringToUTF8(h[i].value, ctx->req->pool)); + } + + /* 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); + } + + 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 (cf->enable) + modsecSetReadBody(modsecurity_read_body_cb); + rc = ngx_http_read_upload_client_request_body(r); + if (rc >= NGX_HTTP_SPECIAL_RESPONSE) { + return rc; + } + } else { + /* processing all the other methods */ + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "modSecurity: method is not POST"); + rc = ngx_http_modsecurity_pass_to_backend(r); + if (rc >= NGX_HTTP_SPECIAL_RESPONSE) { + return rc; + } + } + + return NGX_DONE; +} + +static char * +ngx_http_modsecurity_set_config(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_http_modsecurity_loc_conf_t *ucf = conf; + ngx_str_t *value; + + value = cf->args->elts; + + if (cf->args->nelts == 0 || value[1].len == 0) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "ModSecurity: config path required"); + return NGX_CONF_ERROR; + } + + if (ngx_conf_full_name(cf->cycle, &value[1], 1) != NGX_OK) { + return NGX_CONF_ERROR; + } + /* + ** XXX: we need to check if file exists here + ** b/c modsecurity standalone will segfault with non-existent file + */ + ucf->config_path = (char *) ngx_pcalloc(cf->pool, value[1].len + 1); + if (ucf->config_path == NULL) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "ModSecurity: config path memory allocation error"); + return NGX_CONF_ERROR; + } + ngx_memcpy(ucf->config_path, value[1].data, value[1].len); + + return NGX_CONF_OK; +} + +static char * +ngx_http_modsecurity_add_handler(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_http_core_loc_conf_t *clcf; + ngx_str_t *value; + ngx_http_modsecurity_loc_conf_t *mscf = conf; + + value = cf->args->elts; + + if (value[1].len == 0) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "empty value in \"%V\" directive", + &cmd->name); + + return NGX_CONF_ERROR; + } + + clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); + clcf->handler = ngx_http_modsecurity_handler; + + if (ngx_strcmp(value[1].data, "off") == 0) { + mscf->enable = 0; + return NGX_CONF_OK; + } + if (ngx_strcmp(value[1].data, "on") == 0) { + mscf->enable = 1; + return NGX_CONF_OK; + } + + return NGX_CONF_OK; +} + +static char * +ngx_http_modsecurity_pass(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_http_modsecurity_loc_conf_t *mscf = conf; + ngx_str_t *value; + ngx_http_compile_complex_value_t ccv; + + if ((mscf->url.len != 0) || (mscf->url_cv != NULL)) { + return "is duplicate"; + } + + value = cf->args->elts; + + if (value[1].len == 0) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "empty value in \"%V\" directive", + &cmd->name); + + return NGX_CONF_ERROR; + } + + if (ngx_http_script_variables_count(&value[1])) { + /* complex value */ + mscf->url_cv = ngx_palloc(cf->pool, sizeof(ngx_http_complex_value_t)); + if (mscf->url_cv == NULL) { + return NGX_CONF_ERROR; + } + + ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); + + ccv.cf = cf; + ccv.value = &value[1]; + ccv.complex_value = mscf->url_cv; + + if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { + return NGX_CONF_ERROR; + } + } else { + /* simple value */ + mscf->url = value[1]; + } + + return NGX_CONF_OK; +}