diff --git a/src/proto_htx.c b/src/proto_htx.c index 83a2c33a4..2b2fcf6d0 100644 --- a/src/proto_htx.c +++ b/src/proto_htx.c @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -27,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -44,6 +46,9 @@ static size_t htx_fmt_res_line(const union h1_sl sl, char *str, size_t len); static void htx_debug_stline(const char *dir, struct stream *s, const union h1_sl sl); static void htx_debug_hdr(const char *dir, struct stream *s, const struct ist n, const struct ist v); +static enum rule_result htx_req_get_intercept_rule(struct proxy *px, struct list *rules, struct stream *s, int *deny_status); +static enum rule_result htx_res_get_intercept_rule(struct proxy *px, struct list *rules, struct stream *s); + /* This stream analyser waits for a complete HTTP request. It returns 1 if the * processing can continue on next analysers, or zero if it either needs more * data or wants to immediately abort the request (eg: timeout, error, ...). It @@ -2708,6 +2713,747 @@ void htx_res_set_status(unsigned int status, const char *reason, struct stream * http_replace_res_reason(htx, ist2(reason, strlen(reason))); } +/* Executes the http-request rules for stream , proxy and + * transaction . Returns the verdict of the first rule that prevents + * further processing of the request (auth, deny, ...), and defaults to + * HTTP_RULE_RES_STOP if it executed all rules or stopped on an allow, or + * HTTP_RULE_RES_CONT if the last rule was reached. It may set the TX_CLTARPIT + * on txn->flags if it encounters a tarpit rule. If is not NULL + * and a deny/tarpit rule is matched, it will be filled with this rule's deny + * status. + */ +static enum rule_result htx_req_get_intercept_rule(struct proxy *px, struct list *rules, + struct stream *s, int *deny_status) +{ + struct session *sess = strm_sess(s); + struct http_txn *txn = s->txn; + struct htx *htx; + struct connection *cli_conn; + struct act_rule *rule; + struct http_hdr_ctx ctx; + const char *auth_realm; + struct buffer *early_hints = NULL; + enum rule_result rule_ret = HTTP_RULE_RES_CONT; + int act_flags = 0; + + htx = htx_from_buf(&s->req.buf); + + /* If "the current_rule_list" match the executed rule list, we are in + * resume condition. If a resume is needed it is always in the action + * and never in the ACL or converters. In this case, we initialise the + * current rule, and go to the action execution point. + */ + if (s->current_rule) { + rule = s->current_rule; + s->current_rule = NULL; + if (s->current_rule_list == rules) + goto resume_execution; + } + s->current_rule_list = rules; + + list_for_each_entry(rule, rules, list) { + /* check optional condition */ + if (rule->cond) { + int ret; + + ret = acl_exec_cond(rule->cond, px, sess, s, SMP_OPT_DIR_REQ|SMP_OPT_FINAL); + ret = acl_pass(ret); + + if (rule->cond->pol == ACL_COND_UNLESS) + ret = !ret; + + if (!ret) /* condition not matched */ + continue; + } + + act_flags |= ACT_FLAG_FIRST; + resume_execution: + switch (rule->action) { + case ACT_ACTION_ALLOW: + rule_ret = HTTP_RULE_RES_STOP; + goto end; + + case ACT_ACTION_DENY: + if (deny_status) + *deny_status = rule->deny_status; + rule_ret = HTTP_RULE_RES_DENY; + goto end; + + case ACT_HTTP_REQ_TARPIT: + txn->flags |= TX_CLTARPIT; + if (deny_status) + *deny_status = rule->deny_status; + rule_ret = HTTP_RULE_RES_DENY; + goto end; + + case ACT_HTTP_REQ_AUTH: + /* Be sure to sned any pending HTTP 103 response first */ + if (early_hints) { + htx_send_early_hints(s, early_hints); + free_trash_chunk(early_hints); + early_hints = NULL; + } + /* Auth might be performed on regular http-req rules as well as on stats */ + auth_realm = rule->arg.auth.realm; + if (!auth_realm) { + if (px->uri_auth && rules == &px->uri_auth->http_req_rules) + auth_realm = STATS_DEFAULT_REALM; + else + auth_realm = px->id; + } + /* send 401/407 depending on whether we use a proxy or not. We still + * count one error, because normal browsing won't significantly + * increase the counter but brute force attempts will. + */ + chunk_printf(&trash, (txn->flags & TX_USE_PX_CONN) ? HTTP_407_fmt : HTTP_401_fmt, auth_realm); + txn->status = (txn->flags & TX_USE_PX_CONN) ? 407 : 401; + htx_reply_and_close(s, txn->status, &trash); + stream_inc_http_err_ctr(s); + rule_ret = HTTP_RULE_RES_ABRT; + goto end; + + case ACT_HTTP_REDIR: + /* Be sure to sned any pending HTTP 103 response first */ + if (early_hints) { + htx_send_early_hints(s, early_hints); + free_trash_chunk(early_hints); + early_hints = NULL; + } + rule_ret = HTTP_RULE_RES_DONE; + if (!htx_apply_redirect_rule(rule->arg.redir, s, txn)) + rule_ret = HTTP_RULE_RES_BADREQ; + goto end; + + case ACT_HTTP_SET_NICE: + s->task->nice = rule->arg.nice; + break; + + case ACT_HTTP_SET_TOS: + if ((cli_conn = objt_conn(sess->origin)) && conn_ctrl_ready(cli_conn)) + inet_set_tos(cli_conn->handle.fd, &cli_conn->addr.from, rule->arg.tos); + break; + + case ACT_HTTP_SET_MARK: +#ifdef SO_MARK + if ((cli_conn = objt_conn(sess->origin)) && conn_ctrl_ready(cli_conn)) + setsockopt(cli_conn->handle.fd, SOL_SOCKET, SO_MARK, &rule->arg.mark, sizeof(rule->arg.mark)); +#endif + break; + + case ACT_HTTP_SET_LOGL: + s->logs.level = rule->arg.loglevel; + break; + + case ACT_HTTP_REPLACE_HDR: + case ACT_HTTP_REPLACE_VAL: + if (htx_transform_header(s, &s->req, htx, + ist2(rule->arg.hdr_add.name, rule->arg.hdr_add.name_len), + &rule->arg.hdr_add.fmt, + &rule->arg.hdr_add.re, rule->action)) { + rule_ret = HTTP_RULE_RES_BADREQ; + goto end; + } + break; + + case ACT_HTTP_DEL_HDR: + /* remove all occurrences of the header */ + ctx.blk = NULL; + while (http_find_header(htx, ist2(rule->arg.hdr_add.name, rule->arg.hdr_add.name_len), &ctx, 1)) + http_remove_header(htx, &ctx); + break; + + case ACT_HTTP_SET_HDR: + case ACT_HTTP_ADD_HDR: { + /* The scope of the trash buffer must be limited to this function. The + * build_logline() function can execute a lot of other function which + * can use the trash buffer. So for limiting the scope of this global + * buffer, we build first the header value using build_logline, and + * after we store the header name. + */ + struct buffer *replace; + struct ist n, v; + + replace = alloc_trash_chunk(); + if (!replace) { + rule_ret = HTTP_RULE_RES_BADREQ; + goto end; + } + + replace->data = build_logline(s, replace->area, replace->size, &rule->arg.hdr_add.fmt); + n = ist2(rule->arg.hdr_add.name, rule->arg.hdr_add.name_len); + v = ist2(replace->area, replace->data); + + if (rule->action == ACT_HTTP_SET_HDR) { + /* remove all occurrences of the header */ + ctx.blk = NULL; + while (http_find_header(htx, ist2(rule->arg.hdr_add.name, rule->arg.hdr_add.name_len), &ctx, 1)) + http_remove_header(htx, &ctx); + } + + if (!http_add_header(htx, n, v)) { + static unsigned char rate_limit = 0; + + if ((rate_limit++ & 255) == 0) { + send_log(px, LOG_WARNING, "Proxy %s failed to add or set the request header '%.*s' for request #%u. You might need to increase tune.maxrewrite.", px->id, (int)n.len, n.ptr, s->uniq_id); + } + + HA_ATOMIC_ADD(&sess->fe->fe_counters.failed_rewrites, 1); + if (sess->fe != s->be) + HA_ATOMIC_ADD(&s->be->be_counters.failed_rewrites, 1); + if (sess->listener->counters) + HA_ATOMIC_ADD(&sess->listener->counters->failed_rewrites, 1); + } + free_trash_chunk(replace); + break; + } + + case ACT_HTTP_DEL_ACL: + case ACT_HTTP_DEL_MAP: { + struct pat_ref *ref; + struct buffer *key; + + /* collect reference */ + ref = pat_ref_lookup(rule->arg.map.ref); + if (!ref) + continue; + + /* allocate key */ + key = alloc_trash_chunk(); + if (!key) { + rule_ret = HTTP_RULE_RES_BADREQ; + goto end; + } + + /* collect key */ + key->data = build_logline(s, key->area, key->size, &rule->arg.map.key); + key->area[key->data] = '\0'; + + /* perform update */ + /* returned code: 1=ok, 0=ko */ + HA_SPIN_LOCK(PATREF_LOCK, &ref->lock); + pat_ref_delete(ref, key->area); + HA_SPIN_UNLOCK(PATREF_LOCK, &ref->lock); + + free_trash_chunk(key); + break; + } + + case ACT_HTTP_ADD_ACL: { + struct pat_ref *ref; + struct buffer *key; + + /* collect reference */ + ref = pat_ref_lookup(rule->arg.map.ref); + if (!ref) + continue; + + /* allocate key */ + key = alloc_trash_chunk(); + if (!key) { + rule_ret = HTTP_RULE_RES_BADREQ; + goto end; + } + + /* collect key */ + key->data = build_logline(s, key->area, key->size, &rule->arg.map.key); + key->area[key->data] = '\0'; + + /* perform update */ + /* add entry only if it does not already exist */ + HA_SPIN_LOCK(PATREF_LOCK, &ref->lock); + if (pat_ref_find_elt(ref, key->area) == NULL) + pat_ref_add(ref, key->area, NULL, NULL); + HA_SPIN_UNLOCK(PATREF_LOCK, &ref->lock); + + free_trash_chunk(key); + break; + } + + case ACT_HTTP_SET_MAP: { + struct pat_ref *ref; + struct buffer *key, *value; + + /* collect reference */ + ref = pat_ref_lookup(rule->arg.map.ref); + if (!ref) + continue; + + /* allocate key */ + key = alloc_trash_chunk(); + if (!key) { + rule_ret = HTTP_RULE_RES_BADREQ; + goto end; + } + + /* allocate value */ + value = alloc_trash_chunk(); + if (!value) { + free_trash_chunk(key); + rule_ret = HTTP_RULE_RES_BADREQ; + goto end; + } + + /* collect key */ + key->data = build_logline(s, key->area, key->size, &rule->arg.map.key); + key->area[key->data] = '\0'; + + /* collect value */ + value->data = build_logline(s, value->area, value->size, &rule->arg.map.value); + value->area[value->data] = '\0'; + + /* perform update */ + if (pat_ref_find_elt(ref, key->area) != NULL) + /* update entry if it exists */ + pat_ref_set(ref, key->area, value->area, NULL); + else + /* insert a new entry */ + pat_ref_add(ref, key->area, value->area, NULL); + + free_trash_chunk(key); + free_trash_chunk(value); + break; + } + + case ACT_HTTP_EARLY_HINT: + if (!(txn->req.flags & HTTP_MSGF_VER_11)) + break; + early_hints = htx_apply_early_hint_rule(s, early_hints, + rule->arg.early_hint.name, + rule->arg.early_hint.name_len, + &rule->arg.early_hint.fmt); + if (!early_hints) { + rule_ret = HTTP_RULE_RES_DONE; + goto end; + } + break; + + case ACT_CUSTOM: + if ((s->req.flags & CF_READ_ERROR) || + ((s->req.flags & (CF_SHUTR|CF_READ_NULL)) && + !(s->si[0].flags & SI_FL_CLEAN_ABRT) && + (px->options & PR_O_ABRT_CLOSE))) + act_flags |= ACT_FLAG_FINAL; + + switch (rule->action_ptr(rule, px, s->sess, s, act_flags)) { + case ACT_RET_ERR: + case ACT_RET_CONT: + break; + case ACT_RET_STOP: + rule_ret = HTTP_RULE_RES_DONE; + goto end; + case ACT_RET_YIELD: + s->current_rule = rule; + rule_ret = HTTP_RULE_RES_YIELD; + goto end; + } + break; + + case ACT_ACTION_TRK_SC0 ... ACT_ACTION_TRK_SCMAX: + /* Note: only the first valid tracking parameter of each + * applies. + */ + + if (stkctr_entry(&s->stkctr[trk_idx(rule->action)]) == NULL) { + struct stktable *t; + struct stksess *ts; + struct stktable_key *key; + void *ptr1, *ptr2; + + t = rule->arg.trk_ctr.table.t; + key = stktable_fetch_key(t, s->be, sess, s, SMP_OPT_DIR_REQ | SMP_OPT_FINAL, + rule->arg.trk_ctr.expr, NULL); + + if (key && (ts = stktable_get_entry(t, key))) { + stream_track_stkctr(&s->stkctr[trk_idx(rule->action)], t, ts); + + /* let's count a new HTTP request as it's the first time we do it */ + ptr1 = stktable_data_ptr(t, ts, STKTABLE_DT_HTTP_REQ_CNT); + ptr2 = stktable_data_ptr(t, ts, STKTABLE_DT_HTTP_REQ_RATE); + if (ptr1 || ptr2) { + HA_RWLOCK_WRLOCK(STK_SESS_LOCK, &ts->lock); + + if (ptr1) + stktable_data_cast(ptr1, http_req_cnt)++; + + if (ptr2) + update_freq_ctr_period(&stktable_data_cast(ptr2, http_req_rate), + t->data_arg[STKTABLE_DT_HTTP_REQ_RATE].u, 1); + + HA_RWLOCK_WRUNLOCK(STK_SESS_LOCK, &ts->lock); + + /* If data was modified, we need to touch to re-schedule sync */ + stktable_touch_local(t, ts, 0); + } + + stkctr_set_flags(&s->stkctr[trk_idx(rule->action)], STKCTR_TRACK_CONTENT); + if (sess->fe != s->be) + stkctr_set_flags(&s->stkctr[trk_idx(rule->action)], STKCTR_TRACK_BACKEND); + } + } + break; + + /* other flags exists, but normaly, they never be matched. */ + default: + break; + } + } + + end: + if (early_hints) { + htx_send_early_hints(s, early_hints); + free_trash_chunk(early_hints); + } + + /* we reached the end of the rules, nothing to report */ + return rule_ret; +} + +/* Executes the http-response rules for stream and proxy . It + * returns one of 5 possible statuses: HTTP_RULE_RES_CONT, HTTP_RULE_RES_STOP, + * HTTP_RULE_RES_DONE, HTTP_RULE_RES_YIELD, or HTTP_RULE_RES_BADREQ. If *CONT + * is returned, the process can continue the evaluation of next rule list. If + * *STOP or *DONE is returned, the process must stop the evaluation. If *BADREQ + * is returned, it means the operation could not be processed and a server error + * must be returned. It may set the TX_SVDENY on txn->flags if it encounters a + * deny rule. If *YIELD is returned, the caller must call again the function + * with the same context. + */ +static enum rule_result htx_res_get_intercept_rule(struct proxy *px, struct list *rules, + struct stream *s) +{ + struct session *sess = strm_sess(s); + struct http_txn *txn = s->txn; + struct htx *htx; + struct connection *cli_conn; + struct act_rule *rule; + struct http_hdr_ctx ctx; + enum rule_result rule_ret = HTTP_RULE_RES_CONT; + int act_flags = 0; + + htx = htx_from_buf(&s->res.buf); + + /* If "the current_rule_list" match the executed rule list, we are in + * resume condition. If a resume is needed it is always in the action + * and never in the ACL or converters. In this case, we initialise the + * current rule, and go to the action execution point. + */ + if (s->current_rule) { + rule = s->current_rule; + s->current_rule = NULL; + if (s->current_rule_list == rules) + goto resume_execution; + } + s->current_rule_list = rules; + + list_for_each_entry(rule, rules, list) { + /* check optional condition */ + if (rule->cond) { + int ret; + + ret = acl_exec_cond(rule->cond, px, sess, s, SMP_OPT_DIR_RES|SMP_OPT_FINAL); + ret = acl_pass(ret); + + if (rule->cond->pol == ACL_COND_UNLESS) + ret = !ret; + + if (!ret) /* condition not matched */ + continue; + } + + act_flags |= ACT_FLAG_FIRST; +resume_execution: + switch (rule->action) { + case ACT_ACTION_ALLOW: + rule_ret = HTTP_RULE_RES_STOP; /* "allow" rules are OK */ + goto end; + + case ACT_ACTION_DENY: + txn->flags |= TX_SVDENY; + rule_ret = HTTP_RULE_RES_STOP; + goto end; + + case ACT_HTTP_SET_NICE: + s->task->nice = rule->arg.nice; + break; + + case ACT_HTTP_SET_TOS: + if ((cli_conn = objt_conn(sess->origin)) && conn_ctrl_ready(cli_conn)) + inet_set_tos(cli_conn->handle.fd, &cli_conn->addr.from, rule->arg.tos); + break; + + case ACT_HTTP_SET_MARK: +#ifdef SO_MARK + if ((cli_conn = objt_conn(sess->origin)) && conn_ctrl_ready(cli_conn)) + setsockopt(cli_conn->handle.fd, SOL_SOCKET, SO_MARK, &rule->arg.mark, sizeof(rule->arg.mark)); +#endif + break; + + case ACT_HTTP_SET_LOGL: + s->logs.level = rule->arg.loglevel; + break; + + case ACT_HTTP_REPLACE_HDR: + case ACT_HTTP_REPLACE_VAL: + if (htx_transform_header(s, &s->res, htx, + ist2(rule->arg.hdr_add.name, rule->arg.hdr_add.name_len), + &rule->arg.hdr_add.fmt, + &rule->arg.hdr_add.re, rule->action)) { + rule_ret = HTTP_RULE_RES_BADREQ; + goto end; + } + break; + + case ACT_HTTP_DEL_HDR: + /* remove all occurrences of the header */ + ctx.blk = NULL; + while (http_find_header(htx, ist2(rule->arg.hdr_add.name, rule->arg.hdr_add.name_len), &ctx, 1)) + http_remove_header(htx, &ctx); + break; + + case ACT_HTTP_SET_HDR: + case ACT_HTTP_ADD_HDR: { + struct buffer *replace; + struct ist n, v; + + replace = alloc_trash_chunk(); + if (!replace) { + rule_ret = HTTP_RULE_RES_BADREQ; + goto end; + } + + replace->data = build_logline(s, replace->area, replace->size, &rule->arg.hdr_add.fmt); + n = ist2(rule->arg.hdr_add.name, rule->arg.hdr_add.name_len); + v = ist2(replace->area, replace->data); + + if (rule->action == ACT_HTTP_SET_HDR) { + /* remove all occurrences of the header */ + ctx.blk = NULL; + while (http_find_header(htx, ist2(rule->arg.hdr_add.name, rule->arg.hdr_add.name_len), &ctx, 1)) + http_remove_header(htx, &ctx); + } + + if (!http_add_header(htx, n, v)) { + static unsigned char rate_limit = 0; + + if ((rate_limit++ & 255) == 0) { + send_log(px, LOG_WARNING, "Proxy %s failed to add or set the response header '%.*s' for request #%u. You might need to increase tune.maxrewrite.", px->id, (int)n.len, n.ptr, s->uniq_id); + } + + HA_ATOMIC_ADD(&sess->fe->fe_counters.failed_rewrites, 1); + if (sess->fe != s->be) + HA_ATOMIC_ADD(&s->be->be_counters.failed_rewrites, 1); + if (sess->listener->counters) + HA_ATOMIC_ADD(&sess->listener->counters->failed_rewrites, 1); + if (objt_server(s->target)) + HA_ATOMIC_ADD(&objt_server(s->target)->counters.failed_rewrites, 1); + } + free_trash_chunk(replace); + break; + } + + case ACT_HTTP_DEL_ACL: + case ACT_HTTP_DEL_MAP: { + struct pat_ref *ref; + struct buffer *key; + + /* collect reference */ + ref = pat_ref_lookup(rule->arg.map.ref); + if (!ref) + continue; + + /* allocate key */ + key = alloc_trash_chunk(); + if (!key) { + rule_ret = HTTP_RULE_RES_BADREQ; + goto end; + } + + /* collect key */ + key->data = build_logline(s, key->area, key->size, &rule->arg.map.key); + key->area[key->data] = '\0'; + + /* perform update */ + /* returned code: 1=ok, 0=ko */ + HA_SPIN_LOCK(PATREF_LOCK, &ref->lock); + pat_ref_delete(ref, key->area); + HA_SPIN_UNLOCK(PATREF_LOCK, &ref->lock); + + free_trash_chunk(key); + break; + } + + case ACT_HTTP_ADD_ACL: { + struct pat_ref *ref; + struct buffer *key; + + /* collect reference */ + ref = pat_ref_lookup(rule->arg.map.ref); + if (!ref) + continue; + + /* allocate key */ + key = alloc_trash_chunk(); + if (!key) { + rule_ret = HTTP_RULE_RES_BADREQ; + goto end; + } + + /* collect key */ + key->data = build_logline(s, key->area, key->size, &rule->arg.map.key); + key->area[key->data] = '\0'; + + /* perform update */ + /* check if the entry already exists */ + if (pat_ref_find_elt(ref, key->area) == NULL) + pat_ref_add(ref, key->area, NULL, NULL); + + free_trash_chunk(key); + break; + } + + case ACT_HTTP_SET_MAP: { + struct pat_ref *ref; + struct buffer *key, *value; + + /* collect reference */ + ref = pat_ref_lookup(rule->arg.map.ref); + if (!ref) + continue; + + /* allocate key */ + key = alloc_trash_chunk(); + if (!key) { + rule_ret = HTTP_RULE_RES_BADREQ; + goto end; + } + + /* allocate value */ + value = alloc_trash_chunk(); + if (!value) { + free_trash_chunk(key); + rule_ret = HTTP_RULE_RES_BADREQ; + goto end; + } + + /* collect key */ + key->data = build_logline(s, key->area, key->size, &rule->arg.map.key); + key->area[key->data] = '\0'; + + /* collect value */ + value->data = build_logline(s, value->area, value->size, &rule->arg.map.value); + value->area[value->data] = '\0'; + + /* perform update */ + HA_SPIN_LOCK(PATREF_LOCK, &ref->lock); + if (pat_ref_find_elt(ref, key->area) != NULL) + /* update entry if it exists */ + pat_ref_set(ref, key->area, value->area, NULL); + else + /* insert a new entry */ + pat_ref_add(ref, key->area, value->area, NULL); + HA_SPIN_UNLOCK(PATREF_LOCK, &ref->lock); + free_trash_chunk(key); + free_trash_chunk(value); + break; + } + + case ACT_HTTP_REDIR: + rule_ret = HTTP_RULE_RES_DONE; + if (!http_apply_redirect_rule(rule->arg.redir, s, txn)) + rule_ret = HTTP_RULE_RES_BADREQ; + goto end; + + case ACT_ACTION_TRK_SC0 ... ACT_ACTION_TRK_SCMAX: + /* Note: only the first valid tracking parameter of each + * applies. + */ + if (stkctr_entry(&s->stkctr[trk_idx(rule->action)]) == NULL) { + struct stktable *t; + struct stksess *ts; + struct stktable_key *key; + void *ptr; + + t = rule->arg.trk_ctr.table.t; + key = stktable_fetch_key(t, s->be, sess, s, SMP_OPT_DIR_RES | SMP_OPT_FINAL, + rule->arg.trk_ctr.expr, NULL); + + if (key && (ts = stktable_get_entry(t, key))) { + stream_track_stkctr(&s->stkctr[trk_idx(rule->action)], t, ts); + + HA_RWLOCK_WRLOCK(STK_SESS_LOCK, &ts->lock); + + /* let's count a new HTTP request as it's the first time we do it */ + ptr = stktable_data_ptr(t, ts, STKTABLE_DT_HTTP_REQ_CNT); + if (ptr) + stktable_data_cast(ptr, http_req_cnt)++; + + ptr = stktable_data_ptr(t, ts, STKTABLE_DT_HTTP_REQ_RATE); + if (ptr) + update_freq_ctr_period(&stktable_data_cast(ptr, http_req_rate), + t->data_arg[STKTABLE_DT_HTTP_REQ_RATE].u, 1); + + /* When the client triggers a 4xx from the server, it's most often due + * to a missing object or permission. These events should be tracked + * because if they happen often, it may indicate a brute force or a + * vulnerability scan. Normally this is done when receiving the response + * but here we're tracking after this ought to have been done so we have + * to do it on purpose. + */ + if ((unsigned)(txn->status - 400) < 100) { + ptr = stktable_data_ptr(t, ts, STKTABLE_DT_HTTP_ERR_CNT); + if (ptr) + stktable_data_cast(ptr, http_err_cnt)++; + + ptr = stktable_data_ptr(t, ts, STKTABLE_DT_HTTP_ERR_RATE); + if (ptr) + update_freq_ctr_period(&stktable_data_cast(ptr, http_err_rate), + t->data_arg[STKTABLE_DT_HTTP_ERR_RATE].u, 1); + } + + HA_RWLOCK_WRUNLOCK(STK_SESS_LOCK, &ts->lock); + + /* If data was modified, we need to touch to re-schedule sync */ + stktable_touch_local(t, ts, 0); + + stkctr_set_flags(&s->stkctr[trk_idx(rule->action)], STKCTR_TRACK_CONTENT); + if (sess->fe != s->be) + stkctr_set_flags(&s->stkctr[trk_idx(rule->action)], STKCTR_TRACK_BACKEND); + } + } + break; + + case ACT_CUSTOM: + if ((s->req.flags & CF_READ_ERROR) || + ((s->req.flags & (CF_SHUTR|CF_READ_NULL)) && + !(s->si[0].flags & SI_FL_CLEAN_ABRT) && + (px->options & PR_O_ABRT_CLOSE))) + act_flags |= ACT_FLAG_FINAL; + + switch (rule->action_ptr(rule, px, s->sess, s, act_flags)) { + case ACT_RET_ERR: + case ACT_RET_CONT: + break; + case ACT_RET_STOP: + rule_ret = HTTP_RULE_RES_STOP; + goto end; + case ACT_RET_YIELD: + s->current_rule = rule; + rule_ret = HTTP_RULE_RES_YIELD; + goto end; + } + break; + + /* other flags exists, but normaly, they never be matched. */ + default: + break; + } + } + + end: + /* we reached the end of the rules, nothing to report */ + return rule_ret; +} + /* This function terminates the request because it was completly analyzed or * because an error was triggered during the body forwarding. */