/** * Handle request_header events for user agent extraction. * * Extract the "request_headers" field (a list) from the transactions's * data provider instance, then loop through the list, looking for the * "User-Agent" field. If found, the value is parsed and used to update the * connection object fields. * * @param[in] ib IronBee object * @param[in,out] tx Transaction. * @param[in] event Event type * @param[in] data Callback data (not used) * * @returns Status code */ static ib_status_t modua_user_agent(ib_engine_t *ib, ib_tx_t *tx, ib_state_event_type_t event, void *data) { assert(ib != NULL); assert(tx != NULL); assert(tx->data != NULL); assert(event == request_header_finished_event); ib_field_t *req_agent = NULL; ib_status_t rc = IB_OK; const ib_list_t *bs_list; const ib_bytestr_t *bs; /* Extract the User-Agent header field from the provider instance */ rc = ib_data_get(tx->data, "request_headers:User-Agent", &req_agent); if ( (req_agent == NULL) || (rc != IB_OK) ) { ib_log_debug_tx(tx, "request_header_finished_event: No user agent"); return IB_OK; } if (req_agent->type != IB_FTYPE_LIST) { ib_log_error_tx(tx, "Expected request_headers:User-Agent to " "return list of values."); return IB_EINVAL; } rc = ib_field_value_type(req_agent, ib_ftype_list_out(&bs_list), IB_FTYPE_LIST); if (rc != IB_OK) { ib_log_error_tx(tx, "Cannot retrieve request_headers:User-Agent: %d", rc); return rc; } if (IB_LIST_ELEMENTS(bs_list) == 0) { ib_log_debug_tx(tx, "request_header_finished_event: No user agent"); return IB_OK; } req_agent = (ib_field_t *)IB_LIST_NODE_DATA(IB_LIST_LAST(bs_list)); /* Found it: copy the data into a newly allocated string buffer */ rc = ib_field_value_type(req_agent, ib_ftype_bytestr_out(&bs), IB_FTYPE_BYTESTR); if (rc != IB_OK) { ib_log_error_tx(tx, "Request user agent is not a BYTESTR: %s", ib_status_to_string(rc)); return rc; } /* Finally, split it up & store the components */ rc = modua_agent_fields(ib, tx, bs); return rc; }
/** * Function to reset processing cycle if input data are not yet available. * * @param[in] r the nginx request object */ static void ngxib_post_handler(ngx_http_request_t *r) { ngxib_req_ctx *ctx = ngx_http_get_module_ctx(r, ngx_ironbee_module); if (ctx->body_wait) { ib_log_debug_tx(ctx->tx, "Waiting for more input body data."); ctx->body_wait = 0; ngx_http_core_run_phases(r); } }
static void ironbee_plugin_send_response_hdr(TSCont contp, TSHttpTxn txnp) { assert(contp != NULL); assert(txnp != NULL); tsib_txn_ctx *txndata; txndata = TSContDataGet(contp); if (txndata == NULL) { /* Ironbee is unavailable to help with our response. */ /* This contp is not ours, so we leave it. */ internal_error_response(txnp); TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE); return; } /* If ironbee has sent us into an error response then * we came here in our error path, with nonzero status. */ if (txndata->status != 0) { error_response(txnp, txndata); } /* Feed ironbee the headers if not done already. */ if (!ib_flags_all(txndata->tx->flags, IB_TX_FRES_STARTED)) { if (process_hdr(txndata, txnp, &tsib_direction_client_resp) != HDR_OK) { /* I think this is a shouldn't happen event, and that * if it does we have an ironbee bug or misconfiguration. * Log an error to catch if it happens in practice. */ ib_log_error_tx(txndata->tx, "process_hdr returned error in send_response_hdr event"); } } /* If there is an ironbee-generated response body, notify ironbee. * * NOTE: I do not see anywhere else to put this as the error body is * just a buffer and not delivered via normal IO channels, so * the error body will never get caught by an event. */ if ((txndata->status != 0) && (txndata->err_body != NULL)) { const char *data = txndata->err_body; size_t data_length = txndata->err_body_len; ib_log_debug_tx(txndata->tx, "error_response: calling ib_state_notify_response_body_data() %s:%d", __FILE__, __LINE__); ib_state_notify_response_body_data(txndata->tx->ib, txndata->tx, data, data_length); } TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE); }
/** * Handle transaction context destroy. * * Handles TS_EVENT_HTTP_TXN_CLOSE (transaction close) close event from the * ATS. * * @param[in,out] ctx Transaction context */ static void tsib_txn_ctx_destroy(tsib_txn_ctx *txndata) { if (txndata == NULL) { return; } ib_tx_t *tx = txndata->tx; tsib_ssn_ctx *ssndata = txndata->ssn; assert(tx != NULL); assert(ssndata != NULL); txndata->tx = NULL; ib_log_debug_tx(tx, "TX DESTROY: conn=>%p tx_count=%zd tx=%p id=%s txn_count=%d", tx->conn, tx->conn->tx_count, tx, tx->id, ssndata->txn_count); tx_finish(tx); ib_lock_lock(ssndata->mutex); ib_tx_destroy(tx); txndata->ssn = NULL; /* Decrement the txn count on the ssn, and destroy ssn if it's closing. * We trust TS not to create more TXNs after signalling SSN close! */ if (ssndata->closing && ssndata->txn_count <= 1) { if (ssndata->iconn) { tx_list_destroy(ssndata->iconn); ib_conn_t *conn = ssndata->iconn; ib_engine_t *ib = conn->ib; ssndata->iconn = NULL; TSDebug("ironbee", "tsib_txn_ctx_destroy: calling ib_state_notify_conn_closed()"); ib_state_notify_conn_closed(ib, conn); TSDebug("ironbee", "CONN DESTROY: conn=%p", conn); ib_conn_destroy(conn); } TSContDataSet(ssndata->contp, NULL); TSContDestroy(ssndata->contp); ib_lock_unlock(ssndata->mutex); ib_lock_destroy_malloc(ssndata->mutex); TSfree(ssndata); } else { --(ssndata->txn_count); ib_lock_unlock(ssndata->mutex); } TSfree(txndata); }
static ib_status_t sqli_op_execute( ib_tx_t *tx, void *instance_data, const ib_field_t *field, ib_field_t *capture, ib_num_t *result, void *cbdata ) { assert(tx != NULL); assert(field != NULL); assert(result != NULL); sfilter sf; ib_bytestr_t *bs; ib_status_t rc; const sqli_pattern_set_t *ps = (const sqli_pattern_set_t *)instance_data; *result = 0; /* Currently only bytestring types are supported. * Other types will just get passed through. */ if (field->type != IB_FTYPE_BYTESTR) { return IB_OK; } rc = ib_field_value(field, ib_ftype_bytestr_mutable_out(&bs)); if (rc != IB_OK) { return rc; } /* Run through libinjection. */ libinjection_sqli_init( &sf, (const char *)ib_bytestr_const_ptr(bs), ib_bytestr_length(bs), FLAG_NONE ); if (ps != NULL ) { libinjection_sqli_callback(&sf, sqli_lookup_word, (void *)ps); } if ( libinjection_is_sqli(&sf) ) { ib_log_debug_tx(tx, "Matched SQLi fingerprint: %s", sf.fingerprint); *result = 1; } return IB_OK; }
static ib_status_t ib_error_callback(ib_tx_t *tx, int status, void *cbdata) { tsib_txn_ctx *txndata = (tsib_txn_ctx *)tx->sctx; ib_log_debug_tx(tx, "ib_error_callback with status=%d", status); if ( is_error_status(status) ) { if (is_error_status(txndata->status) ) { ib_log_debug_tx(tx, "Ignoring: status already set to %d", txndata->status); return IB_OK; } /* We can't return an error after the response has started */ if (ib_flags_all(tx->flags, IB_TX_FCLIENTRES_STARTED)) { ib_log_debug_tx(tx, "Too late to change status=%d", status); return IB_DECLINED; } /* ironbee wants to return an HTTP status. We'll oblige */ /* FIXME: would the semantics work for 1xx? Do we care? */ /* No, we don't care unless a use case arises for the proxy * to initiate a 1xx response independently of the backend. */ txndata->status = status; return IB_OK; } return IB_ENOTIMPL; }
static void ironbee_plugin_txn_close(TSCont contp, TSHttpTxn txnp) { assert(contp != NULL); assert(txnp != NULL); tsib_txn_ctx *txndata; txndata = TSContDataGet(contp); if (txndata != NULL) { TSContDestroy(txndata->out_data_cont); TSContDestroy(txndata->in_data_cont); } TSContDataSet(contp, NULL); TSContDestroy(contp); if ( (txndata != NULL) && (txndata->tx != NULL) ) { ib_log_debug_tx(txndata->tx, "TXN Close: %p", (void *)contp); tsib_txn_ctx_destroy(txndata); } TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE); }
static ib_status_t xss_op_execute( ib_tx_t *tx, const ib_field_t *field, ib_field_t *capture, ib_num_t *result, void *instance_data, void *cbdata ) { assert(tx != NULL); assert(field != NULL); assert(result != NULL); ib_bytestr_t *bs; ib_status_t rc; *result = 0; /* Currently only bytestring types are supported. * Other types will just get passed through. */ if (field->type != IB_FTYPE_BYTESTR) { return IB_OK; } rc = ib_field_value(field, ib_ftype_bytestr_mutable_out(&bs)); if (rc != IB_OK) { return rc; } /* Run through libinjection. */ // TODO: flags parameter is currently undocumented - using 0 if (libinjection_is_xss((const char *)ib_bytestr_const_ptr(bs), ib_bytestr_length(bs), 0)) { ib_log_debug_tx(tx, "Matched XSS."); *result = 1; } return IB_OK; }
static void ironbee_plugin_send_request_hdr(TSCont contp, TSHttpTxn txnp) { assert(contp != NULL); assert(txnp != NULL); tsib_txn_ctx *txndata; txndata = TSContDataGet(contp); /* This event is about as late as we can possibly block * body attacks against the server. */ /* If we are not yet blocked, ask IronBee if we should block. */ if (!HTTP_CODE(txndata->status)) { if (!ib_flags_all(txndata->tx->flags, IB_TX_FREQ_FINISHED)) { ib_log_debug_tx( txndata->tx, "data_event: calling ib_state_notify_request_finished()" ); (tsib_direction_client_req.ib_notify_end)(txndata->tx->ib, txndata->tx); } /* We we've transitioned to blocking, * - Add a handler for response header. * - Reenable with an error. * - Return (so we don't double-reenable). */ if (HTTP_CODE(txndata->status)) { TSHttpTxnHookAdd(txnp, TS_HTTP_SEND_RESPONSE_HDR_HOOK, contp); TSHttpTxnReenable(txnp, TS_EVENT_HTTP_ERROR); return; } } TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE); }
/** * Callback functions for IronBee to signal to us */ static ib_status_t ib_header_callback( ib_tx_t *tx, ib_server_direction_t dir, ib_server_header_action_t action, const char *name, size_t name_length, const char *value, size_t value_length, void *cbdata ) { tsib_txn_ctx *txndata = (tsib_txn_ctx *)tx->sctx; hdr_action_t *header; /* Logic for whether we're in time for the requested action */ /* Output headers can change any time before they're sent */ /* Input headers can only be touched during their read */ if (ib_flags_all(tx->flags, IB_TX_FCLIENTRES_STARTED) || (ib_flags_all(tx->flags, IB_TX_FSERVERREQ_STARTED) && dir == IB_SERVER_REQUEST)) { ib_log_debug_tx(tx, "Too late to change headers."); return IB_DECLINED; /* too late for requested op */ } header = ib_mm_alloc(tx->mm, sizeof(*header)); header->next = txndata->hdr_actions; txndata->hdr_actions = header; header->dir = dir; /* FIXME: deferring merge support - implementing append instead */ header->action = action = action == IB_HDR_MERGE ? IB_HDR_APPEND : action; header->hdr = ib_mm_memdup_to_str(tx->mm, name, name_length); header->value = ib_mm_memdup_to_str(tx->mm, value, value_length); return IB_OK; }
ib_status_t ib_parsed_resp_line_create(ib_tx_t *tx, ib_parsed_resp_line_t **line, const char *raw, size_t raw_len, const char *protocol, size_t protocol_len, const char *status, size_t status_len, const char *msg, size_t msg_len) { ib_status_t rc = IB_OK; assert(tx != NULL); assert(tx->ib != NULL); assert(tx->mp != NULL); ib_parsed_resp_line_t *line_tmp = ib_mpool_alloc(tx->mp, sizeof(*line_tmp)); if (line_tmp == NULL) { *line = NULL; return IB_EALLOC; } if (protocol != NULL) { rc = ib_bytestr_dup_mem(&line_tmp->protocol, tx->mp, (const uint8_t *)protocol, protocol_len); if (rc != IB_OK) { return rc; } } else { rc = ib_bytestr_dup_mem(&line_tmp->protocol, tx->mp, (const uint8_t *)"", 0); if (rc != IB_OK) { return rc; } } if (status != NULL) { rc = ib_bytestr_dup_mem(&line_tmp->status, tx->mp, (const uint8_t *)status, status_len); if (rc != IB_OK) { return rc; } } else { rc = ib_bytestr_dup_mem(&line_tmp->status, tx->mp, (const uint8_t *)"", 0); if (rc != IB_OK) { return rc; } } if (msg != NULL) { rc = ib_bytestr_dup_mem(&line_tmp->msg, tx->mp, (const uint8_t *)msg, msg_len); if (rc != IB_OK) { return rc; } } else { rc = ib_bytestr_dup_mem(&line_tmp->msg, tx->mp, (const uint8_t *)"", 0); if (rc != IB_OK) { return rc; } } /* If no raw line is available, then create one. */ if (raw == NULL) { assert(protocol != NULL); assert(status != NULL); if (protocol_len + status_len + msg_len == 0) { rc = ib_bytestr_dup_mem(&line_tmp->raw, tx->mp, (const uint8_t *)"", 0); if (rc != IB_OK) { return rc; } } else { /* Create a correctly sized bytestr and manually copy * the data into it. */ rc = ib_bytestr_create(&line_tmp->raw, tx->mp, protocol_len + 1 + status_len + (msg == NULL ? 0 : 1 + msg_len)); if (rc != IB_OK) { return rc; } ib_bytestr_append_mem(line_tmp->raw, (const uint8_t *)protocol, protocol_len); ib_bytestr_append_mem(line_tmp->raw, (const uint8_t *)" ", 1); ib_bytestr_append_mem(line_tmp->raw, (const uint8_t *)status, status_len); if (msg != NULL) { ib_bytestr_append_mem(line_tmp->raw, (const uint8_t *)" ", 1); ib_bytestr_append_mem(line_tmp->raw, (const uint8_t *)msg, msg_len); } } } else { rc = ib_bytestr_dup_mem(&line_tmp->raw, tx->mp, (const uint8_t *)raw, raw_len); if (rc != IB_OK) { return rc; } /* Now, if all components are missing, then parse them out * from the raw line. If only some are missing, then * do not assume anything is parsable. * * NOTE: This is a strict HTTP parser and assumes single * space (0x20) component separators. Better is to * have the server parse the components properly. */ if ((protocol == NULL) && (status == NULL) && (msg == NULL)) { uint8_t *raw_end = (uint8_t *)(raw + raw_len) - 1; uint8_t *ptr = (uint8_t *)raw; const uint8_t *parsed_field = ptr; ib_log_debug_tx(tx, "Parsing raw response line into components."); /* Parse the protocol. */ while (ptr <= raw_end) { if (*ptr == ' ') { break; } ++ptr; } rc = ib_bytestr_dup_mem(&line_tmp->protocol, tx->mp, parsed_field, (ptr - parsed_field)); if (rc != IB_OK) { return rc; } /* Parse the status. */ parsed_field = ++ptr; while (ptr <= raw_end) { if (*ptr == ' ') { break; } ++ptr; } rc = ib_bytestr_dup_mem(&line_tmp->status, tx->mp, parsed_field, (ptr - parsed_field)); if (rc != IB_OK) { return rc; } /* Parse the message. */ parsed_field = ++ptr; if (parsed_field <= raw_end) { rc = ib_bytestr_dup_mem(&line_tmp->msg, tx->mp, parsed_field, (raw_end - parsed_field) + 1); if (rc != IB_OK) { return rc; } } else { rc = ib_bytestr_dup_mem(&line_tmp->msg, tx->mp, (const uint8_t *)"", 0); if (rc != IB_OK) { return rc; } } } } /* Commit back successfully created line. */ *line = line_tmp; return IB_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; }
/** * Handle request_header events for user agent extraction. * * Extract the "request_headers" field (a list) from the transactions's * data provider instance, then loop through the list, looking for the * "User-Agent" field. If found, the value is parsed and used to update the * connection object fields. * * @param[in] ib IronBee object * @param[in,out] tx Transaction. * @param[in] event Event type * @param[in] data Callback data (module) * * @returns Status code */ static ib_status_t modua_user_agent(ib_engine_t *ib, ib_tx_t *tx, ib_state_event_type_t event, void *data) { assert(ib != NULL); assert(tx != NULL); assert(tx->var_store != NULL); assert(event == request_header_finished_event); assert(data != NULL); const ib_module_t *m = (const ib_module_t *)data; const ib_field_t *req_agent = NULL; ib_status_t rc = IB_OK; const ib_list_t *bs_list; const ib_bytestr_t *bs; const modua_config_t *cfg; rc = ib_context_module_config(ib_context_main(ib), m, &cfg); if (rc != IB_OK) { ib_log_error_tx(tx, "Can't fetch configuration: %s", ib_status_to_string(rc)); return rc; } /* Extract the User-Agent header field */ rc = ib_var_target_get_const( cfg->user_agent, &bs_list, tx->mp, tx->var_store ); if (rc == IB_ENOENT || ib_list_elements(bs_list) == 0) { ib_log_debug_tx(tx, "request_header_finished_event: No user agent"); return IB_OK; } if (rc != IB_OK) { ib_log_error_tx(tx, "Cannot retrieve request_headers:User-Agent: %d", rc); return rc; } if (IB_LIST_ELEMENTS(bs_list) == 0) { ib_log_debug_tx(tx, "request_header_finished_event: No user agent"); return IB_OK; } req_agent = (ib_field_t *)IB_LIST_NODE_DATA(IB_LIST_LAST(bs_list)); /* Found it: copy the data into a newly allocated string buffer */ rc = ib_field_value_type(req_agent, ib_ftype_bytestr_out(&bs), IB_FTYPE_BYTESTR); if (rc != IB_OK) { ib_log_error_tx(tx, "Request user agent is not a BYTESTR: %s", ib_status_to_string(rc)); return rc; } /* Finally, split it up & store the components */ rc = modua_agent_fields(ib, tx, bs); return rc; }
/** * Handle request_header events for remote IP extraction. * * Extract the "request_headers" field (a list) from the transactions's * data provider instance, then loop through the list, looking for the * "X-Forwarded-For" field. If found, the first value in the (comma * separated) list replaces the local ip address string in the connection * object. * * @param[in] ib IronBee object * @param[in,out] tx Transaction object * @param[in] event Event type * @param[in] cbdata Callback data (module) * * @returns Status code */ static ib_status_t modua_remoteip(ib_engine_t *ib, ib_tx_t *tx, ib_state_event_type_t event, void *cbdata) { assert(ib != NULL); assert(tx != NULL); assert(tx->var_store != NULL); assert(event == request_header_finished_event); const ib_module_t *m = (const ib_module_t *)cbdata; ib_field_t *field = NULL; ib_status_t rc = IB_OK; const ib_bytestr_t *bs; const uint8_t *data; size_t len; char *buf; uint8_t *comma; const ib_list_t *list; const ib_list_node_t *node; const ib_field_t *forwarded; uint8_t *stripped; size_t num; ib_flags_t flags; const modua_config_t *cfg; rc = ib_context_module_config(ib_context_main(ib), m, &cfg); if (rc != IB_OK) { ib_log_error_tx(tx, "Can't fetch configuration: %s", ib_status_to_string(rc)); return rc; } ib_log_debug3_tx(tx, "Checking for alternate remote address"); /* Extract the X-Forwarded-For header field */ rc = ib_var_target_get_const( cfg->forwarded_for, &list, tx->mp, tx->var_store ); if (rc == IB_ENOENT || ib_list_elements(list) == 0) { ib_log_debug_tx(tx, "No X-Forwarded-For"); return IB_OK; } if (rc != IB_OK) { ib_log_error_tx(tx, "Cannot retrieve request_headers:User-Agent: %d", rc); return rc; } num = ib_list_elements(list); if (num == 0) { ib_log_debug_tx(tx, "No X-Forwarded-For header found"); return rc; } else if (num != 1) { ib_log_debug_tx(tx, "%zd X-Forwarded-For headers found: ignoring", num); return rc; } node = ib_list_last_const(list); if ( (node == NULL) || (node->data == NULL) ) { ib_log_notice_tx(tx, "Invalid X-Forwarded-For header found"); return rc; } forwarded = (const ib_field_t *)node->data; /* Found it: copy the data into a newly allocated string buffer */ rc = ib_field_value_type(forwarded, ib_ftype_bytestr_out(&bs), IB_FTYPE_BYTESTR); if (rc != IB_OK) { ib_log_notice_tx(tx, "Invalid X-Forwarded-For header value"); return rc; } if (bs == NULL) { ib_log_notice_tx(tx, "X-Forwarded-For header not a bytestr"); return IB_EINVAL; } len = ib_bytestr_length(bs); data = ib_bytestr_const_ptr(bs); /* Search for a comma in the buffer */ comma = memchr(data, ',', len); if (comma != NULL) { len = comma - data; } /* Trim whitespace */ stripped = (uint8_t *)data; rc = ib_strtrim_lr_ex(IB_STROP_INPLACE, tx->mp, stripped, len, &stripped, &len, &flags); if (rc != IB_OK) { return rc; } /* Verify that it looks like a valid IP v4/6 address */ rc = ib_ip_validate_ex((const char *)stripped, len); if (rc != IB_OK) { ib_log_error_tx(tx, "X-Forwarded-For \"%.*s\" is not a valid IP address", (int)len, stripped ); return IB_OK; } /* Allocate memory for copy of stripped string */ buf = (char *)ib_mpool_alloc(tx->mp, len+1); if (buf == NULL) { ib_log_error_tx(tx, "Failed to allocate %zd bytes for remote address", len+1); return IB_EALLOC; } /* Copy the string out */ memcpy(buf, stripped, len); buf[len] = '\0'; ib_log_debug_tx(tx, "Remote address changed to \"%s\"", buf); /* This will lose the pointer to the original address * buffer, but it should be cleaned up with the rest * of the memory pool. */ tx->er_ipstr = buf; /* Update the remote address field in the tx collection */ rc = ib_field_create_bytestr_alias( &field, tx->mp, "", 0, (uint8_t *)buf, len ); if (rc != IB_OK) { ib_log_error_tx(tx, "Failed to create field for remote_addr: %s", ib_status_to_string(rc)); return rc; } rc = ib_var_source_set(cfg->remote_addr, tx->var_store, field); if (rc != IB_OK) { ib_log_error_tx(tx, "Failed to set remote address var: %s", ib_status_to_string(rc)); return rc; } return IB_OK; }
/** * 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; }
static void ironbee_plugin_pre_remap(TSCont contp, TSHttpTxn txnp) { assert(contp != NULL); assert(txnp != NULL); tsib_txn_ctx *txndata; tsib_hdr_outcome status; int request_inspection_finished = 0; txndata = TSContDataGet(contp); assert ((txndata != NULL) && (txndata->tx != NULL)); status = process_hdr(txndata, txnp, &tsib_direction_client_req); if (HDR_OUTCOME_IS_HTTP_OR_ERROR(status, txndata)) { if (status == HDR_HTTP_STATUS) { ib_log_debug_tx(txndata->tx, "HTTP code %d contp=%p", txndata->status, contp); } else { /* Ironbee set a status we don't handle. * We returned EINVAL, but we also need housekeeping to * avoid a crash in modhtp and log something bad. */ ib_log_debug_tx(txndata->tx, "Internal error %d contp=%p", txndata->status, contp); /* Ugly hack: notifications to stop modhtp bombing out */ request_inspection_finished = 1; } } else { /* Other nonzero statuses not supported */ switch(status) { case HDR_OK: /* If we're not inspecting the Request body, * we can bring forward notification of end-request * so any header-only tests run on Request phase * can abort the tx before opening a backend connection. */ if (!ib_flags_all(txndata->tx->flags, IB_TX_FINSPECT_REQBODY)) { request_inspection_finished = 1; } break; /* All's well */ case HDR_HTTP_STATUS: // FIXME: should we take the initiative here and return 500? ib_log_error_tx(txndata->tx, "Internal error: ts-ironbee requested error but no error response set."); break; case HDR_HTTP_100: /* This can't actually happen with current Trafficserver * versions, as TS will generate a 400 error without * reference to us. But in case that changes in future ... */ ib_log_error_tx(txndata->tx, "No request headers found."); break; default: ib_log_error_tx(txndata->tx, "Unhandled state arose in handling request headers."); break; } } if (request_inspection_finished) { if (!ib_flags_all(txndata->tx->flags, IB_TX_FREQ_STARTED) ) { ib_state_notify_request_started(txndata->tx->ib, txndata->tx, NULL); } if (!ib_flags_all(txndata->tx->flags, IB_TX_FREQ_FINISHED) ) { ib_state_notify_request_finished(txndata->tx->ib, txndata->tx); } } else { /* hook an input filter to watch data */ TSHttpTxnHookAdd(txnp, TS_HTTP_REQUEST_TRANSFORM_HOOK, txndata->in_data_cont); } /* Flag that we can no longer prevent a request going to backend */ ib_tx_flags_set(txndata->tx, IB_TX_FSERVERREQ_STARTED); /* Check whether Ironbee told us to block the request. * This could now come not just from process_hdr, but also * from a brought-forward notification if we aren't inspecting * a request body and notified request_finished. */ if (HTTP_CODE(txndata->status)) { TSHttpTxnHookAdd(txnp, TS_HTTP_SEND_RESPONSE_HDR_HOOK, contp); TSHttpTxnReenable(txnp, TS_EVENT_HTTP_ERROR); } else { TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE); } }
/** * Parse the user agent header, splitting into component fields. * * Attempt to tokenize the user agent string passed in, storing the * result in the DPI associated with the transaction. * * @param[in] ib IronBee object * @param[in,out] tx Transaction object * @param[in] bs Byte string containing the agent string * * @returns Status code */ static ib_status_t modua_agent_fields(ib_engine_t *ib, ib_tx_t *tx, const ib_bytestr_t *bs) { const modua_match_rule_t *rule = NULL; ib_field_t *agent_list = NULL; char *product = NULL; char *platform = NULL; char *extra = NULL; char *agent; char *buf; size_t len; ib_status_t rc; /* Get the length of the byte string */ len = ib_bytestr_length(bs); /* Allocate memory for a copy of the string to split up below. */ buf = (char *)ib_mpool_calloc(tx->mp, 1, len+1); if (buf == NULL) { ib_log_error_tx(tx, "Failed to allocate %zd bytes for agent string", len+1); return IB_EALLOC; } /* Copy the string out */ memcpy(buf, ib_bytestr_const_ptr(bs), len); buf[len] = '\0'; ib_log_debug_tx(tx, "Found user agent: '%s'", buf); /* Copy the agent string */ agent = (char *)ib_mpool_strdup(tx->mp, buf); if (agent == NULL) { ib_log_error_tx(tx, "Failed to allocate copy of agent string"); return IB_EALLOC; } /* Parse the user agent string */ rc = modua_parse_uastring(buf, &product, &platform, &extra); if (rc != IB_OK) { ib_log_debug_tx(tx, "Failed to parse User Agent string '%s'", agent); return IB_OK; } /* Categorize the parsed string */ rule = modua_match_cat_rules(product, platform, extra); if (rule == NULL) { ib_log_debug_tx(tx, "No rule matched" ); } else { ib_log_debug_tx(tx, "Matched to rule #%d / category '%s'", rule->rule_num, rule->category ); } /* Build a new list. */ rc = ib_data_add_list(tx->data, "UA", &agent_list); if (rc != IB_OK) { ib_log_alert_tx(tx, "Unable to add UserAgent list to DPI."); return rc; } /* Store Agent */ rc = modua_store_field(ib, tx->mp, agent_list, "agent", agent); if (rc != IB_OK) { return rc; } /* Store product */ rc = modua_store_field(ib, tx->mp, agent_list, "PRODUCT", product); if (rc != IB_OK) { return rc; } /* Store Platform */ rc = modua_store_field(ib, tx->mp, agent_list, "OS", platform); if (rc != IB_OK) { return rc; } /* Store Extra */ rc = modua_store_field(ib, tx->mp, agent_list, "extra", extra); if (rc != IB_OK) { return rc; } /* Store Extra */ if (rule != NULL) { rc = modua_store_field(ib, tx->mp, agent_list, "category", rule->category); } else { rc = modua_store_field(ib, tx->mp, agent_list, "category", NULL ); } if (rc != IB_OK) { return rc; } /* Done */ return IB_OK; }
ib_status_t ib_parsed_req_line_create(ib_tx_t *tx, ib_parsed_req_line_t **line, const char *raw, size_t raw_len, const char *method, size_t method_len, const char *uri, size_t uri_len, const char *protocol, size_t protocol_len) { ib_status_t rc = IB_OK; assert(tx != NULL); assert(tx->ib != NULL); assert(tx->mp != NULL); ib_parsed_req_line_t *line_tmp = ib_mpool_alloc(tx->mp, sizeof(*line_tmp)); if ( line_tmp == NULL ) { *line = NULL; return IB_EALLOC; } /* Record the components if available. If the components are * not available, but the raw line is, then it will be possible * to parse the components out later on. Otherwise, if there * is no component and no raw line, then set default values. */ if (method != NULL) { rc = ib_bytestr_dup_mem(&line_tmp->method, tx->mp, (const uint8_t *)method, method_len); if (rc != IB_OK) { return rc; } } else { rc = ib_bytestr_dup_mem(&line_tmp->method, tx->mp, (const uint8_t *)"", 0); if (rc != IB_OK) { return rc; } } if (uri != NULL) { rc = ib_bytestr_dup_mem(&line_tmp->uri, tx->mp, (const uint8_t *)uri, uri_len); if (rc != IB_OK) { return rc; } } else { rc = ib_bytestr_dup_mem(&line_tmp->uri, tx->mp, (const uint8_t *)"", 0); if (rc != IB_OK) { return rc; } } if (protocol != NULL) { rc = ib_bytestr_dup_mem(&line_tmp->protocol, tx->mp, (const uint8_t *)protocol, protocol_len); if (rc != IB_OK) { return rc; } } else { rc = ib_bytestr_dup_mem(&line_tmp->protocol, tx->mp, (const uint8_t *)"", 0); if (rc != IB_OK) { return rc; } } /* If no raw line is available, then create one. */ if (raw == NULL) { if (method_len + uri_len + protocol_len == 0) { ib_log_notice_tx(tx, "Unable to generate raw request line without line " "components - using zero length request line." ); rc = ib_bytestr_dup_mem(&line_tmp->raw, tx->mp, (const uint8_t *)"", 0); if (rc != IB_OK) { return rc; } } else { size_t raw_line_len; assert(method != NULL); assert(uri != NULL); /* Create a correctly sized bytestr and manually copy * the data into it. */ raw_line_len = method_len + 1 + uri_len + (protocol == NULL ? 0 : 1 + protocol_len); rc = ib_bytestr_create(&line_tmp->raw, tx->mp, raw_line_len); if (rc != IB_OK) { return rc; } ib_log_debug_tx(tx, "Generating raw request line from components " "(length %zd).", raw_line_len); ib_bytestr_append_mem(line_tmp->raw, (const uint8_t *)method, method_len); ib_bytestr_append_mem(line_tmp->raw, (const uint8_t *)" ", 1); ib_bytestr_append_mem(line_tmp->raw, (const uint8_t *)uri, uri_len); if (protocol != NULL) { ib_bytestr_append_mem(line_tmp->raw, (const uint8_t *)" ", 1); ib_bytestr_append_mem(line_tmp->raw, (const uint8_t *)protocol, protocol_len); } } } else { rc = ib_bytestr_dup_mem(&line_tmp->raw, tx->mp, (const uint8_t *)raw, raw_len); if (rc != IB_OK) { return rc; } /* Now, if all components are missing, then parse them out * from the raw line. If only some are missing, then * do not assume anything is parsable. * * NOTE: This is a strict HTTP parser and assumes single * space (0x20) component separators. Better is to * have the server parse the components properly. */ if ((method == NULL) && (uri == NULL) && (protocol == NULL)) { uint8_t *raw_end = (uint8_t *)(raw + raw_len) - 1; uint8_t *ptr = (uint8_t *)raw; const uint8_t *parsed_field = ptr; ib_log_debug_tx(tx, "Parsing raw request line into components."); /* Parse the method. */ while (ptr <= raw_end) { if (*ptr == ' ') { break; } ++ptr; } rc = ib_bytestr_dup_mem(&line_tmp->method, tx->mp, parsed_field, (ptr - parsed_field)); if (rc != IB_OK) { return rc; } /* Parse the uri. */ parsed_field = ++ptr; while (ptr <= raw_end) { if (*ptr == ' ') { break; } ++ptr; } rc = ib_bytestr_dup_mem(&line_tmp->uri, tx->mp, parsed_field, (ptr - parsed_field)); if (rc != IB_OK) { return rc; } /* Parse the protocol. */ parsed_field = ++ptr; if (parsed_field <= raw_end) { rc = ib_bytestr_dup_mem(&line_tmp->protocol, tx->mp, parsed_field, (raw_end - parsed_field) + 1); if (rc != IB_OK) { return rc; } } else { rc = ib_bytestr_dup_mem(&line_tmp->protocol, tx->mp, (const uint8_t *)"", 0); if (rc != IB_OK) { return rc; } } } } /* Commit back successfully created line. */ *line = line_tmp; return IB_OK; }
static void ironbee_plugin_txn_start(TSCont contp, TSHttpTxn txnp) { assert(contp != NULL); assert(txnp != NULL); /* start of Request */ /* First req on a connection, we set up conn stuff */ ib_status_t rc; ib_engine_t *ib = NULL; TSCont mycont; tsib_ssn_ctx *ssndata; tsib_txn_ctx *txndata; ssndata = TSContDataGet(contp); if (ssndata->iconn == NULL) { rc = tsib_manager_engine_acquire(&ib); if (rc == IB_DECLINED) { /* OK, this means the manager is disabled deliberately, * but otherwise all's well. So this TXN * gets processed without intervention from Ironbee * and is invisble when our SSN_CLOSE hook runs. */ TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE); TSDebug("ironbee", "Decline from engine manager"); return; } else if (rc != IB_OK) { TSError("[ironbee] Failed to acquire engine: %s", ib_status_to_string(rc)); goto noib_error; } if (ib != NULL) { rc = ib_conn_create(ib, &ssndata->iconn, contp); if (rc != IB_OK) { TSError("[ironbee] ib_conn_create: %s", ib_status_to_string(rc)); tsib_manager_engine_release(ib); goto noib_error; } /* In the normal case, release the engine when the * connection's memory pool is destroyed */ rc = ib_mm_register_cleanup(ssndata->iconn->mm, cleanup_ib_connection, ib); if (rc != IB_OK) { TSError("[ironbee] ib_mm_register_cleanup: %s", ib_status_to_string(rc)); tsib_manager_engine_release(ib); goto noib_error; } TSDebug("ironbee", "CONN CREATE: conn=%p", ssndata->iconn); ssndata->txnp = txnp; ssndata->txn_count = ssndata->closing = 0; rc = ironbee_conn_init(ssndata); if (rc != IB_OK) { TSError("[ironbee] ironbee_conn_init: %s", ib_status_to_string(rc)); goto noib_error; } TSContDataSet(contp, ssndata); TSDebug("ironbee", "ironbee_plugin: ib_state_notify_conn_opened()"); rc = ib_state_notify_conn_opened(ib, ssndata->iconn); if (rc != IB_OK) { TSError("[ironbee] Failed to notify connection opened: %s", ib_status_to_string(rc)); } } else { /* Use TSError where there's no ib or tx */ TSError("Ironbee: No ironbee engine!"); goto noib_error; } } /* create a txn cont (request ctx) and tx */ txndata = TSmalloc(sizeof(*txndata)); memset(txndata, 0, sizeof(*txndata)); txndata->ssn = ssndata; txndata->txnp = txnp; rc = ib_tx_create(&txndata->tx, ssndata->iconn, txndata); if (rc != IB_OK) { TSError("[ironbee] Failed to create tx: %d", rc); tsib_manager_engine_release(ib); TSfree(txndata); goto noib_error; } ++ssndata->txn_count; ib_log_debug_tx(txndata->tx, "TX CREATE: conn=%p tx=%p id=%s txn_count=%d", ssndata->iconn, txndata->tx, txndata->tx->id, txndata->ssn->txn_count); mycont = TSContCreate(ironbee_plugin, TSContMutexGet(contp)); TSContDataSet(mycont, txndata); TSHttpTxnHookAdd(txnp, TS_HTTP_TXN_CLOSE_HOOK, mycont); /* Hook to process responses */ TSHttpTxnHookAdd(txnp, TS_HTTP_READ_RESPONSE_HDR_HOOK, mycont); /* Hook to process requests */ TSHttpTxnHookAdd(txnp, TS_HTTP_PRE_REMAP_HOOK, mycont); /* Hook to process request headers when sent to the server. */ TSHttpTxnHookAdd(txnp, TS_HTTP_SEND_REQUEST_HDR_HOOK, mycont); /* Create continuations for input and output filtering * to give them txn lifetime. */ txndata->in_data_cont = TSTransformCreate(in_data_event, txnp); TSContDataSet(txndata->in_data_cont, txndata); txndata->out_data_cont = TSTransformCreate(out_data_event, txnp); TSContDataSet(txndata->out_data_cont, txndata); TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE); return; noib_error: /* NULL txndata signals this to SEND_RESPONSE */ TSContDataSet(contp, NULL); TSError("[ironbee] Internal error initialising for transaction"); TSHttpTxnHookAdd(txnp, TS_HTTP_SEND_RESPONSE_HDR_HOOK, contp); /* FIXME: check this. * Purpose is to ensure contp doesn't leak, but may not be right */ TSHttpTxnHookAdd(txnp, TS_HTTP_TXN_CLOSE_HOOK, contp); TSHttpTxnReenable(txnp, TS_EVENT_HTTP_ERROR); return; }
/** * Handler function to generate an error response */ static void error_response(TSHttpTxn txnp, tsib_txn_ctx *txndata) { const char *reason; TSMBuffer bufp; TSMLoc hdr_loc; TSMLoc field_loc; hdr_list *hdrs; TSReturnCode rv; /* make caller responsible for sanity checking */ assert((txndata != NULL) && (txndata->tx != NULL)); reason = TSHttpHdrReasonLookup(txndata->status); if (TSHttpTxnClientRespGet(txnp, &bufp, &hdr_loc) != TS_SUCCESS) { ib_log_error_tx(txndata->tx, "ErrorDoc: couldn't retrieve client response header."); TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE); return; } rv = TSHttpHdrStatusSet(bufp, hdr_loc, txndata->status); if (rv != TS_SUCCESS) { ib_log_error_tx(txndata->tx, "ErrorDoc: TSHttpHdrStatusSet"); } if (reason == NULL) { reason = "Other"; } rv = TSHttpHdrReasonSet(bufp, hdr_loc, reason, strlen(reason)); if (rv != TS_SUCCESS) { ib_log_error_tx(txndata->tx, "ErrorDoc: TSHttpHdrReasonSet"); } while (hdrs = txndata->err_hdrs, hdrs != 0) { txndata->err_hdrs = hdrs->next; rv = TSMimeHdrFieldCreate(bufp, hdr_loc, &field_loc); if (rv != TS_SUCCESS) { ib_log_error_tx(txndata->tx, "ErrorDoc: TSMimeHdrFieldCreate"); continue; } rv = TSMimeHdrFieldNameSet(bufp, hdr_loc, field_loc, hdrs->hdr, strlen(hdrs->hdr)); if (rv != TS_SUCCESS) { ib_log_error_tx(txndata->tx, "ErrorDoc: TSMimeHdrFieldNameSet"); goto errordoc_free1; } rv = TSMimeHdrFieldValueStringInsert(bufp, hdr_loc, field_loc, -1, hdrs->value, strlen(hdrs->value)); if (rv != TS_SUCCESS) { ib_log_error_tx(txndata->tx, "ErrorDoc: TSMimeHdrFieldValueStringInsert"); goto errordoc_free1; } rv = TSMimeHdrFieldAppend(bufp, hdr_loc, field_loc); if (rv != TS_SUCCESS) { ib_log_error_tx(txndata->tx, "ErrorDoc: TSMimeHdrFieldAppend"); goto errordoc_free1; } errordoc_free1: rv = TSHandleMLocRelease(bufp, hdr_loc, field_loc); if (rv != TS_SUCCESS) { ib_log_error_tx(txndata->tx, "ErrorDoc: TSHandleMLocRelease 1"); continue; } } if (txndata->err_body) { /* this will free the body, so copy it first! */ TSHttpTxnErrorBodySet(txnp, txndata->err_body, txndata->err_body_len, NULL); } rv = TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc); if (rv != TS_SUCCESS) { ib_log_error_tx(txndata->tx, "ErrorDoc: TSHandleMLocRelease 2"); } ib_log_debug_tx(txndata->tx, "Sent error page %d \"%s\".", txndata->status, reason); }
/** * Lookup the IP address in the GeoIP database * * @param[in] ib IronBee engine * @param[in] tx Transaction * @param[in] event Event * @param[in] data callback data (Module configuration) */ static ib_status_t geoip_lookup( ib_engine_t *ib, ib_tx_t *tx, ib_state_event_type_t event, void *data ) { assert(ib != NULL); assert(event == handle_context_tx_event); assert(data != NULL); const char *ip = tx->er_ipstr; const module_data_t *mod_data = (const module_data_t *)data; if (ip == NULL) { ib_log_alert_tx(tx, "Trying to lookup NULL IP in GEOIP"); return IB_EINVAL; } #ifdef GEOIP_HAVE_VERSION /** * Some configurations exist as single characters and must be converted to * a string. This is simply a place to assemble that string before * it is passed into ip_data_add_nulstr. * This is only needed if we have support confidence items. WAM */ char one_char_str[2] = { '\0', '\0' }; #endif /* GEOIP_HAVE_VERSION */ ib_status_t rc; /* Declare and initialize the GeoIP property list. * Regardless of if we find a record or not, we want to create the list * artifact so that later modules know we ran and did [not] find a * record. */ ib_field_t *geoip_lst = NULL; ib_field_t *tmp_field = NULL; /* Id of geo ip record to read. */ int geoip_id; ib_log_debug_tx(tx, "GeoIP Lookup '%s'", ip); /* Build a new list. */ rc = ib_var_source_initialize( mod_data->geoip_source, &geoip_lst, tx->var_store, IB_FTYPE_LIST ); /* NOTICE: Called before GeoIP_record_by_addr allocates a * GeoIPRecord. */ if (rc != IB_OK) { ib_log_alert_tx(tx, "Unable to add GEOIP var."); return IB_EINVAL; } if (mod_data->geoip_db == NULL) { ib_log_alert_tx(tx, "GeoIP database was never opened. Perhaps the " "configuration file needs a GeoIPDatabaseFile " "\"/usr/share/geoip/GeoLite.dat\" line?"); return IB_EINVAL; } geoip_id = GeoIP_id_by_addr(mod_data->geoip_db, ip); if (geoip_id > 0) { const char *tmp_str; ib_log_debug_tx(tx, "GeoIP record found."); /* Add integers. */ tmp_field = NULL; tmp_str = GeoIP_code_by_id(geoip_id); if (tmp_str) { ib_field_create(&tmp_field, tx->mp, IB_FIELD_NAME("country_code"), IB_FTYPE_NULSTR, ib_ftype_nulstr_in(tmp_str)); ib_field_list_add(geoip_lst, tmp_field); } tmp_str = GeoIP_code3_by_id(geoip_id); if (tmp_str) { ib_field_create(&tmp_field, tx->mp, IB_FIELD_NAME("country_code3"), IB_FTYPE_NULSTR, ib_ftype_nulstr_in(tmp_str)); ib_field_list_add(geoip_lst, tmp_field); } tmp_str = GeoIP_country_name_by_id(mod_data->geoip_db, geoip_id); if (tmp_str) { ib_field_create(&tmp_field, tx->mp, IB_FIELD_NAME("country_name"), IB_FTYPE_NULSTR, ib_ftype_nulstr_in(tmp_str)); ib_field_list_add(geoip_lst, tmp_field); } tmp_str = GeoIP_continent_by_id(geoip_id); if (tmp_str) { ib_field_create(&tmp_field, tx->mp, IB_FIELD_NAME("continent_code"), IB_FTYPE_NULSTR, ib_ftype_nulstr_in(tmp_str)); ib_field_list_add(geoip_lst, tmp_field); } } else { ib_log_debug_tx(tx, "No GeoIP record found."); ib_field_create(&tmp_field, tx->mp, IB_FIELD_NAME("country_code"), IB_FTYPE_NULSTR, ib_ftype_nulstr_in("O1")); ib_field_list_add(geoip_lst, tmp_field); ib_field_create(&tmp_field, tx->mp, IB_FIELD_NAME("country_code3"), IB_FTYPE_NULSTR, ib_ftype_nulstr_in("O01")); ib_field_list_add(geoip_lst, tmp_field); ib_field_create(&tmp_field, tx->mp, IB_FIELD_NAME("country_name"), IB_FTYPE_NULSTR, ib_ftype_nulstr_in("Other Country")); ib_field_list_add(geoip_lst, tmp_field); ib_field_create(&tmp_field, tx->mp, IB_FIELD_NAME("continent_code"), IB_FTYPE_NULSTR, ib_ftype_nulstr_in("O1")); ib_field_list_add(geoip_lst, tmp_field); } return IB_OK; }
/** * Handle request_header events for remote IP extraction. * * Extract the "request_headers" field (a list) from the transactions's * data provider instance, then loop through the list, looking for the * "X-Forwarded-For" field. If found, the first value in the (comma * separated) list replaces the local ip address string in the connection * object. * * @param[in] ib IronBee object * @param[in,out] tx Transaction object * @param[in] event Event type * @param[in] cbdata Callback data (not used) * * @returns Status code */ static ib_status_t modua_remoteip(ib_engine_t *ib, ib_tx_t *tx, ib_state_event_type_t event, void *cbdata) { assert(ib != NULL); assert(tx != NULL); assert(tx->data != NULL); assert(event == request_header_finished_event); ib_field_t *field = NULL; ib_status_t rc = IB_OK; const ib_bytestr_t *bs; const uint8_t *data; size_t len; char *buf; uint8_t *comma; const ib_list_t *list; const ib_list_node_t *node; const ib_field_t *forwarded; uint8_t *stripped; size_t num; ib_flags_t flags; ib_log_debug3_tx(tx, "Checking for alternate remote address"); /* Extract the X-Forwarded-For from the provider instance */ rc = ib_data_get(tx->data, "request_headers:X-Forwarded-For", &field); if ( (field == NULL) || (rc != IB_OK) ) { ib_log_debug_tx(tx, "No X-Forwarded-For field"); return IB_OK; } /* Because we asked for a filtered item, what we get back is a list */ rc = ib_field_value(field, ib_ftype_list_out(&list)); if (rc != IB_OK) { ib_log_debug_tx(tx, "No request header collection"); return rc; } num = ib_list_elements(list); if (num == 0) { ib_log_debug_tx(tx, "No X-Forwarded-For header found"); return rc; } else if (num != 1) { ib_log_debug_tx(tx, "%zd X-Forwarded-For headers found: ignoring", num); return rc; } node = ib_list_last_const(list); if ( (node == NULL) || (node->data == NULL) ) { ib_log_notice_tx(tx, "Invalid X-Forwarded-For header found"); return rc; } forwarded = (const ib_field_t *)node->data; /* Found it: copy the data into a newly allocated string buffer */ rc = ib_field_value_type(forwarded, ib_ftype_bytestr_out(&bs), IB_FTYPE_BYTESTR); if (rc != IB_OK) { ib_log_notice_tx(tx, "Invalid X-Forwarded-For header value"); return rc; } if (bs == NULL) { ib_log_notice_tx(tx, "X-Forwarded-For header not a bytestr"); return IB_EINVAL; } len = ib_bytestr_length(bs); data = ib_bytestr_const_ptr(bs); /* Search for a comma in the buffer */ comma = memchr(data, ',', len); if (comma != NULL) { len = comma - data; } /* Trim whitespace */ stripped = (uint8_t *)data; rc = ib_strtrim_lr_ex(IB_STROP_INPLACE, tx->mp, stripped, len, &stripped, &len, &flags); if (rc != IB_OK) { return rc; } /* Verify that it looks like a valid IP v4/6 address */ rc = ib_ip_validate_ex((const char *)stripped, len); if (rc != IB_OK) { ib_log_error_tx(tx, "X-Forwarded-For \"%.*s\" is not a valid IP address", (int)len, stripped ); return IB_OK; } /* Allocate memory for copy of stripped string */ buf = (char *)ib_mpool_alloc(tx->mp, len+1); if (buf == NULL) { ib_log_error_tx(tx, "Failed to allocate %zd bytes for remote address", len+1); return IB_EALLOC; } /* Copy the string out */ memcpy(buf, stripped, len); buf[len] = '\0'; ib_log_debug_tx(tx, "Remote address changed to \"%s\"", buf); /* This will lose the pointer to the original address * buffer, but it should be cleaned up with the rest * of the memory pool. */ tx->er_ipstr = buf; /* Update the remote address field in the tx collection */ rc = ib_data_add_bytestr(tx->data, "remote_addr", (uint8_t*)buf, len, NULL); if (rc != IB_OK) { ib_log_error_tx(tx, "Failed to create remote address TX field: %s", ib_status_to_string(rc)); return rc; } return IB_OK; }
static void ironbee_plugin_read_response_hdr(TSCont contp, TSHttpTxn txnp) { assert(contp != NULL); assert(txnp != NULL); tsib_txn_ctx *txndata; tsib_hdr_outcome status; txndata = TSContDataGet(contp); if (txndata->tx == NULL) { TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE); return; } /* Feed ironbee the headers if not done already. */ if (!ib_flags_all(txndata->tx->flags, IB_TX_FRES_STARTED)) { status = process_hdr(txndata, txnp, &tsib_direction_server_resp); /* OK, if this was an HTTP 100 response, it's not the * response we're interested in. No headers have been * sent yet, and no data will be sent until we've * reached here again with the final response. */ if (status == HDR_HTTP_100) { TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE); return; } // FIXME: Need to know if this fails as it (I think) means // that the response did not come from the server and // that ironbee should ignore it. /* I've not seen a fail here. AFAICT if either the origin * isn't responding or we're responding from cache. we * never reach here in the first place. */ } /* If ironbee signalled an error while processing request body data, * this is the first opportunity to divert to an errordoc */ if (HTTP_CODE(txndata->status)) { ib_log_debug_tx(txndata->tx, "HTTP code %d contp=%p", txndata->status, contp); TSHttpTxnHookAdd(txnp, TS_HTTP_SEND_RESPONSE_HDR_HOOK, contp); TSHttpTxnReenable(txnp, TS_EVENT_HTTP_ERROR); return; } /* If we're not going to inspect response body data * we can bring forward notification of response-end * so we're in time to respond with an errordoc if Ironbee * wants to block in the response phase. * * This currently fails. However, that appears to be because I * can't unset IB_TX_FINSPECT_RESBODY with InspectionEngineOptions */ if (!ib_flags_all(txndata->tx->flags, IB_TX_FINSPECT_RESBODY)) { if (!ib_flags_all(txndata->tx->flags, IB_TX_FRES_STARTED) ) { ib_state_notify_response_started(txndata->tx->ib, txndata->tx, NULL); } if (!ib_flags_all(txndata->tx->flags, IB_TX_FRES_FINISHED) ) { ib_state_notify_response_finished(txndata->tx->ib, txndata->tx); } /* Test again for Ironbee telling us to block */ if (HTTP_CODE(txndata->status)) { TSHttpTxnHookAdd(txnp, TS_HTTP_SEND_RESPONSE_HDR_HOOK, contp); TSHttpTxnReenable(txnp, TS_EVENT_HTTP_ERROR); return; } } /* Flag that we're too late to divert to an error response */ ib_tx_flags_set(txndata->tx, IB_TX_FCLIENTRES_STARTED); /* Normal execution. Add output filter to inspect response. */ TSHttpTxnHookAdd(txnp, TS_HTTP_RESPONSE_TRANSFORM_HOOK, txndata->out_data_cont); TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE); }
/** * Plugin for the IronBee ATS. * * Handles some ATS events. * * @param[in,out] contp Pointer to the continuation * @param[in,out] event Event from ATS * @param[in,out] edata Event data * * @returns status */ int ironbee_plugin(TSCont contp, TSEvent event, void *edata) { ib_status_t rc; TSCont mycont; TSHttpTxn txnp = (TSHttpTxn) edata; TSHttpSsn ssnp = (TSHttpSsn) edata; tsib_txn_ctx *txndata; tsib_ssn_ctx *ssndata; tsib_hdr_outcome status; TSMutex ts_mutex = NULL; TSDebug("ironbee", "Entering ironbee_plugin with %d", event); switch (event) { /* CONNECTION */ case TS_EVENT_HTTP_SSN_START: /* start of connection */ /* But we can't initialize conn stuff here, because there's * no API to get the connection stuff required by ironbee * at this point. So instead, intercept the first TXN * * what we can and must do: create a new contp whose * lifetime is our ssn */ ts_mutex = TSMutexCreate(); mycont = TSContCreate(ironbee_plugin, ts_mutex); TSHttpSsnHookAdd (ssnp, TS_HTTP_TXN_START_HOOK, mycont); ssndata = TSmalloc(sizeof(*ssndata)); memset(ssndata, 0, sizeof(*ssndata)); /* The only failure here is EALLOC, and if that happens * we're ****ed anyway */ rc = ib_lock_create_malloc(&(ssndata->mutex)); assert(rc == IB_OK); ssndata->contp = mycont; ssndata->ts_mutex = ts_mutex; TSContDataSet(mycont, ssndata); TSHttpSsnHookAdd (ssnp, TS_HTTP_SSN_CLOSE_HOOK, mycont); TSHttpSsnReenable (ssnp, TS_EVENT_HTTP_CONTINUE); break; case TS_EVENT_HTTP_TXN_START: { /* start of Request */ /* First req on a connection, we set up conn stuff */ ib_status_t rc; ib_engine_t *ib = NULL; ssndata = TSContDataGet(contp); ib_lock_lock(ssndata->mutex); if (ssndata->iconn == NULL) { rc = tsib_manager_engine_acquire(&ib); if (rc == IB_DECLINED) { /* OK, this means the manager is disabled deliberately, * but otherwise all's well. So this TXN * gets processed without intervention from Ironbee * and is invisble when our SSN_CLOSE hook runs. */ ib_lock_unlock(ssndata->mutex); TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE); TSDebug("ironbee", "Decline from engine manager"); break; } else if (rc != IB_OK) { TSError("[ironbee] Failed to acquire engine: %s", ib_status_to_string(rc)); goto noib_error; } if (ib != NULL) { rc = ib_conn_create(ib, &ssndata->iconn, contp); if (rc != IB_OK) { TSError("[ironbee] ib_conn_create: %s", ib_status_to_string(rc)); tsib_manager_engine_release(ib); goto noib_error; } /* In the normal case, release the engine when the * connection's memory pool is destroyed */ rc = ib_mm_register_cleanup(ssndata->iconn->mm, cleanup_ib_connection, ib); if (rc != IB_OK) { TSError("[ironbee] ib_mm_register_cleanup: %s", ib_status_to_string(rc)); tsib_manager_engine_release(ib); goto noib_error; } TSDebug("ironbee", "CONN CREATE: conn=%p", ssndata->iconn); ssndata->txnp = txnp; ssndata->txn_count = ssndata->closing = 0; rc = ironbee_conn_init(ssndata); if (rc != IB_OK) { TSError("[ironbee] ironbee_conn_init: %s", ib_status_to_string(rc)); goto noib_error; } TSContDataSet(contp, ssndata); TSDebug("ironbee", "ironbee_plugin: ib_state_notify_conn_opened()"); rc = ib_state_notify_conn_opened(ib, ssndata->iconn); if (rc != IB_OK) { TSError("[ironbee] Failed to notify connection opened: %s", ib_status_to_string(rc)); } } else { /* Use TSError where there's no ib or tx */ TSError("Ironbee: No ironbee engine!"); goto noib_error; } } /* create a txn cont (request ctx) and tx */ txndata = TSmalloc(sizeof(*txndata)); memset(txndata, 0, sizeof(*txndata)); txndata->ssn = ssndata; txndata->txnp = txnp; rc = ib_tx_create(&txndata->tx, ssndata->iconn, txndata); if (rc != IB_OK) { TSError("[ironbee] Failed to create tx: %d", rc); tsib_manager_engine_release(ib); TSfree(txndata); goto noib_error; } ++ssndata->txn_count; ib_lock_unlock(ssndata->mutex); ib_log_debug_tx(txndata->tx, "TX CREATE: conn=%p tx=%p id=%s txn_count=%d", ssndata->iconn, txndata->tx, txndata->tx->id, txndata->ssn->txn_count); mycont = TSContCreate(ironbee_plugin, ssndata->ts_mutex); TSContDataSet(mycont, txndata); TSHttpTxnHookAdd(txnp, TS_HTTP_TXN_CLOSE_HOOK, mycont); /* Hook to process responses */ TSHttpTxnHookAdd(txnp, TS_HTTP_READ_RESPONSE_HDR_HOOK, mycont); /* Hook to process requests */ TSHttpTxnHookAdd(txnp, TS_HTTP_READ_REQUEST_HDR_HOOK, mycont); /* Create continuations for input and output filtering * to give them txn lifetime. */ txndata->in_data_cont = TSTransformCreate(in_data_event, txnp); TSContDataSet(txndata->in_data_cont, txndata); txndata->out_data_cont = TSTransformCreate(out_data_event, txnp); TSContDataSet(txndata->out_data_cont, txndata); TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE); break; noib_error: ib_lock_unlock(ssndata->mutex); /* NULL txndata signals this to SEND_RESPONSE */ mycont = TSContCreate(ironbee_plugin, ssndata->ts_mutex); TSContDataSet(mycont, NULL); TSError("[ironbee] Internal error initialising for transaction"); TSHttpTxnHookAdd(txnp, TS_HTTP_SEND_RESPONSE_HDR_HOOK, mycont); TSHttpTxnReenable(txnp, TS_EVENT_HTTP_ERROR); break; } /* HTTP RESPONSE */ case TS_EVENT_HTTP_READ_RESPONSE_HDR: txndata = TSContDataGet(contp); if (txndata->tx == NULL) { TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE); break; } /* Feed ironbee the headers if not done already. */ if (!ib_flags_all(txndata->tx->flags, IB_TX_FRES_STARTED)) { status = process_hdr(txndata, txnp, &tsib_direction_server_resp); /* OK, if this was an HTTP 100 response, it's not the * response we're interested in. No headers have been * sent yet, and no data will be sent until we've * reached here again with the final response. */ if (status == HDR_HTTP_100) { TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE); break; } // FIXME: Need to know if this fails as it (I think) means // that the response did not come from the server and // that ironbee should ignore it. /* I've not seen a fail here. AFAICT if either the origin * isn't responding or we're responding from cache. we * never reach here in the first place. */ } /* If ironbee signalled an error while processing request body data, * this is the first opportunity to divert to an errordoc */ if (HTTP_CODE(txndata->status)) { ib_log_debug_tx(txndata->tx, "HTTP code %d contp=%p", txndata->status, contp); TSHttpTxnHookAdd(txnp, TS_HTTP_SEND_RESPONSE_HDR_HOOK, contp); TSHttpTxnReenable(txnp, TS_EVENT_HTTP_ERROR); break; } /* If we're not going to inspect response body data * we can bring forward notification of response-end * so we're in time to respond with an errordoc if Ironbee * wants to block in the response phase. * * This currently fails. However, that appears to be because I * can't unset IB_TX_FINSPECT_RESBODY with InspectionEngineOptions */ if (!ib_flags_all(txndata->tx->flags, IB_TX_FINSPECT_RESBODY)) { if (!ib_flags_all(txndata->tx->flags, IB_TX_FRES_STARTED) ) { ib_state_notify_response_started(txndata->tx->ib, txndata->tx, NULL); } if (!ib_flags_all(txndata->tx->flags, IB_TX_FRES_FINISHED) ) { ib_state_notify_response_finished(txndata->tx->ib, txndata->tx); } /* Test again for Ironbee telling us to block */ if (HTTP_CODE(txndata->status)) { TSHttpTxnHookAdd(txnp, TS_HTTP_SEND_RESPONSE_HDR_HOOK, contp); TSHttpTxnReenable(txnp, TS_EVENT_HTTP_ERROR); break; } } /* Flag that we're too late to divert to an error response */ ib_tx_flags_set(txndata->tx, IB_TX_FCLIENTRES_STARTED); /* Normal execution. Add output filter to inspect response. */ TSHttpTxnHookAdd(txnp, TS_HTTP_RESPONSE_TRANSFORM_HOOK, txndata->out_data_cont); TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE); break; /* Hook for processing response headers. */ case TS_EVENT_HTTP_SEND_RESPONSE_HDR: txndata = TSContDataGet(contp); if (txndata == NULL) { /* Ironbee is unavailable to help with our response. */ internal_error_response(txnp); /* This contp isn't going through the normal flow. */ TSContDestroy(contp); TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE); break; } /* If ironbee has sent us into an error response then * we came here in our error path, with nonzero status. */ if (txndata->status != 0) { error_response(txnp, txndata); } /* Feed ironbee the headers if not done already. */ if (!ib_flags_all(txndata->tx->flags, IB_TX_FRES_STARTED)) { if (process_hdr(txndata, txnp, &tsib_direction_client_resp) != HDR_OK) { /* I think this is a shouldn't happen event, and that * if it does we have an ironbee bug or misconfiguration. * Log an error to catch if it happens in practice. */ ib_log_error_tx(txndata->tx, "process_hdr returned error in send_response_hdr event"); } } /* If there is an ironbee-generated response body, notify ironbee. * * NOTE: I do not see anywhere else to put this as the error body is * just a buffer and not delivered via normal IO channels, so * the error body will never get caught by an event. */ if ((txndata->status != 0) && (txndata->err_body != NULL)) { const char *data = txndata->err_body; size_t data_length = txndata->err_body_len; ib_log_debug_tx(txndata->tx, "error_response: calling ib_state_notify_response_body_data() %s:%d", __FILE__, __LINE__); ib_state_notify_response_body_data(txndata->tx->ib, txndata->tx, data, data_length); } TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE); break; /* HTTP REQUEST */ case TS_EVENT_HTTP_READ_REQUEST_HDR: /* hook to examine output headers. They're not available yet */ TSHttpTxnHookAdd(txnp, TS_HTTP_PRE_REMAP_HOOK, contp); TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE); break; /* hook for processing incoming request/headers * The OS_DNS hook is an alternative here. */ case TS_EVENT_HTTP_PRE_REMAP: { int request_inspection_finished = 0; txndata = TSContDataGet(contp); assert ((txndata != NULL) && (txndata->tx != NULL)); status = process_hdr(txndata, txnp, &tsib_direction_client_req); if (HDR_OUTCOME_IS_HTTP_OR_ERROR(status, txndata)) { if (status == HDR_HTTP_STATUS) { ib_log_debug_tx(txndata->tx, "HTTP code %d contp=%p", txndata->status, contp); } else { /* Ironbee set a status we don't handle. * We returned EINVAL, but we also need housekeeping to * avoid a crash in modhtp and log something bad. */ ib_log_debug_tx(txndata->tx, "Internal error %d contp=%p", txndata->status, contp); /* Ugly hack: notifications to stop modhtp bombing out */ request_inspection_finished = 1; } } else { /* Other nonzero statuses not supported */ switch(status) { case HDR_OK: /* If we're not inspecting the Request body, * we can bring forward notification of end-request * so any header-only tests run on Request phase * can abort the tx before opening a backend connection. */ if (!ib_flags_all(txndata->tx->flags, IB_TX_FINSPECT_REQBODY)) { request_inspection_finished = 1; } break; /* All's well */ case HDR_HTTP_STATUS: // FIXME: should we take the initiative here and return 500? ib_log_error_tx(txndata->tx, "Internal error: ts-ironbee requested error but no error response set."); break; case HDR_HTTP_100: /* This can't actually happen with current Trafficserver * versions, as TS will generate a 400 error without * reference to us. But in case that changes in future ... */ ib_log_error_tx(txndata->tx, "No request headers found."); break; default: ib_log_error_tx(txndata->tx, "Unhandled state arose in handling request headers."); break; } } if (request_inspection_finished) { if (!ib_flags_all(txndata->tx->flags, IB_TX_FREQ_STARTED) ) { ib_state_notify_request_started(txndata->tx->ib, txndata->tx, NULL); } if (!ib_flags_all(txndata->tx->flags, IB_TX_FREQ_FINISHED) ) { ib_state_notify_request_finished(txndata->tx->ib, txndata->tx); } } else { /* hook an input filter to watch data */ TSHttpTxnHookAdd(txnp, TS_HTTP_REQUEST_TRANSFORM_HOOK, txndata->in_data_cont); } /* Flag that we can no longer prevent a request going to backend */ ib_tx_flags_set(txndata->tx, IB_TX_FSERVERREQ_STARTED); /* Check whether Ironbee told us to block the request. * This could now come not just from process_hdr, but also * from a brought-forward notification if we aren't inspecting * a request body and notified request_finished. */ if (HTTP_CODE(txndata->status)) { TSHttpTxnHookAdd(txnp, TS_HTTP_SEND_RESPONSE_HDR_HOOK, contp); TSHttpTxnReenable(txnp, TS_EVENT_HTTP_ERROR); } else { TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE); } break; } /* CLEANUP EVENTS */ case TS_EVENT_HTTP_TXN_CLOSE: { txndata = TSContDataGet(contp); TSContDestroy(txndata->out_data_cont); TSContDestroy(txndata->in_data_cont); TSContDataSet(contp, NULL); TSContDestroy(contp); if ( (txndata != NULL) && (txndata->tx != NULL) ) { ib_log_debug_tx(txndata->tx, "TXN Close: %p", (void *)contp); tsib_txn_ctx_destroy(txndata); } TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE); break; } case TS_EVENT_HTTP_SSN_CLOSE: TSDebug("ironbee", "SSN Close: %p", (void *)contp); tsib_ssn_ctx_destroy(TSContDataGet(contp)); tsib_manager_engine_cleanup(); TSHttpSsnReenable(ssnp, TS_EVENT_HTTP_CONTINUE); break; case TS_EVENT_MGMT_UPDATE: { TSDebug("ironbee", "Management update"); ib_status_t rc; rc = tsib_manager_engine_create(); if (rc != IB_OK) { TSError("[ironbee] Error creating new engine: %s", ib_status_to_string(rc)); } break; } /* if we get here we've got a bug */ default: TSError("[ironbee] *** Unhandled event %d in ironbee_plugin.", event); break; } return 0; }
/** * @brief Execute the rule. * * @param[in] ib Ironbee engine * @param[in] tx The transaction. * @param[in,out] User data. A @c pcre_rule_data_t. * @param[in] flags Operator instance flags * @param[in] field The field content. * @param[out] result The result. * @returns IB_OK most times. IB_EALLOC when a memory allocation error handles. */ static ib_status_t dfa_operator_execute(ib_engine_t *ib, ib_tx_t *tx, const ib_rule_t *rule, void *data, ib_flags_t flags, ib_field_t *field, ib_num_t *result) { IB_FTRACE_INIT(); assert(tx); assert(data); int matches; ib_status_t ib_rc; const int ovecsize = 3 * MATCH_MAX; dfa_rule_data_t *rule_data; int *ovector; const char* subject; size_t subject_len; const ib_bytestr_t* bytestr; dfa_workspace_t *dfa_workspace; int options; /* dfa exec options. */ ovector = (int *)malloc(ovecsize*sizeof(*ovector)); if (ovector==NULL) { IB_FTRACE_RET_STATUS(IB_EALLOC); } /* Pull out the rule data. */ rule_data = (dfa_rule_data_t *)data; if (field->type == IB_FTYPE_NULSTR) { ib_rc = ib_field_value(field, ib_ftype_nulstr_out(&subject)); if (ib_rc != IB_OK) { free(ovector); IB_FTRACE_RET_STATUS(ib_rc); } subject_len = strlen(subject); } else if (field->type == IB_FTYPE_BYTESTR) { ib_rc = ib_field_value(field, ib_ftype_bytestr_out(&bytestr)); if (ib_rc != IB_OK) { free(ovector); IB_FTRACE_RET_STATUS(ib_rc); } subject_len = ib_bytestr_length(bytestr); subject = (const char *) ib_bytestr_const_ptr(bytestr); } else { free(ovector); IB_FTRACE_RET_STATUS(IB_EINVAL); } /* Debug block. Escapes a string and prints it to the log. * Memory is freed. */ if (ib_log_get_level(ib) >= 9) { /* Worst case, we can have a string that is 4x larger. * Consider if a string of 0xF7 is passed. That single character * will expand to a string of 4 printed characters +1 for the \0 * character. */ char *debug_str = ib_util_hex_escape(subject, subject_len); if ( debug_str != NULL ) { ib_log_debug3_tx(tx, "Matching against: %s", debug_str); free( debug_str ); } } /* Get the per-tx workspace data for this rule data id. */ ib_rc = get_dfa_tx_data(tx, rule_data->id, &dfa_workspace); if (ib_rc == IB_ENOENT) { options = PCRE_PARTIAL_SOFT; ib_rc = alloc_dfa_tx_data(tx, rule_data->id, &dfa_workspace); if (ib_rc != IB_OK) { free(ovector); ib_log_error_tx(tx, "Unexpected error creating tx storage " "for dfa operator %s", rule_data->id); IB_FTRACE_RET_STATUS(ib_rc); } ib_log_debug_tx(tx, "Created DFA workspace at %p for id %s.", dfa_workspace, rule_data->id); } else if (ib_rc == IB_OK) { options = PCRE_PARTIAL_SOFT | PCRE_DFA_RESTART; ib_log_debug_tx(tx, "Reusing existing DFA workspace %p for id %s.", dfa_workspace, rule_data->id); } else { free(ovector); ib_log_error_tx(tx, "Unexpected error fetching dfa data " "for dfa operator %s", rule_data->id); IB_FTRACE_RET_STATUS(ib_rc); } /* Actually do the DFA match. */ matches = pcre_dfa_exec(rule_data->cpatt, rule_data->edata, subject, subject_len, 0, /* Starting offset. */ options, ovector, ovecsize, dfa_workspace->workspace, dfa_workspace->wscount); if (matches >= 0) { ib_rc = IB_OK; *result = 1; } else if (matches == PCRE_ERROR_PARTIAL) { ib_log_debug2_tx(tx, "Partial match found, but not a full match."); ib_rc = IB_OK; *result = 0; } else if (matches == PCRE_ERROR_NOMATCH) { if (ib_log_get_level(ib) >= 7) { char* tmp_c = malloc(subject_len+1); memcpy(tmp_c, subject, subject_len); tmp_c[subject_len] = '\0'; /* No match. Return false to the caller (*result = 0). */ ib_log_debug2_tx(tx, "No match for [%s] using pattern [%s].", tmp_c, rule_data->patt); free(tmp_c); } ib_rc = IB_OK; *result = 0; } else { /* Some other error occurred. Set the status to false and report the error. */ ib_rc = IB_EUNKNOWN; *result = 0; } free(ovector); IB_FTRACE_RET_STATUS(ib_rc); }
static ib_status_t sqli_op_execute( ib_tx_t *tx, const ib_field_t *field, ib_field_t *capture, ib_num_t *result, void *instance_data, void *cbdata ) { assert(tx != NULL); assert(field != NULL); assert(result != NULL); const sqli_fingerprint_set_t *ps = (const sqli_fingerprint_set_t *)instance_data; sfilter sf; ib_bytestr_t *bs; ib_status_t rc; sqli_callback_data_t callback_data; *result = 0; /* Currently only bytestring types are supported. * Other types will just get passed through. */ if (field->type != IB_FTYPE_BYTESTR) { return IB_OK; } rc = ib_field_value(field, ib_ftype_bytestr_mutable_out(&bs)); if (rc != IB_OK) { return rc; } /* Run through libinjection. */ libinjection_sqli_init( &sf, (const char *)ib_bytestr_const_ptr(bs), ib_bytestr_length(bs), FLAG_NONE ); callback_data.confidence = 0; callback_data.fingerprint_set = NULL; if (ps != NULL) { callback_data.fingerprint_set = ps; libinjection_sqli_callback(&sf, sqli_lookup_word, (void *)&callback_data); } if (libinjection_is_sqli(&sf)) { ib_log_debug_tx(tx, "Matched SQLi fingerprint: %s", sf.fingerprint); *result = 1; } if (*result == 1 && capture != NULL) { { ib_field_t *fingerprint_field; size_t fingerprint_length = strlen(sf.fingerprint); const uint8_t *fingerprint; fingerprint = ib_mm_memdup( tx->mm, sf.fingerprint, fingerprint_length ); if (fingerprint == NULL) { return IB_EALLOC; } rc = ib_field_create_bytestr_alias( &fingerprint_field, tx->mm, IB_S2SL("fingerprint"), fingerprint, fingerprint_length ); if (rc != IB_OK) { return rc; } rc = ib_field_list_add(capture, fingerprint_field); if (rc != IB_OK) { return rc; } } { ib_field_t *confidence_field; rc = ib_field_create( &confidence_field, tx->mm, IB_S2SL("confidence"), IB_FTYPE_NUM, ib_ftype_num_in(&callback_data.confidence) ); if (rc != IB_OK) { return rc; } rc = ib_field_list_add(capture, confidence_field); if (rc != IB_OK) { return rc; } } } return IB_OK; }