const std::string &ResultStr(ib_flags_t result, std::string &s) const { int n = 0; if (result == IB_STRFLAG_NONE) { s = "<None>"; return s; } s = "<"; if (ib_flags_all(result, IB_STRFLAG_MODIFIED)) { if (n++ > 0) { s += ","; } s += "MODIFIED"; } if (ib_flags_all(result, IB_STRFLAG_NEWBUF)) { if (n++ > 0) { s += ","; } s += "NEWBUF"; } if (ib_flags_all(result, IB_STRFLAG_ALIAS)) { if (n++ > 0) { s += ","; } s += "ALIAS"; } s += ">"; return s; }
static void tx_finish(ib_tx_t *tx) { if (!ib_flags_all(tx->flags, IB_TX_FREQ_FINISHED) ) { ib_state_notify_request_finished(tx->ib, tx); } if (!ib_flags_all(tx->flags, IB_TX_FRES_FINISHED) ) { ib_state_notify_response_finished(tx->ib, tx); } if (!ib_flags_all(tx->flags, IB_TX_FPOSTPROCESS)) { ib_state_notify_postprocess(tx->ib, tx); } if (!ib_flags_all(tx->flags, IB_TX_FLOGGING)) { ib_state_notify_logging(tx->ib, tx); } }
void CheckResult(int lineno, const BaseTestDatum &test, ib_status_t rc, ib_flags_t exresult, ib_flags_t result) { std::string s1, s2; EXPECT_EQ(IB_OK, rc) << "Line " << lineno << ": " << Stringize(test) << " returned " << rc; if (rc != IB_OK) { return; } // NEWBUF and ALIAS result flags should never both be set bool both = ib_flags_all(result, IB_STRFLAG_NEWBUF|IB_STRFLAG_ALIAS); ASSERT_FALSE(both) << "Line " << lineno << ": " << Stringize(test) << " both NEWBUF and ALIAS result flags are set!" << ResultStr(result, s1); // Build the expected output EXPECT_EQ(exresult, result) << "Line " << lineno << ": " << Stringize(test) << " expected result=" << ResultStr(exresult, s1) << exresult << " actual=" << ResultStr(result, s2) << result; }
static ib_status_t ib_errbody_callback( ib_tx_t *tx, const char *data, size_t dlen, void *cbdata) { uint8_t *err_body; tsib_txn_ctx *txndata = (tsib_txn_ctx *)tx->sctx; /* Handle No Data as zero length data. */ if (data == NULL || dlen == 0) { return IB_OK; } /* We can't return an error after the response has started */ if (ib_flags_all(tx->flags, IB_TX_FCLIENTRES_STARTED)) { return IB_DECLINED; } /* This alloc will be freed within TSHttpTxnErrorBodySet * so we have to use TSmalloc for it. */ err_body = TSmalloc(dlen); if (err_body == NULL) { return IB_EALLOC; } txndata->err_body = memcpy(err_body, data, dlen); txndata->err_body_len = dlen; return IB_OK; }
/* ASCII lowercase function (string version); See string.h */ ib_status_t ib_strlower(ib_strop_t op, ib_mpool_t *mp, char *str_in, char **str_out, ib_flags_t *result) { IB_FTRACE_INIT(); size_t len; ib_status_t rc = IB_OK; char *out = NULL; assert(mp != NULL); assert(str_in != NULL); assert(str_out != NULL); assert(result != NULL); len = strlen(str_in); switch(op) { case IB_STROP_INPLACE: out = str_in; rc = inplace(IB_STRFLAG_ALIAS, (uint8_t*)str_in, len, result); break; case IB_STROP_COPY: out = ib_mpool_strdup(mp, str_in); if (out == NULL) { IB_FTRACE_RET_STATUS(IB_EALLOC); } rc = inplace(IB_STRFLAG_NEWBUF, (uint8_t*)out, len, result); break; case IB_STROP_COW: { #if ((__GNUC__==4) && (__GNUC_MINOR__<3)) uint8_t *uint8ptr; rc = copy_on_write(mp, (uint8_t *)str_in, len+1, &uint8ptr, &len, result); out = (char *)uint8ptr; #else rc = copy_on_write(mp, (uint8_t *)str_in, len+1, (uint8_t **)&out, &len, result); #endif break; } default: IB_FTRACE_RET_STATUS(IB_EINVAL); } if (rc == IB_OK) { if (ib_flags_all(*result, IB_STRFLAG_MODIFIED)) { *(out+len) = '\0'; } *str_out = out; } IB_FTRACE_RET_STATUS(rc); }
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); }
/** * 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; }
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); }
static ib_status_t ib_errhdr_callback( ib_tx_t *tx, 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_list *hdrs; /* We can't return an error after the response has started */ if (ib_flags_all(tx->flags, IB_TX_FCLIENTRES_STARTED)) return IB_DECLINED; if (!name || !value) return IB_EINVAL; hdrs = ib_mm_alloc(tx->mm, sizeof(*hdrs)); hdrs->hdr = ib_mm_memdup_to_str(tx->mm, name, name_length); hdrs->value = ib_mm_memdup_to_str(tx->mm, value, value_length); hdrs->next = txndata->err_hdrs; txndata->err_hdrs = hdrs; 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; }
/** * Determine buffering policy from config settings * * @param[in] ibd - the filter descriptor * @param[in] tx - the transaction */ static void buffer_init(ibd_ctx *ibd, ib_tx_t *tx) { ib_core_cfg_t *corecfg = NULL; ib_status_t rc; tsib_filter_ctx *fctx = ibd->data; ib_server_direction_t dir = ibd->ibd->dir; if (tx == NULL) { fctx->buffering = IOBUF_NOBUF; return; } rc = ib_core_context_config(ib_context_main(tx->ib), &corecfg); if (rc != IB_OK) { ib_log_error_tx(tx, "Error determining buffering configuration."); } else { if (dir == IBD_REQ) { fctx->buffering = (corecfg->buffer_req == 0) ? IOBUF_NOBUF : (corecfg->limits.request_body_buffer_limit < 0) ? IOBUF_BUFFER_ALL : (corecfg->limits.request_body_buffer_limit_action == IB_BUFFER_LIMIT_ACTION_FLUSH_ALL) ? IOBUF_BUFFER_FLUSHALL : IOBUF_BUFFER_FLUSHPART; fctx->buf_limit = (size_t) corecfg->limits.request_body_buffer_limit; } else { fctx->buffering = (corecfg->buffer_res == 0) ? IOBUF_NOBUF : (corecfg->limits.response_body_buffer_limit < 0) ? IOBUF_BUFFER_ALL : (corecfg->limits.response_body_buffer_limit_action == IB_BUFFER_LIMIT_ACTION_FLUSH_ALL) ? IOBUF_BUFFER_FLUSHALL : IOBUF_BUFFER_FLUSHPART; fctx->buf_limit = (size_t) corecfg->limits.response_body_buffer_limit; } } /* Override buffering based on flags */ if (fctx->buffering != IOBUF_NOBUF) { if (dir == IBD_REQ) { if (ib_flags_any(tx->flags, IB_TX_FALLOW_ALL | IB_TX_FALLOW_REQUEST) || (!ib_flags_all(tx->flags, IB_TX_FINSPECT_REQBODY) && !ib_flags_all(tx->flags, IB_TX_FINSPECT_REQHDR)) ) { fctx->buffering = IOBUF_NOBUF; ib_log_debug2_tx(tx, "\tDisable request buffering"); } } else if (dir == IBD_RESP) { if (ib_flags_any(tx->flags, IB_TX_FALLOW_ALL) || (!ib_flags_all(tx->flags, IB_TX_FINSPECT_RESBODY) && !ib_flags_all(tx->flags, IB_TX_FINSPECT_RESHDR)) ) { fctx->buffering = IOBUF_NOBUF; ib_log_debug2_tx(tx, "\tDisable response buffering"); } } } }
/** * String modification transformation core * * @param[in] ib IronBee engine * @param[in] mp Memory pool to use for allocations. * @param[in] str_fn NUL-terminated string transformation function * @param[in] ex_fn EX (string/length) transformation function * @param[in] fin Input field. * @param[out] fout Output field. * @param[out] pflags Transformation flags. * * @returns IB_OK if successful. */ static ib_status_t tfn_strmod(ib_engine_t *ib, ib_mpool_t *mp, ib_strmod_fn_t str_fn, ib_strmod_ex_fn_t ex_fn, const ib_field_t *fin, ib_field_t **fout, ib_flags_t *pflags) { ib_status_t rc; ib_flags_t result; assert(ib != NULL); assert(mp != NULL); assert(str_fn != NULL); assert(ex_fn != NULL); assert(fin != NULL); assert(fout != NULL); assert(pflags != NULL); /* Initialize the output field pointer */ *fout = NULL; switch(fin->type) { case IB_FTYPE_NULSTR : { const char *in; char *out; rc = ib_field_value(fin, ib_ftype_nulstr_out(&in)); if (rc != IB_OK) { return rc; } if (in == NULL) { return IB_EINVAL; } rc = str_fn(IB_STROP_COW, mp, (char *)in, &out, &result); if (rc != IB_OK) { return rc; } rc = ib_field_create(fout, mp, fin->name, fin->nlen, IB_FTYPE_NULSTR, ib_ftype_nulstr_in(out)); if (rc != IB_OK) { return rc; } break; } case IB_FTYPE_BYTESTR: { const ib_bytestr_t *bs; const uint8_t *din; uint8_t *dout; size_t dlen; rc = ib_field_value(fin, ib_ftype_bytestr_out(&bs)); if (rc != IB_OK) { return rc; } if (bs == NULL) { return IB_EINVAL; } din = ib_bytestr_const_ptr(bs); if (din == NULL) { return IB_EINVAL; } dlen = ib_bytestr_length(bs); rc = ex_fn(IB_STROP_COW, mp, (uint8_t *)din, dlen, &dout, &dlen, &result); if (rc != IB_OK) { return rc; } rc = ib_field_create_bytestr_alias(fout, mp, fin->name, fin->nlen, dout, dlen); if (rc != IB_OK) { return rc; } break; } default: return IB_EINVAL; } /* switch(fin->type) */ /* Check the flags */ if (ib_flags_all(result, IB_STRFLAG_MODIFIED)) { *pflags = IB_TFN_FMODIFIED; } else { *pflags = IB_TFN_NONE; } return IB_OK; }
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); } }
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); }
/** * Handle a data event from ATS. * * Handles all data events from ATS, uses process_data to handle the data * itself. * * @param[in,out] contp Pointer to the continuation * @param[in,out] event Event from ATS * @param[in,out] ibd unknown * * @returns status */ static int data_event(TSCont contp, TSEvent event, ibd_ctx *ibd) { /* Check to see if the transformation has been closed by a call to * TSVConnClose. */ tsib_txn_ctx *txndata = TSContDataGet(contp); ib_log_debug2_tx(txndata->tx, "Entering out_data for %s", ibd->ibd->dir_label); if (TSVConnClosedGet(contp)) { ib_log_debug2_tx(txndata->tx, "\tVConn is closed"); return 0; } switch (event) { case TS_EVENT_ERROR: { TSVIO input_vio; ib_log_debug2_tx(txndata->tx, "\tEvent is TS_EVENT_ERROR"); /* Get the write VIO for the write operation that was * performed on ourself. This VIO contains the continuation of * our parent transformation. This is the input VIO. */ input_vio = TSVConnWriteVIOGet(contp); /* Call back the write VIO continuation to let it know that we * have completed the write operation. */ TSContCall(TSVIOContGet(input_vio), TS_EVENT_ERROR, input_vio); } break; case TS_EVENT_VCONN_WRITE_COMPLETE: ib_log_debug2_tx(txndata->tx, "\tEvent is TS_EVENT_VCONN_WRITE_COMPLETE"); /* When our output connection says that it has finished * reading all the txndata we've written to it then we should * shutdown the write portion of its connection to * indicate that we don't want to hear about it anymore. */ TSVConnShutdown(TSTransformOutputVConnGet(contp), 0, 1); if (ibd->ibd->dir == IBD_REQ) { if (!ib_flags_all(txndata->tx->flags, IB_TX_FREQ_FINISHED)) { ib_log_debug2_tx(txndata->tx, "data_event: calling ib_state_notify_request_finished()"); (*ibd->ibd->ib_notify_end)(txndata->tx->ib, txndata->tx); } } else { if (!ib_flags_all(txndata->tx->flags, IB_TX_FRES_FINISHED)) { ib_log_debug2_tx(txndata->tx, "data_event: calling ib_state_notify_response_finished()"); (*ibd->ibd->ib_notify_end)(txndata->tx->ib, txndata->tx); } } if ( (ibd->ibd->ib_notify_post != NULL) && (!ib_flags_all(txndata->tx->flags, IB_TX_FPOSTPROCESS)) ) { (*ibd->ibd->ib_notify_post)(txndata->tx->ib, txndata->tx); } if ( (ibd->ibd->ib_notify_log != NULL) && (!ib_flags_all(txndata->tx->flags, IB_TX_FLOGGING)) ) { (*ibd->ibd->ib_notify_log)(txndata->tx->ib, txndata->tx); } break; case TS_EVENT_VCONN_WRITE_READY: ib_log_debug2_tx(txndata->tx, "\tEvent is TS_EVENT_VCONN_WRITE_READY"); /* fall through */ default: ib_log_debug2_tx(txndata->tx, "\t(event is %d)", event); /* If we get a WRITE_READY event or any other type of * event (sent, perhaps, because we were re-enabled) then * we'll attempt to transform more data. */ process_data(contp, ibd); 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 pcre_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(ib!=NULL); assert(tx!=NULL); assert(tx->dpi!=NULL); assert(data!=NULL); int matches; ib_status_t ib_rc; const int ovecsize = 3 * MATCH_MAX; int *ovector = (int *)malloc(ovecsize*sizeof(*ovector)); const char* subject = NULL; size_t subject_len = 0; const ib_bytestr_t* bytestr; pcre_rule_data_t *rule_data = (pcre_rule_data_t *)data; pcre_extra *edata = NULL; #ifdef PCRE_JIT_STACK pcre_jit_stack *jit_stack = pcre_jit_stack_alloc(PCRE_JIT_MIN_STACK_SZ, PCRE_JIT_MAX_STACK_SZ); #endif if (ovector==NULL) { IB_FTRACE_RET_STATUS(IB_EALLOC); } 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); } if (subject != NULL) { 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); } if (bytestr != NULL) { subject_len = ib_bytestr_length(bytestr); subject = (const char *) ib_bytestr_const_ptr(bytestr); } } else { free(ovector); IB_FTRACE_RET_STATUS(IB_EINVAL); } if (subject == NULL) { subject = ""; } /* 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 ); } } #ifdef PCRE_JIT_STACK /* Log if we expected jit, but did not get it. */ if (rule_data->is_jit && jit_stack == NULL) { ib_log_debug(ib, "Failed to allocate a jit stack for a jit-compiled rule. " "Not using jit for this call."); edata = NULL; } /* If the study data is NULL or size zero, don't use it. */ else if (rule_data->edata == NULL || rule_data->study_data_sz <= 0) { edata = NULL; } /* Only if we get here do we use the study data (edata) in the rule_data. */ else { edata = rule_data->edata; pcre_assign_jit_stack(rule_data->edata, NULL, jit_stack); } #endif matches = pcre_exec(rule_data->cpatt, edata, subject, subject_len, 0, /* Starting offset. */ 0, /* Options. */ ovector, ovecsize); #ifdef PCRE_JIT_STACK if (jit_stack != NULL) { pcre_jit_stack_free(jit_stack); } #endif if (matches > 0) { if (ib_flags_all(rule->flags, IB_RULE_FLAG_CAPTURE) == true) { pcre_set_matches(ib, tx, ovector, matches, subject); } ib_rc = IB_OK; *result = 1; } 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); }
/** * 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; }