]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MINOR: proto_htx: Add functions to apply req* and rsp* rules on HTX messages
authorChristopher Faulet <cfaulet@haproxy.com>
Wed, 24 Oct 2018 09:53:01 +0000 (11:53 +0200)
committerWilly Tarreau <w@1wt.eu>
Sun, 18 Nov 2018 21:08:57 +0000 (22:08 +0100)
It is more or less the same than legacy versions but adapted to be called from
HTX analyzers.

src/proto_htx.c

index 2b2fcf6d053045a8ea96b5df8ef24f8ff246a88e..c0f9d07247b877afc6ea4b9bf3b8f217b7b7143c 100644 (file)
@@ -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 <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
  * because an error was triggered during the body forwarding.
  */