/** * Function to apply regexp-based edit to a header in a list. * * @param[in] list the list to add to * @param[in] entry the name of the entry to edit * @param[in] val string value of the regexp, if rx is null * @param[in] tx The ironbee tx rec * @param[in] rx The compiled regex operation, or NULL to use val */ static ib_status_t list_edit(ngx_list_t *list, const char *entry, const char *val, ib_tx_t *tx, ib_rx_t *rx) { ngx_list_part_t *part; ngx_table_elt_t *elt; unsigned int i; char *repl; char *oldval; ngxib_req_ctx *ctx = tx->sctx; ngx_log_t *prev_log = ngxib_log(ctx->r->connection->log); ngx_regex_malloc_init(ctx->r->pool); /* Check we were passed something valid */ if (rx == NULL) { if (rx = ib_rx_compile(tx->mp, val), rx == NULL) { ngx_log_error(NGX_LOG_ERR, ctx->r->connection->log, 0, "Failed to compile %s as regexp", val); cleanup_return(prev_log) IB_EINVAL; } } for (part = &list->part; part; part = part->next) { elt = part->elts; for (i = 0; i < part->nelts; ++i) { if (elt[i].key.len == strlen(entry) && !strncasecmp((const char*)elt[i].key.data, entry, elt[i].key.len)) { /* we need a null-terminated string for ib_rx_exec */ oldval = strndup((const char*)elt[i].value.data, elt[i].value.len); ib_rx_exec(tx->mp, rx, oldval, &repl, NULL); free(oldval); if (repl != NULL) { elt[i].value.data = (unsigned char*)repl; elt[i].value.len = strlen(repl); } } } if (part == list->last) break; } cleanup_return(prev_log) IB_OK; }
static ngx_int_t ngx_regex_module_init(ngx_cycle_t *cycle) { int opt; const char *errstr; ngx_uint_t i; ngx_list_part_t *part; ngx_regex_elt_t *elts; opt = 0; #if (NGX_HAVE_PCRE_JIT) { ngx_regex_conf_t *rcf; ngx_pool_cleanup_t *cln; rcf = (ngx_regex_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_regex_module); if (rcf->pcre_jit) { opt = PCRE_STUDY_JIT_COMPILE; /* * The PCRE JIT compiler uses mmap for its executable codes, so we * have to explicitly call the pcre_free_study() function to free * this memory. */ cln = ngx_pool_cleanup_add(cycle->pool, 0); if (cln == NULL) { return NGX_ERROR; } cln->handler = ngx_pcre_free_studies; cln->data = ngx_pcre_studies; } } #endif ngx_regex_malloc_init(cycle->pool); part = &ngx_pcre_studies->part; elts = part->elts; for (i = 0 ; /* void */ ; i++) { if (i >= part->nelts) { if (part->next == NULL) { break; } part = part->next; elts = part->elts; i = 0; } elts[i].regex->extra = pcre_study(elts[i].regex->code, opt, &errstr); if (errstr != NULL) { ngx_log_error(NGX_LOG_ALERT, cycle->log, 0, "pcre_study() failed: %s in \"%s\"", errstr, elts[i].name); } #if (NGX_HAVE_PCRE_JIT) if (opt & PCRE_STUDY_JIT_COMPILE) { int jit, n; jit = 0; n = pcre_fullinfo(elts[i].regex->code, elts[i].regex->extra, PCRE_INFO_JIT, &jit); if (n != 0 || jit != 1) { ngx_log_error(NGX_LOG_INFO, cycle->log, 0, "JIT compiler does not support pattern: \"%s\"", elts[i].name); } } #endif } ngx_regex_malloc_done(); ngx_pcre_studies = NULL; return NGX_OK; }
ngx_int_t ngx_regex_compile(ngx_regex_compile_t *rc) { int n, erroff; char *p; pcre *re; const char *errstr; ngx_regex_elt_t *elt; ngx_regex_malloc_init(rc->pool); re = pcre_compile((const char *) rc->pattern.data, (int) rc->options, &errstr, &erroff, NULL); /* ensure that there is no current pool */ ngx_regex_malloc_done(); if (re == NULL) { if ((size_t) erroff == rc->pattern.len) { rc->err.len = ngx_snprintf(rc->err.data, rc->err.len, "pcre_compile() failed: %s in \"%V\"", errstr, &rc->pattern) - rc->err.data; } else { rc->err.len = ngx_snprintf(rc->err.data, rc->err.len, "pcre_compile() failed: %s in \"%V\" at \"%s\"", errstr, &rc->pattern, rc->pattern.data + erroff) - rc->err.data; } return NGX_ERROR; } rc->regex = ngx_pcalloc(rc->pool, sizeof(ngx_regex_t)); if (rc->regex == NULL) { goto nomem; } rc->regex->code = re; /* do not study at runtime */ if (ngx_pcre_studies != NULL) { elt = ngx_list_push(ngx_pcre_studies); if (elt == NULL) { goto nomem; } elt->regex = rc->regex; elt->name = rc->pattern.data; } n = pcre_fullinfo(re, NULL, PCRE_INFO_CAPTURECOUNT, &rc->captures); if (n < 0) { p = "pcre_fullinfo(\"%V\", PCRE_INFO_CAPTURECOUNT) failed: %d"; goto failed; } if (rc->captures == 0) { return NGX_OK; } n = pcre_fullinfo(re, NULL, PCRE_INFO_NAMECOUNT, &rc->named_captures); if (n < 0) { p = "pcre_fullinfo(\"%V\", PCRE_INFO_NAMECOUNT) failed: %d"; goto failed; } if (rc->named_captures == 0) { return NGX_OK; } n = pcre_fullinfo(re, NULL, PCRE_INFO_NAMEENTRYSIZE, &rc->name_size); if (n < 0) { p = "pcre_fullinfo(\"%V\", PCRE_INFO_NAMEENTRYSIZE) failed: %d"; goto failed; } n = pcre_fullinfo(re, NULL, PCRE_INFO_NAMETABLE, &rc->names); if (n < 0) { p = "pcre_fullinfo(\"%V\", PCRE_INFO_NAMETABLE) failed: %d"; goto failed; } return NGX_OK; failed: rc->err.len = ngx_snprintf(rc->err.data, rc->err.len, p, &rc->pattern, n) - rc->err.data; return NGX_ERROR; nomem: rc->err.len = ngx_snprintf(rc->err.data, rc->err.len, "regex \"%V\" compilation failed: no memory", &rc->pattern) - rc->err.data; return NGX_ERROR; }
ngx_int_t ngx_regex_compile(ngx_regex_compile_t *rc) { int n, erroff; char *p; const char *errstr; ngx_regex_t *re; ngx_regex_malloc_init(rc->pool); re = pcre_compile((const char *) rc->pattern.data, (int) rc->options, &errstr, &erroff, NULL); /* ensure that there is no current pool */ ngx_regex_malloc_done(); if (re == NULL) { if ((size_t) erroff == rc->pattern.len) { rc->err.len = ngx_snprintf(rc->err.data, rc->err.len, "pcre_compile() failed: %s in \"%V\"", errstr, &rc->pattern) - rc->err.data; } else { rc->err.len = ngx_snprintf(rc->err.data, rc->err.len, "pcre_compile() failed: %s in \"%V\" at \"%s\"", errstr, &rc->pattern, rc->pattern.data + erroff) - rc->err.data; } return NGX_ERROR; } rc->regex = re; n = pcre_fullinfo(re, NULL, PCRE_INFO_CAPTURECOUNT, &rc->captures); if (n < 0) { p = "pcre_fullinfo(\"%V\", PCRE_INFO_CAPTURECOUNT) failed: %d"; goto failed; } if (rc->captures == 0) { return NGX_OK; } n = pcre_fullinfo(re, NULL, PCRE_INFO_NAMECOUNT, &rc->named_captures); if (n < 0) { p = "pcre_fullinfo(\"%V\", PCRE_INFO_NAMECOUNT) failed: %d"; goto failed; } if (rc->named_captures == 0) { return NGX_OK; } n = pcre_fullinfo(re, NULL, PCRE_INFO_NAMEENTRYSIZE, &rc->name_size); if (n < 0) { p = "pcre_fullinfo(\"%V\", PCRE_INFO_NAMEENTRYSIZE) failed: %d"; goto failed; } n = pcre_fullinfo(re, NULL, PCRE_INFO_NAMETABLE, &rc->names); if (n < 0) { p = "pcre_fullinfo(\"%V\", PCRE_INFO_NAMETABLE) failed: %d"; goto failed; } return NGX_OK; failed: rc->err.len = ngx_snprintf(rc->err.data, rc->err.len, p, &rc->pattern, n) - rc->err.data; return NGX_OK; }
/** * nginx handler to feed request body (if any) to IronBee * * @param[in] r the nginx request object * @return NGX_DECLINED for normal operation * @return NGX_DONE if body is not yet available (processing will resume * on new data) * @return Error status if set by IronBee on sight of request data. */ ngx_int_t ngxib_handler(ngx_http_request_t *r) { ngx_chain_t *link; ngxib_req_ctx *ctx; ngx_int_t rv = NGX_DECLINED; ngx_http_request_body_t *rb; /* Don't process internal requests */ if (r->internal) return rv; ctx = ngx_http_get_module_ctx(r, ngx_ironbee_module); if (ctx->body_done) return rv; /* We already completed handling of no-body requests * when we looked at headers */ if (!ngxib_has_request_body(r, ctx)) return rv; ngx_regex_malloc_init(r->pool); /* We can now read the body. * This may come asynchronously in many chunks, so we need * to check for AGAIN and return DONE if waiting. * We pass it a handler to go round again while waiting. * * TODO: figure out how to pass data to ironbee asynchronously */ rv = ngx_http_read_client_request_body(r, ngxib_post_handler); if (rv == NGX_AGAIN) { ctx->body_wait = 1; cleanup_return NGX_DONE; } /* We now have the request body. Feed it to ironbee */ rb = r->request_body; if (!rb) { ib_log_error_tx(ctx->tx, "Failed to read request body."); cleanup_return NGX_HTTP_INTERNAL_SERVER_ERROR; } if (!rb->bufs) { /* I think this shouldn't happen */ /* But rethink if this turns up in logs when all is fine */ ib_log_error_tx(ctx->tx, "Probable error reading request body."); } if (rb->temp_file && (rb->temp_file->file.fd != NGX_INVALID_FILE)) { /* Reader has put request body in temp file */ off_t count = 0; u_char buf[BUFSIZE]; size_t buf_len; ib_log_debug_tx(ctx->tx, "Reading request body in temp file."); while (buf_len = ngx_read_file(&rb->temp_file->file, buf, BUFSIZE, count), buf_len > 0) { ib_log_debug_tx(ctx->tx, "Feeding %zd bytes request data to ironbee.", buf_len); ib_state_notify_request_body_data(ctx->tx->ib, ctx->tx, (const char*)buf, buf_len); count += buf_len; } if ((int)buf_len == NGX_ERROR) { ib_log_error_tx(ctx->tx, "Failed to read request body in temp file."); } } for (link = rb->bufs; link != NULL; link = link->next) { size_t len = (link->buf->last - link->buf->pos); ib_log_debug_tx(ctx->tx, "Feeding %zd bytes request data to ironbee.", len); if (len > 0) { ib_state_notify_request_body_data(ctx->tx->ib, ctx->tx, (const char*)link->buf->pos, len); } } ctx->body_done = 1; ib_state_notify_request_finished(ctx->tx->ib, ctx->tx); /* If IronBee signaled an error, we can return it */ if (STATUS_IS_ERROR(ctx->status)) { rv = ctx->status; ctx->internal_errordoc = 1; ib_log_error_tx(ctx->tx, "IronBee set %d reading request body.", (int)rv); } cleanup_return rv; }
//PCRE使用参考http://blog.chinaunix.net/uid-26575352-id-3517146.html http://blog.csdn.net/kofiory/article/details/5829697 ngx_int_t ngx_regex_compile(ngx_regex_compile_t *rc) { int n, erroff; char *p; pcre *re; const char *errstr; ngx_regex_elt_t *elt; ngx_regex_malloc_init(rc->pool); //正则表达式在使用之前要经过编译。编译的目的是将正则表达式的pattern转换成PCRE引擎能够识别的结构(struct real_pcre)。 re = pcre_compile((const char *) rc->pattern.data, (int) rc->options, &errstr, &erroff, NULL); /* ensure that there is no current pool */ ngx_regex_malloc_done(); if (re == NULL) { if ((size_t) erroff == rc->pattern.len) { rc->err.len = ngx_snprintf(rc->err.data, rc->err.len, "pcre_compile() failed: %s in \"%V\"", errstr, &rc->pattern) - rc->err.data; } else { rc->err.len = ngx_snprintf(rc->err.data, rc->err.len, "pcre_compile() failed: %s in \"%V\" at \"%s\"", errstr, &rc->pattern, rc->pattern.data + erroff) - rc->err.data; } return NGX_ERROR; } rc->regex = ngx_pcalloc(rc->pool, sizeof(ngx_regex_t)); if (rc->regex == NULL) { goto nomem; } rc->regex->code = re; /* do not study at runtime */ if (ngx_pcre_studies != NULL) { elt = ngx_list_push(ngx_pcre_studies); if (elt == NULL) { goto nomem; } elt->regex = rc->regex; elt->name = rc->pattern.data; } //PCRE_INFO_CAPTURECOUNT: 得到的是所有子模式的个数,包含命名子模式和非命名子模式 n = pcre_fullinfo(re, NULL, PCRE_INFO_CAPTURECOUNT, &rc->captures); if (n < 0) { p = "pcre_fullinfo(\"%V\", PCRE_INFO_CAPTURECOUNT) failed: %d"; goto failed; } if (rc->captures == 0) { return NGX_OK; } /* See if there are any named substrings, and if so, show them by name. First we have to extract the count of named parentheses from the pattern. */ n = pcre_fullinfo(re, NULL, PCRE_INFO_NAMECOUNT, &rc->named_captures); if (n < 0) { p = "pcre_fullinfo(\"%V\", PCRE_INFO_NAMECOUNT) failed: %d"; goto failed; } if (rc->named_captures == 0) { return NGX_OK; } /* Before we can access the substrings, we must extract the table for translating names to numbers, and the size of each entry in the table. */ n = pcre_fullinfo(re, NULL, PCRE_INFO_NAMEENTRYSIZE, &rc->name_size); /* where to put the answer */ if (n < 0) { p = "pcre_fullinfo(\"%V\", PCRE_INFO_NAMEENTRYSIZE) failed: %d"; goto failed; } n = pcre_fullinfo(re, NULL, PCRE_INFO_NAMETABLE, &rc->names); /* where to put the answer */ if (n < 0) { p = "pcre_fullinfo(\"%V\", PCRE_INFO_NAMETABLE) failed: %d"; goto failed; } return NGX_OK; failed: rc->err.len = ngx_snprintf(rc->err.data, rc->err.len, p, &rc->pattern, n) - rc->err.data; return NGX_ERROR; nomem: rc->err.len = ngx_snprintf(rc->err.data, rc->err.len, "regex \"%V\" compilation failed: no memory", &rc->pattern) - rc->err.data; return NGX_ERROR; }
static ngx_int_t ngx_regex_module_init(ngx_cycle_t *cycle) { int opt; const char *errstr; ngx_uint_t i; ngx_list_part_t *part; ngx_regex_elt_t *elts; opt = 0; #if (NGX_HAVE_PCRE_JIT) { ngx_regex_conf_t *rcf; ngx_pool_cleanup_t *cln; rcf = (ngx_regex_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_regex_module); if (rcf->pcre_jit) { opt = PCRE_STUDY_JIT_COMPILE; /* * The PCRE JIT compiler uses mmap for its executable codes, so we * have to explicitly call the pcre_free_study() function to free * this memory. */ cln = ngx_pool_cleanup_add(cycle->pool, 0); if (cln == NULL) { return NGX_ERROR; } cln->handler = ngx_pcre_free_studies; cln->data = ngx_pcre_studies; } } #endif ngx_regex_malloc_init(cycle->pool); part = &ngx_pcre_studies->part; elts = part->elts; for (i = 0 ; /* void */ ; i++) { if (i >= part->nelts) { if (part->next == NULL) { break; } part = part->next; elts = part->elts; i = 0; } /* 对编译后的正则表达式结构(struct real_pcre)进行分析和学习,学习的结果是一个数据结构(struct pcre_extra),这个数据结构连同编译 后的规则(struct real_pcre)可以一起送给pcre_exec单元进行匹配. pcre_study()的引入主要是为了加速正则表达式匹配的速度.(为什么学习后就能加速呢?)这个还是比较有用的,可以将正则表达式编译, 学习后保存到一个文件或内存中,这样进行匹配的时候效率比较搞.snort中就是这样做的. */ elts[i].regex->extra = pcre_study(elts[i].regex->code, opt, &errstr); if (errstr != NULL) { ngx_log_error(NGX_LOG_ALERT, cycle->log, 0, "pcre_study() failed: %s in \"%s\"", errstr, elts[i].name); } #if (NGX_HAVE_PCRE_JIT) if (opt & PCRE_STUDY_JIT_COMPILE) { int jit, n; jit = 0; n = pcre_fullinfo(elts[i].regex->code, elts[i].regex->extra, PCRE_INFO_JIT, &jit); if (n != 0 || jit != 1) { ngx_log_error(NGX_LOG_INFO, cycle->log, 0, "JIT compiler does not support pattern: \"%s\"", elts[i].name); } } #endif } ngx_regex_malloc_done(); ngx_pcre_studies = NULL; return NGX_OK; }
ib_conn_t *ngxib_conn_get(ngxib_req_ctx *rctx) { ngx_pool_cleanup_t *cln; ib_status_t rc; ib_engine_t *ib; /* Suggested by Maxim Dounin on dev list: * Look through pool cleanups for our conn * No race condition because no threads! */ for (cln = rctx->r->connection->pool->cleanup; cln != NULL; cln = cln->next) { if (cln->handler == conn_end) { /* Our connection is already initialized and it's here */ rctx->conn = cln->data; return rctx->conn->iconn; } } /* This connection is new, so initialize our conn struct * and notify IronBee. * * No threads, so no race condition here */ /* Acquire an IronBee engine */ /* n.b. pool cleanup for this and the ib_conn is conn_end, added below */ rc = ngxib_acquire_engine(&ib, rctx->r->connection->log); if (rc != IB_OK) { cleanup_return NULL; } ngx_regex_malloc_init(rctx->r->connection->pool); rctx->conn = ngx_palloc(rctx->r->connection->pool, sizeof(ngxib_conn_t)); if (rctx->conn == NULL) { ngxib_release_engine(ib, rctx->r->connection->log); cleanup_return NULL; } rctx->conn->engine = ib; rctx->conn->log = rctx->r->connection->log; rc = ib_conn_create(rctx->conn->engine, &rctx->conn->iconn, rctx->r->connection); if (rc != IB_OK) { ngxib_release_engine(ib, rctx->r->connection->log); cleanup_return NULL; } /* Initialize the connection */ rc = conn_init(rctx->conn->iconn); if (rc != IB_OK) { ngxib_release_engine(ib, rctx->r->connection->log); cleanup_return NULL; } ib_state_notify_conn_opened(rctx->conn->engine, rctx->conn->iconn); cln = ngx_pool_cleanup_add(rctx->r->connection->pool, 0); if (cln != NULL) { cln->handler = conn_end; cln->data = rctx->conn; } cleanup_return rctx->conn->iconn; }
/** * Ironbee initialisation function. Sets up engine and logging, * and reads Ironbee config. * * @param[in] cf Configuration rec * @return NGX_OK or error */ static ngx_int_t ironbee_init(ngx_conf_t *cf) { ngx_log_t *prev_log; ib_context_t *ctx; ib_cfgparser_t *cp; ironbee_proc_t *proc; ib_status_t rc, rc1; prev_log = ngxib_log(cf->log); ngx_regex_malloc_init(cf->pool); ngx_log_error(NGX_LOG_NOTICE, cf->log, 0, "ironbee_init %d", getpid()); proc = ngx_http_conf_get_module_main_conf(cf, ngx_ironbee_module); if (proc->loglevel == NGX_CONF_UNSET_UINT) proc->loglevel = 4; /* default */ rc = ib_initialize(); if (rc != IB_OK) cleanup_return(prev_log) IB2NG(rc); ib_util_log_level(proc->loglevel); rc = ib_engine_create(&ironbee, ngxib_server()); if (rc != IB_OK) cleanup_return(prev_log) IB2NG(rc); if (proc->use_ngxib_logger) ib_log_set_logger_fn(ironbee, ngxib_logger, NULL); /* Using default log level function. */ rc = ib_engine_init(ironbee); if (rc != IB_OK) cleanup_return(prev_log) IB2NG(rc); /* TODO: TS creates logfile at this point */ ib_hook_conn_register(ironbee, conn_opened_event, ngxib_conn_init, NULL); rc = ib_cfgparser_create(&cp, ironbee); assert((cp != NULL) || (rc != IB_OK)); if (rc != IB_OK) cleanup_return(prev_log) IB2NG(rc); rc = ib_engine_config_started(ironbee, cp); if (rc != IB_OK) cleanup_return(prev_log) IB2NG(rc); /* Get the main context, set some defaults */ ctx = ib_context_main(ironbee); ib_context_set_num(ctx, "logger.log_level", proc->loglevel); /* FIXME - use the temp pool operation for this */ char *buf = strndup((char*)proc->config_file.data, proc->config_file.len); rc = ib_cfgparser_parse(cp, buf); free(buf); rc1 = ib_engine_config_finished(ironbee); ib_cfgparser_destroy(cp); cleanup_return(prev_log) rc == IB_OK ? rc1 == IB_OK ? NGX_OK : IB2NG(rc1) : IB2NG(rc); }
/** * nginx post-read-request handler to feed request line and headers to Ironbee. * * @param[in] r The nginx request object. * @return Declined (ignoreme) or error status. */ static ngx_int_t ironbee_post_read_request(ngx_http_request_t *r) { ngx_log_t *prev_log; ngxib_req_ctx *ctx; ib_conn_t *iconn; ib_parsed_req_line_t *rline; ib_parsed_header_wrapper_t *ibhdrs; ib_status_t rc; ngx_list_part_t *part; ngx_table_elt_t *hdr; unsigned int i; /* Don't process internal requests */ if (r->internal) return NGX_DECLINED; prev_log = ngxib_log(r->connection->log); ngx_regex_malloc_init(r->pool); ctx = ngx_pcalloc(r->pool, sizeof(ngxib_req_ctx)); ctx->r = r; ngx_http_set_ctx(r, ctx, ngx_ironbee_module); iconn = ngxib_conn_get(ctx, ironbee); ib_tx_create(&ctx->tx, iconn, ctx); /* Notify Ironbee of request line and headers */ rc = ib_parsed_req_line_create(ctx->tx, &rline, (const char*)r->request_line.data, r->request_line.len, (const char*)r->method_name.data, r->method_name.len, (const char*)r->unparsed_uri.data, r->unparsed_uri.len, (const char*)r->http_protocol.data, r->http_protocol.len); if (rc != IB_OK) cleanup_return(prev_log) NGX_ERROR; ib_state_notify_request_started(ironbee, ctx->tx, rline); rc = ib_parsed_name_value_pair_list_wrapper_create(&ibhdrs, ctx->tx); if (rc != IB_OK) cleanup_return(prev_log) NGX_ERROR; for (part = &r->headers_in.headers.part; part != NULL; part = part->next) { hdr = part->elts; for (i = 0; i < part->nelts; ++i) { ib_parsed_name_value_pair_list_add(ibhdrs, (const char*)hdr->key.data, hdr->key.len, (const char*)hdr->value.data, hdr->value.len); ++hdr; } } rc = ib_state_notify_request_header_data(ironbee, ctx->tx, ibhdrs); if (rc != IB_OK) cleanup_return(prev_log) NGX_ERROR; rc = ib_state_notify_request_header_finished(ironbee, ctx->tx); if (rc != IB_OK) cleanup_return(prev_log) NGX_ERROR; if (!ngxib_has_request_body(r, ctx)) { rc = ib_state_notify_request_finished(ironbee, ctx->tx); if (rc != IB_OK) cleanup_return(prev_log) NGX_ERROR; } ctx->hdrs_in = 1; if (STATUS_IS_ERROR(ctx->status)) { ctx->internal_errordoc = 1; cleanup_return(prev_log) ctx->status; } cleanup_return(prev_log) NGX_DECLINED; }
/** * A header filter to intercept response line and headers and feed to Ironbee. * * @param[in] r The nginx request object. * @return status propagated from next filter, or Error */ static ngx_int_t ironbee_headers_out(ngx_http_request_t *r) { ngx_log_t *prev_log; ngxib_req_ctx *ctx; ib_parsed_resp_line_t *rline; ib_parsed_header_wrapper_t *ibhdrs; ib_status_t rc; ngx_list_part_t *part; ngx_table_elt_t *hdr; unsigned int i; char proto[12]; char *status; const char *reason; int status_len, reason_len; /* FIXME: needs more logic here to catch error pages */ if (r->internal) return ngx_http_next_header_filter(r); ctx = ngx_http_get_module_ctx(r, ngx_ironbee_module); assert((ctx != NULL) && (ctx->tx != NULL)); prev_log = ngxib_log(r->connection->log); ngx_regex_malloc_init(r->pool); /* Notify Ironbee of request line and headers */ sprintf(proto, "HTTP/%d.%d", r->http_major, r->http_minor); if (r->headers_out.status_line.len) { status = (char*)r->headers_out.status_line.data; status_len = strcspn(status, " \t"); for (reason = status+status_len; isspace(*reason); ++reason); reason_len = r->headers_out.status_line.len - (reason-status); } else if (r->headers_out.status >= 100 && r->headers_out.status < 600) { status = ngx_palloc(r->pool, 4); /* cast to int, because ngx_int_t requires different format args * on different platforms. We're already limited to 3-digit numbers. */ sprintf(status, "%d", (int)r->headers_out.status); status_len = 3; reason = ""; reason_len = 0; } else { ib_log_error_tx(ctx->tx, "Ironbee: bogus response status %d", (int)r->headers_out.status); cleanup_return(prev_log) NGX_ERROR; } rc = ib_parsed_resp_line_create(ctx->tx, &rline, NULL, 0, proto, strlen(proto), status, status_len, reason, reason_len); if (rc != IB_OK) cleanup_return(prev_log) NGX_ERROR; ib_state_notify_response_started(ironbee, ctx->tx, rline); rc = ib_parsed_name_value_pair_list_wrapper_create(&ibhdrs, ctx->tx); if (rc != IB_OK) cleanup_return(prev_log) NGX_ERROR; for (part = &r->headers_out.headers.part; part != NULL; part = part->next) { hdr = part->elts; for (i = 0; i < part->nelts; ++i) { ib_parsed_name_value_pair_list_add(ibhdrs, (const char*)hdr->key.data, hdr->key.len, (const char*)hdr->value.data, hdr->value.len); ++hdr; } } /* Ironbee currently crashes if called here with no headers, * even perfectly correctly on a 204/304 response. */ if (ibhdrs->size > 0) { rc = ib_state_notify_response_header_data(ironbee, ctx->tx, ibhdrs); if (rc != IB_OK) cleanup_return(prev_log) NGX_ERROR; } rc = ib_state_notify_response_header_finished(ironbee, ctx->tx); if (rc != IB_OK) cleanup_return(prev_log) NGX_ERROR; ctx->hdrs_out = 1; cleanup_return(prev_log) ngx_http_next_header_filter(r); }
/** * A body filter to intercept response body and feed it to Ironbee, * and to buffer the data if required by Ironbee configuration. * * @param[in] r The nginx request object. * @param[in] in The data to filter. * @return status propagated from next filter, or OK/Error */ static ngx_int_t ironbee_body_out(ngx_http_request_t *r, ngx_chain_t *in) { ngx_log_t *prev_log; ngxib_req_ctx *ctx; ngx_chain_t *link; ib_status_t rc; ib_num_t num; ngx_int_t rv = NGX_OK; ib_txdata_t itxdata; if (r->internal) return ngx_http_next_body_filter(r, in); prev_log = ngxib_log(r->connection->log); ctx = ngx_http_get_module_ctx(r, ngx_ironbee_module); assert((ctx != NULL) && (ctx->tx != NULL)); ib_log_debug_tx(ctx->tx, "ironbee_body_out"); if (in == NULL) { /* FIXME: could this happen in circumstances when we should * notify Ironbee of end-of-response ? */ ib_log_debug_tx(ctx->tx, "ironbee_body_out: input was null"); cleanup_return(prev_log) ngx_http_next_body_filter(r, in); } ctx = ngx_http_get_module_ctx(r, ngx_ironbee_module); if (ctx->output_filter_done) { ib_log_debug_tx(ctx->tx, "ironbee_body_out: already done"); cleanup_return(prev_log) ngx_http_next_body_filter(r, in); } if (!ctx->output_filter_init) { ctx->output_filter_init = 1; if (ctx->internal_errordoc) { /* If it's our own errordoc, pass it straight through */ /* Should we log anything here? The error will already * have been logged. */ ctx->output_buffering = IOBUF_NOBUF; ctx->response_buf = NULL; ib_log_debug_tx(ctx->tx, "ironbee_body_out: in internal errordoc"); } else { /* Determine whether we're configured to buffer */ rc = ib_context_get(ctx->tx->ctx, "buffer_res", ib_ftype_num_out(&num), NULL); ib_log_debug_tx(ctx->tx, "ironbee_body_out: buffer_res is %d", (int)num); if (rc != IB_OK) ib_log_error_tx(ctx->tx, "Can't determine output buffer configuration!"); if (num == 0) { ib_log_debug_tx(ctx->tx, "ironbee_body_out: NOBUF"); ctx->output_buffering = IOBUF_NOBUF; ctx->response_buf = NULL; } else { /* If we're buffering, initialise the buffer */ ib_log_debug_tx(ctx->tx, "ironbee_body_out: BUFFER"); ctx->output_buffering = IOBUF_BUFFER; } } } ngx_regex_malloc_init(r->pool); for (link = in; link != NULL; link = link->next) { /* Feed the data to ironbee */ itxdata.data = link->buf->pos; itxdata.dlen = link->buf->last - link->buf->pos; ib_log_debug_tx(ctx->tx, "ironbee_body_out: %d bytes", (int)itxdata.dlen); if (itxdata.dlen > 0) { ib_state_notify_response_body_data(ironbee, ctx->tx, &itxdata); } /* If Ironbee just signalled an error, switch to discard data mode, * and dump anything we already have buffered, */ if ( (STATUS_IS_ERROR(ctx->status)) && !ctx->internal_errordoc && (ctx->output_buffering != IOBUF_DISCARD) ) { ib_log_debug_tx(ctx->tx, "ironbee_body_out: error %d", ctx->status); free_chain(r->pool, ctx->response_buf); ctx->response_buf = NULL; ctx->output_buffering = IOBUF_DISCARD; } else if (ctx->output_buffering == IOBUF_BUFFER) { /* Copy any data to our buffer */ if (ctx->response_buf == NULL) { ctx->response_buf = ngx_pcalloc(r->pool, sizeof(ngx_chain_t)); ctx->response_ptr = ctx->response_buf; } else { ctx->response_ptr->next = ngx_pcalloc(r->pool, sizeof(ngx_chain_t)); ctx->response_ptr = ctx->response_ptr->next; } /* Not sure if any data types need setaside, but let's be safe */ #if NO_COPY_REQUIRED ctx->response_ptr->buf = link->buf; #else if (itxdata.dlen > 0) { ctx->response_ptr->buf = ngx_create_temp_buf(r->pool, itxdata.dlen); memcpy(ctx->response_ptr->buf->pos, link->buf->pos, itxdata.dlen); ctx->response_ptr->buf->last += itxdata.dlen; } else { ctx->response_ptr->buf = ngx_palloc(r->pool, sizeof(ngx_buf_t)); memcpy(ctx->response_ptr->buf, link->buf, sizeof(ngx_buf_t)); } #endif } if (link->buf->last_buf) { ib_log_debug_tx(ctx->tx, "ironbee_body_out: last_buf"); ctx->output_filter_done = 1; } } if (ctx->output_buffering == IOBUF_NOBUF) { /* Normal operation - pass it down the chain */ ib_log_debug_tx(ctx->tx, "ironbee_body_out: passing on"); ctx->start_response = 1; rv = ngx_http_next_body_filter(r, in); } else if (ctx->output_buffering == IOBUF_BUFFER) { ib_log_debug_tx(ctx->tx, "ironbee_body_out: buffering"); if (ctx->output_filter_done) { /* We can pass on the buffered data all at once */ ib_log_debug_tx(ctx->tx, "ironbee_body_out: passing buffer"); ctx->start_response = 1; rv = ngx_http_next_body_filter(r, ctx->response_buf); } } else if (ctx->output_buffering == IOBUF_DISCARD) { ib_log_debug_tx(ctx->tx, "ironbee_body_out: discarding"); if (ctx->output_filter_done) { /* Pass a last bucket with no data */ //ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, // "ironbee_body_out: passing NULL last-buffer"); //ctx->response_buf = ngx_pcalloc(r->pool, sizeof(ngx_chain_t)); //ctx->response_buf->buf = ngx_calloc_buf(r->pool); //ctx->response_buf->buf->last_buf = ctx->response_buf->buf->last_in_chain = 1; //rv = ngx_http_next_body_filter(r, ctx->response_buf); /* FIXME: Is setting rv enough to serve error page */ rv = ctx->status; } } if (ctx->output_filter_done) { ib_log_debug_tx(ctx->tx, "ironbee_body_out: notify_postprocess"); rc = ib_state_notify_postprocess(ironbee, ctx->tx); if ((rv == NGX_OK) && (rc != IB_OK)) { rv = NGX_HTTP_INTERNAL_SERVER_ERROR; } rc = ib_state_notify_logging(ironbee, ctx->tx); if ((rv == NGX_OK) && (rc != IB_OK)) { rv = NGX_HTTP_INTERNAL_SERVER_ERROR; } } cleanup_return(prev_log) rv; }
/** * IronBee initialization function. Sets up engine and logging, * and reads IronBee config. * * @param[in] cf Configuration rec * @return NGX_OK or error */ static ngx_int_t ironbee_init(ngx_conf_t *cf) { ironbee_proc_t *proc; ib_status_t rc; module_data_t *mod_data = &module_data; char *buf; /* We still use the global-log hack to initialise */ ngx_regex_malloc_init(cf->pool); ngx_log_error(NGX_LOG_NOTICE, cf->log, 0, "ironbee_init %d", getpid()); proc = ngx_http_conf_get_module_main_conf(cf, ngx_ironbee_module); if (proc->log_level == NGX_CONF_UNSET_UINT) { proc->log_level = IB_LOG_NOTICE; } if (proc->max_engines == NGX_CONF_UNSET_UINT) { proc->max_engines = IB_MANAGER_DEFAULT_MAX_ENGINES; } if (proc->use_ngxib_logger == NGX_CONF_UNSET) { proc->use_ngxib_logger =1; } /* initialise fields in mod_data */ mod_data->ib_log_active = proc->use_ngxib_logger; mod_data->log = cf->log; mod_data->log_level = proc->log_level; rc = ib_initialize(); if (rc != IB_OK) { cleanup_return IB2NG(rc); } /* Create the IronBee engine manager */ rc = ib_manager_create(&(mod_data->manager), /* Engine Manager */ ngxib_server(), /* Server object */ proc->max_engines); /* Max engines */ if (rc != IB_OK) { cleanup_return IB2NG(rc); } rc = ib_manager_register_module_fn( mod_data->manager, ngxib_module, mod_data); if (rc != IB_OK) { cleanup_return IB2NG(rc); } /* Null manager here would be a bug, as per RNS-CR-143 comments */ assert(mod_data->manager != NULL); /* FIXME - use the temp pool operation for this */ buf = strndup((char*)proc->config_file.data, proc->config_file.len); /* Create the initial engine */ rc = ib_manager_engine_create(mod_data->manager, buf); if (rc != IB_OK) { free(buf); cleanup_return IB2NG(rc); } free(buf); cleanup_return rc == IB_OK ? NGX_OK : IB2NG(rc); }
/** * nginx handler to feed request body (if any) to Ironbee * * @param[in] r the nginx request object * @return NGX_DECLINED for normal operation * @return NGX_DONE if body is not yet available (processing will restart * on new data) * @return Error status if set by Ironbee on sight of request data. */ ngx_int_t ngxib_handler(ngx_http_request_t *r) { ngx_log_t *prev_log; ib_txdata_t itxdata; ngx_chain_t *link; ngxib_req_ctx *ctx; ngx_int_t rv = NGX_DECLINED; ngx_http_request_body_t *rb; /* Don't process internal requests */ if (r->internal) return rv; ctx = ngx_http_get_module_ctx(r, ngx_ironbee_module); if (ctx->body_done) return rv; if (!has_request_body(r, ctx)) return rv; prev_log = ngxib_log(r->connection->log); ngx_regex_malloc_init(r->pool); /* We can now read the body. * This may come asynchronously in many chunks, so we need * to check for AGAIN and return DONE if waiting. * We pass it a handler to go round again while waiting. * * TODO: figure out how to pass data to ironbee asynchronously */ rv = ngx_http_read_client_request_body(r, ngxib_post_handler); if (rv == NGX_AGAIN) { ctx->body_wait = 1; cleanup_return(prev_log) NGX_DONE; } /* We now have the request body. Feed it to ironbee */ rb = r->request_body; if (!rb) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "Error reading request body"); cleanup_return(prev_log) NGX_HTTP_INTERNAL_SERVER_ERROR; } if (!rb->bufs) { /* I think this shouldn't happen */ /* But rethink if this turns up in logs when all is fine */ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "Probable error reading request body"); } if (rb->temp_file && (rb->temp_file->file.fd != NGX_INVALID_FILE)) { /* Reader has put request body in temp file */ off_t count = 0; u_char buf[BUFSIZE]; ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "Reading request body in temp file"); while (itxdata.dlen = ngx_read_file(&rb->temp_file->file, buf, BUFSIZE, count), (int)itxdata.dlen > 0) { itxdata.data = buf; ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "Feeding %d bytes request data to ironbee", itxdata.dlen); ib_state_notify_request_body_data(ngxib_engine(), ctx->tx, &itxdata); count += itxdata.dlen; } if ((int)itxdata.dlen == NGX_ERROR) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "Error reading request body in temp file"); } } for (link = rb->bufs; link != NULL; link = link->next) { itxdata.data = link->buf->pos; itxdata.dlen = (link->buf->last - link->buf->pos); ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "Feeding %d bytes request data to ironbee", itxdata.dlen); if (itxdata.dlen > 0) { ib_state_notify_request_body_data(ngxib_engine(), ctx->tx, &itxdata); } } ctx->body_done = 1; ib_state_notify_request_finished(ngxib_engine(), ctx->tx); /* If Ironbee signalled an error, we can return it */ if (STATUS_IS_ERROR(ctx->status)) { rv = ctx->status; ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "Ironbee set %d reading request body", rv); } cleanup_return(prev_log) rv; }