From: Christopher Faulet Date: Wed, 24 Oct 2018 09:53:01 +0000 (+0200) Subject: MINOR: proto_htx: Add functions to apply req* and rsp* rules on HTX messages X-Git-Tag: v1.9-dev7~56 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=336400861eebf825406211c20e8145da66d97500;p=thirdparty%2Fhaproxy.git MINOR: proto_htx: Add functions to apply req* and rsp* rules on HTX messages It is more or less the same than legacy versions but adapted to be called from HTX analyzers. --- diff --git a/src/proto_htx.c b/src/proto_htx.c index 2b2fcf6d05..c0f9d07247 100644 --- a/src/proto_htx.c +++ b/src/proto_htx.c @@ -49,6 +49,9 @@ static void htx_debug_hdr(const char *dir, struct stream *s, const struct ist n, 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); +static int htx_apply_filters_to_request(struct stream *s, struct channel *req, struct proxy *px); +static int htx_apply_filters_to_response(struct stream *s, struct channel *res, struct proxy *px); + /* 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 @@ -3454,6 +3457,431 @@ resume_execution: return rule_ret; } +/* Iterate the same filter through all request headers. + * Returns 1 if this filter can be stopped upon return, otherwise 0. + * Since it can manage the switch to another backend, it updates the per-proxy + * DENY stats. + */ +static int htx_apply_filter_to_req_headers(struct stream *s, struct channel *req, struct hdr_exp *exp) +{ + struct http_txn *txn = s->txn; + struct htx *htx; + struct buffer *hdr = get_trash_chunk(); + int32_t pos; + + htx = htx_from_buf(&req->buf); + + for (pos = htx_get_head(htx); pos != -1; pos = htx_get_next(htx, pos)) { + struct htx_blk *blk = htx_get_blk(htx, pos); + enum htx_blk_type type; + struct ist n, v; + + next_hdr: + type = htx_get_blk_type(blk); + if (type == HTX_BLK_EOH) + break; + if (type != HTX_BLK_HDR) + continue; + + if (unlikely(txn->flags & (TX_CLDENY | TX_CLTARPIT))) + return 1; + else if (unlikely(txn->flags & TX_CLALLOW) && + (exp->action == ACT_ALLOW || + exp->action == ACT_DENY || + exp->action == ACT_TARPIT)) + return 0; + + n = htx_get_blk_name(htx, blk); + v = htx_get_blk_value(htx, blk); + + chunk_memcat(hdr, n.ptr, n.len); + hdr->area[hdr->data++] = ':'; + hdr->area[hdr->data++] = ' '; + chunk_memcat(hdr, v.ptr, v.len); + + /* Now we have one header in */ + + if (regex_exec_match2(exp->preg, hdr->area, hdr->data, MAX_MATCH, pmatch, 0)) { + struct http_hdr_ctx ctx; + int len; + + switch (exp->action) { + case ACT_ALLOW: + txn->flags |= TX_CLALLOW; + goto end; + + case ACT_DENY: + txn->flags |= TX_CLDENY; + goto end; + + case ACT_TARPIT: + txn->flags |= TX_CLTARPIT; + goto end; + + case ACT_REPLACE: + len = exp_replace(trash.area, trash.size, hdr->area, exp->replace, pmatch); + if (len < 0) + return -1; + + http_parse_header(ist2(trash.area, len), &n, &v); + ctx.blk = blk; + ctx.value = v; + if (!http_replace_header(htx, &ctx, n, v)) + return -1; + if (!ctx.blk) + goto end; + pos = htx_get_blk_pos(htx, blk); + break; + + case ACT_REMOVE: + ctx.blk = blk; + ctx.value = v; + if (!http_remove_header(htx, &ctx)) + return -1; + if (!ctx.blk) + goto end; + pos = htx_get_blk_pos(htx, blk); + goto next_hdr; + + } + } + } + end: + return 0; +} + +/* Apply the filter to the request line. + * Returns 0 if nothing has been done, 1 if the filter has been applied, + * or -1 if a replacement resulted in an invalid request line. + * Since it can manage the switch to another backend, it updates the per-proxy + * DENY stats. + */ +static int htx_apply_filter_to_req_line(struct stream *s, struct channel *req, struct hdr_exp *exp) +{ + struct http_txn *txn = s->txn; + struct htx *htx; + struct buffer *reqline = get_trash_chunk(); + int done; + + htx = htx_from_buf(&req->buf); + + if (unlikely(txn->flags & (TX_CLDENY | TX_CLTARPIT))) + return 1; + else if (unlikely(txn->flags & TX_CLALLOW) && + (exp->action == ACT_ALLOW || + exp->action == ACT_DENY || + exp->action == ACT_TARPIT)) + return 0; + else if (exp->action == ACT_REMOVE) + return 0; + + done = 0; + + reqline->data = htx_fmt_req_line(http_find_stline(htx), reqline->area, reqline->size); + + /* Now we have the request line between cur_ptr and cur_end */ + if (regex_exec_match2(exp->preg, reqline->area, reqline->data, MAX_MATCH, pmatch, 0)) { + union h1_sl sl; + int len; + + switch (exp->action) { + case ACT_ALLOW: + txn->flags |= TX_CLALLOW; + done = 1; + break; + + case ACT_DENY: + txn->flags |= TX_CLDENY; + done = 1; + break; + + case ACT_TARPIT: + txn->flags |= TX_CLTARPIT; + done = 1; + break; + + case ACT_REPLACE: + len = exp_replace(trash.area, trash.size, reqline->area, exp->replace, pmatch); + if (len < 0) + return -1; + + http_parse_stline(ist2(trash.area, len), + &sl.rq.m, &sl.rq.u, &sl.rq.v); + sl.rq.meth = find_http_meth(sl.rq.m.ptr, sl.rq.m.len); + + if (!http_replace_reqline(htx, sl)) + return -1; + done = 1; + break; + } + } + return done; +} + +/* + * Apply all the req filters of proxy to all headers in buffer of stream . + * Returns 0 if everything is alright, or -1 in case a replacement lead to an + * unparsable request. Since it can manage the switch to another backend, it + * updates the per-proxy DENY stats. + */ +static int htx_apply_filters_to_request(struct stream *s, struct channel *req, struct proxy *px) +{ + struct session *sess = s->sess; + struct http_txn *txn = s->txn; + struct hdr_exp *exp; + + for (exp = px->req_exp; exp; exp = exp->next) { + int ret; + + /* + * The interleaving of transformations and verdicts + * makes it difficult to decide to continue or stop + * the evaluation. + */ + + if (txn->flags & (TX_CLDENY|TX_CLTARPIT)) + break; + + if ((txn->flags & TX_CLALLOW) && + (exp->action == ACT_ALLOW || exp->action == ACT_DENY || + exp->action == ACT_TARPIT || exp->action == ACT_PASS)) + continue; + + /* if this filter had a condition, evaluate it now and skip to + * next filter if the condition does not match. + */ + if (exp->cond) { + ret = acl_exec_cond(exp->cond, px, sess, s, SMP_OPT_DIR_REQ|SMP_OPT_FINAL); + ret = acl_pass(ret); + if (((struct acl_cond *)exp->cond)->pol == ACL_COND_UNLESS) + ret = !ret; + + if (!ret) + continue; + } + + /* Apply the filter to the request line. */ + ret = htx_apply_filter_to_req_line(s, req, exp); + if (unlikely(ret < 0)) + return -1; + + if (likely(ret == 0)) { + /* The filter did not match the request, it can be + * iterated through all headers. + */ + if (unlikely(htx_apply_filter_to_req_headers(s, req, exp) < 0)) + return -1; + } + } + return 0; +} + +/* Iterate the same filter through all response headers contained in . + * Returns 1 if this filter can be stopped upon return, otherwise 0. + */ +static int htx_apply_filter_to_resp_headers(struct stream *s, struct channel *res, struct hdr_exp *exp) +{ + struct http_txn *txn = s->txn; + struct htx *htx; + struct buffer *hdr = get_trash_chunk(); + int32_t pos; + + htx = htx_from_buf(&res->buf); + + for (pos = htx_get_head(htx); pos != -1; pos = htx_get_next(htx, pos)) { + struct htx_blk *blk = htx_get_blk(htx, pos); + enum htx_blk_type type; + struct ist n, v; + + next_hdr: + type = htx_get_blk_type(blk); + if (type == HTX_BLK_EOH) + break; + if (type != HTX_BLK_HDR) + continue; + + if (unlikely(txn->flags & TX_SVDENY)) + return 1; + else if (unlikely(txn->flags & TX_SVALLOW) && + (exp->action == ACT_ALLOW || + exp->action == ACT_DENY)) + return 0; + + n = htx_get_blk_name(htx, blk); + v = htx_get_blk_value(htx, blk); + + chunk_memcat(hdr, n.ptr, n.len); + hdr->area[hdr->data++] = ':'; + hdr->area[hdr->data++] = ' '; + chunk_memcat(hdr, v.ptr, v.len); + + /* Now we have one header in */ + + if (regex_exec_match2(exp->preg, hdr->area, hdr->data, MAX_MATCH, pmatch, 0)) { + struct http_hdr_ctx ctx; + int len; + + switch (exp->action) { + case ACT_ALLOW: + txn->flags |= TX_SVALLOW; + goto end; + break; + + case ACT_DENY: + txn->flags |= TX_SVDENY; + goto end; + break; + + case ACT_REPLACE: + len = exp_replace(trash.area, trash.size, hdr->area, exp->replace, pmatch); + if (len < 0) + return -1; + + http_parse_header(ist2(trash.area, len), &n, &v); + ctx.blk = blk; + ctx.value = v; + if (!http_replace_header(htx, &ctx, n, v)) + return -1; + if (!ctx.blk) + goto end; + pos = htx_get_blk_pos(htx, blk); + break; + + case ACT_REMOVE: + ctx.blk = blk; + ctx.value = v; + if (!http_remove_header(htx, &ctx)) + return -1; + if (!ctx.blk) + goto end; + pos = htx_get_blk_pos(htx, blk); + goto next_hdr; + } + } + + } + end: + return 0; +} + +/* Apply the filter to the status line in the response buffer . + * Returns 0 if nothing has been done, 1 if the filter has been applied, + * or -1 if a replacement resulted in an invalid status line. + */ +static int htx_apply_filter_to_sts_line(struct stream *s, struct channel *res, struct hdr_exp *exp) +{ + struct http_txn *txn = s->txn; + struct htx *htx; + struct buffer *resline = get_trash_chunk(); + int done; + + htx = htx_from_buf(&res->buf); + + if (unlikely(txn->flags & TX_SVDENY)) + return 1; + else if (unlikely(txn->flags & TX_SVALLOW) && + (exp->action == ACT_ALLOW || + exp->action == ACT_DENY)) + return 0; + else if (exp->action == ACT_REMOVE) + return 0; + + done = 0; + resline->data = htx_fmt_res_line(http_find_stline(htx), resline->area, resline->size); + + /* Now we have the status line between cur_ptr and cur_end */ + if (regex_exec_match2(exp->preg, resline->area, resline->data, MAX_MATCH, pmatch, 0)) { + union h1_sl sl; + int len; + + switch (exp->action) { + case ACT_ALLOW: + txn->flags |= TX_SVALLOW; + done = 1; + break; + + case ACT_DENY: + txn->flags |= TX_SVDENY; + done = 1; + break; + + case ACT_REPLACE: + len = exp_replace(trash.area, trash.size, resline->area, exp->replace, pmatch); + if (len < 0) + return -1; + + http_parse_stline(ist2(trash.area, len), + &sl.st.v, &sl.st.c, &sl.st.r); + sl.st.status = strl2ui(sl.st.c.ptr, sl.st.c.len); + + if (!http_replace_resline(htx, sl)) + return -1; + + done = 1; + return 1; + } + } + return done; +} + +/* + * Apply all the resp filters of proxy to all headers in buffer of stream . + * Returns 0 if everything is alright, or -1 in case a replacement lead to an + * unparsable response. + */ +static int htx_apply_filters_to_response(struct stream *s, struct channel *res, struct proxy *px) +{ + struct session *sess = s->sess; + struct http_txn *txn = s->txn; + struct hdr_exp *exp; + + for (exp = px->rsp_exp; exp; exp = exp->next) { + int ret; + + /* + * The interleaving of transformations and verdicts + * makes it difficult to decide to continue or stop + * the evaluation. + */ + + if (txn->flags & TX_SVDENY) + break; + + if ((txn->flags & TX_SVALLOW) && + (exp->action == ACT_ALLOW || exp->action == ACT_DENY || + exp->action == ACT_PASS)) { + exp = exp->next; + continue; + } + + /* if this filter had a condition, evaluate it now and skip to + * next filter if the condition does not match. + */ + if (exp->cond) { + ret = acl_exec_cond(exp->cond, px, sess, s, SMP_OPT_DIR_RES|SMP_OPT_FINAL); + ret = acl_pass(ret); + if (((struct acl_cond *)exp->cond)->pol == ACL_COND_UNLESS) + ret = !ret; + if (!ret) + continue; + } + + /* Apply the filter to the status line. */ + ret = htx_apply_filter_to_sts_line(s, res, exp); + if (unlikely(ret < 0)) + return -1; + + if (likely(ret == 0)) { + /* The filter did not match the response, it can be + * iterated through all headers. + */ + if (unlikely(htx_apply_filter_to_resp_headers(s, res, exp) < 0)) + return -1; + } + } + return 0; +} + /* This function terminates the request because it was completly analyzed or * because an error was triggered during the body forwarding. */