mirror of
https://git.haproxy.org/git/haproxy.git/
synced 2025-12-11 12:41:00 +01:00
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.
This commit is contained in:
parent
3e9641951f
commit
336400861e
428
src/proto_htx.c
428
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_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 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
|
/* 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
|
* 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
|
* data or wants to immediately abort the request (eg: timeout, error, ...). It
|
||||||
@ -3454,6 +3457,431 @@ resume_execution:
|
|||||||
return rule_ret;
|
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 <hdr> */
|
||||||
|
|
||||||
|
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 <px> to all headers in buffer <req> of stream <s>.
|
||||||
|
* 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 <res>.
|
||||||
|
* 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 <hdr> */
|
||||||
|
|
||||||
|
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 <res>.
|
||||||
|
* 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 <px> to all headers in buffer <res> of stream <s>.
|
||||||
|
* 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
|
/* This function terminates the request because it was completly analyzed or
|
||||||
* because an error was triggered during the body forwarding.
|
* because an error was triggered during the body forwarding.
|
||||||
*/
|
*/
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user