diff --git a/etc/tempesta_fw.conf b/etc/tempesta_fw.conf index f1d5190280..0441a8e9e2 100644 --- a/etc/tempesta_fw.conf +++ b/etc/tempesta_fw.conf @@ -310,6 +310,8 @@ # Rule sets netfilter marks into all skbs for all matched requests. # - 'block' # Rule blocks all matched requests. +# - 'jsch' +# Rule responds with JS challenage to matched requests. # # VAL is possible value for specified action; only 'mark' action is allowed to # have value (unsigned integer type). diff --git a/fw/hpack.c b/fw/hpack.c index 1c6f5987c1..4069b664db 100644 --- a/fw/hpack.c +++ b/fw/hpack.c @@ -1309,7 +1309,6 @@ tfw_hpack_hdr_set(TfwHPack *__restrict hp, TfwHttpReq *__restrict req, break; case TFW_TAG_HDR_ACCEPT: parser->_hdr_tag = TFW_HTTP_HDR_RAW; - h2_set_hdr_accept(req, &entry->cstate); break; case TFW_TAG_HDR_CONTENT_ENCODING: parser->_hdr_tag = TFW_HTTP_HDR_CONTENT_ENCODING; diff --git a/fw/hpack.h b/fw/hpack.h index 9bbe40c12e..ebc4ef4162 100644 --- a/fw/hpack.h +++ b/fw/hpack.h @@ -152,7 +152,6 @@ typedef enum { */ typedef struct { union { - unsigned char accept_text_html; long if_msince_date; unsigned long authority_port; unsigned char ifnmatch_etag_any; diff --git a/fw/http_parser.c b/fw/http_parser.c index cfedb48a84..edbe9679d2 100644 --- a/fw/http_parser.c +++ b/fw/http_parser.c @@ -2636,7 +2636,6 @@ __req_parse_accept(TfwHttpReq *req, unsigned char *data, size_t len) } __FSM_STATE(Req_I_AfterTextSlashToken) { - TRY_STR("html", Req_I_AfterTextSlashToken, Req_I_AcceptHtml); TRY_STR_INIT(); __FSM_I_JMP(Req_I_Subtype); } @@ -2653,14 +2652,6 @@ __req_parse_accept(TfwHttpReq *req, unsigned char *data, size_t len) return CSTR_NEQ; } - __FSM_STATE(Req_I_AcceptHtml) { - if (IS_WS(c) || c == ',' || c == ';' || IS_CRLF(c)) { - __set_bit(TFW_HTTP_B_ACCEPT_HTML, req->flags); - __FSM_I_JMP(I_EoT); - } - __FSM_I_JMP(Req_I_Subtype); - } - __FSM_STATE(Req_I_Type) { __FSM_I_MATCH_MOVE(token, Req_I_Type); c = *(p + __fsm_sz); @@ -7179,13 +7170,6 @@ __h2_req_parse_authority(TfwHttpReq *req, unsigned char *data, size_t len, } STACK_FRAME_NON_STANDARD(__h2_req_parse_authority); -void -h2_set_hdr_accept(TfwHttpReq *req, const TfwCachedHeaderState *cstate) -{ - if (cstate->is_set && cstate->accept_text_html) - __set_bit(TFW_HTTP_B_ACCEPT_HTML, req->flags); -} - static int __h2_req_parse_accept(TfwHttpReq *req, unsigned char *data, size_t len, bool fin) @@ -7236,12 +7220,6 @@ __h2_req_parse_accept(TfwHttpReq *req, unsigned char *data, size_t len, } __FSM_STATE(Req_I_AfterTextSlashToken) { - H2_TRY_STR_LAMBDA("html", { - parser->cstate.is_set = 1; - parser->cstate.accept_text_html = 1; - h2_set_hdr_accept(req, &parser->cstate); - __FSM_EXIT(CSTR_EQ); - }, Req_I_AfterTextSlashToken, Req_I_AcceptHtml); TRY_STR_INIT(); __FSM_I_JMP(Req_I_Subtype); } @@ -7258,14 +7236,6 @@ __h2_req_parse_accept(TfwHttpReq *req, unsigned char *data, size_t len, return CSTR_NEQ; } - __FSM_STATE(Req_I_AcceptHtml) { - if (IS_WS(c) || c == ',' || c == ';') { - __set_bit(TFW_HTTP_B_ACCEPT_HTML, req->flags); - __FSM_I_JMP(I_EoT); - } - __FSM_I_JMP(Req_I_Subtype); - } - __FSM_STATE(Req_I_Type) { __FSM_H2_I_MATCH_MOVE_LAMBDA(token, Req_I_Type, { __FSM_EXIT(CSTR_NEQ); diff --git a/fw/http_parser.h b/fw/http_parser.h index a6a66964f2..ce4c8d00d8 100644 --- a/fw/http_parser.h +++ b/fw/http_parser.h @@ -159,7 +159,6 @@ int tfw_http_parse_terminate(TfwHttpMsg *hm); bool tfw_http_parse_is_done(TfwHttpMsg *hm); void tfw_idx_hdr_parse_host_port(TfwHttpReq *req, TfwStr *hdr); -void h2_set_hdr_accept(TfwHttpReq *req, const TfwCachedHeaderState *cstate); int h2_set_hdr_if_mod_since(TfwHttpReq *req, const TfwCachedHeaderState *cstate); void h2_set_hdr_authority(TfwHttpReq *req, const TfwCachedHeaderState *cstate); diff --git a/fw/http_sess.c b/fw/http_sess.c index 11363360e2..60fe3091d5 100644 --- a/fw/http_sess.c +++ b/fw/http_sess.c @@ -29,7 +29,7 @@ * JS challenge client should execute it and send new request with * appropriate cookie just in time. * - * Copyright (C) 2015-2024 Tempesta Technologies, Inc. + * Copyright (C) 2015-2025 Tempesta Technologies, Inc. * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by @@ -116,10 +116,8 @@ tfw_http_sess_cookie_enabled(TfwHttpReq *req) } /** - * Normal browser must be able to execute the challenge: not all requests - * can be challenged, e.g. images - a browser won't execute the JS code if - * receives the challenge. Send redirect only for requests with - * 'Accept: text/html' and GET method. + * Send JS challenge to a client only if request beign + * matched by http chain "jsch" rule and it is GET request. */ static bool tfw_http_sticky_redirect_applied(TfwHttpReq *req) @@ -127,8 +125,8 @@ tfw_http_sticky_redirect_applied(TfwHttpReq *req) if (!req->vhost->cookie->js_challenge) return true; - return (req->method == TFW_HTTP_METH_GET) - && test_bit(TFW_HTTP_B_ACCEPT_HTML, req->flags); + return req->method == TFW_HTTP_METH_GET && + test_bit(TFW_HTTP_B_MUST_BE_CHALLENGED, req->flags); } static int diff --git a/fw/http_tbl.c b/fw/http_tbl.c index e664d08be7..bd1dc5aecd 100644 --- a/fw/http_tbl.c +++ b/fw/http_tbl.c @@ -501,6 +501,11 @@ tfw_cfgop_http_rule(TfwCfgSpec *cs, TfwCfgEntry *e) rule->act.type = TFW_HTTP_MATCH_ACT_CACHE_TTL; rule->act.cache_ttl = act_val_parsed; } + else if (!strcasecmp(action, "jsch")) { + rule->act.type = TFW_HTTP_MATCH_ACT_FLAG; + rule->act.flg.fid = TFW_HTTP_B_MUST_BE_CHALLENGED; + rule->act.flg.set = true; + } else if (action && action_val && !tfw_cfg_parse_uint(action, &rule->act.redir.resp_code)) { diff --git a/fw/http_types.h b/fw/http_types.h index cee69cf4ac..b5fc915388 100644 --- a/fw/http_types.h +++ b/fw/http_types.h @@ -96,8 +96,6 @@ enum { TFW_HTTP_B_CHAIN_NO_CACHE, /* Request is non-idempotent. */ TFW_HTTP_B_NON_IDEMP, - /* Request stated 'Accept: text/html' header */ - TFW_HTTP_B_ACCEPT_HTML, /* Request is created by HTTP health monitor. */ TFW_HTTP_B_HMONITOR, /* Client was disconnected, drop the request. */ @@ -108,6 +106,8 @@ enum { TFW_HTTP_B_NEED_STRIP_LEADING_CR, /* Need strip 1 leading LF */ TFW_HTTP_B_NEED_STRIP_LEADING_LF, + /* JS challenge must be applied. */ + TFW_HTTP_B_MUST_BE_CHALLENGED, /* * Request should be challenged, but requested resourse * is non-challengeable. Try to service such request diff --git a/fw/t/unit/test_http1_parser.c b/fw/t/unit/test_http1_parser.c index 8dcc39ab9f..82448dbd21 100644 --- a/fw/t/unit/test_http1_parser.c +++ b/fw/t/unit/test_http1_parser.c @@ -1660,15 +1660,8 @@ TEST(http1_parser, folding) TEST(http1_parser, accept) { -#define __FOR_ACCEPT(accept_val, EXPECT_HTML_MACRO) \ - FOR_REQ_SIMPLE("Accept:" accept_val) \ - { \ - EXPECT_HTML_MACRO(test_bit(TFW_HTTP_B_ACCEPT_HTML, \ - req->flags)); \ - } +#define FOR_ACCEPT(accept_val) FOR_REQ_SIMPLE("Accept:" accept_val) -#define FOR_ACCEPT(accept_val) __FOR_ACCEPT(accept_val, EXPECT_FALSE) -#define FOR_ACCEPT_HTML(accept_val) __FOR_ACCEPT(accept_val, EXPECT_TRUE) #define EXPECT_BLOCK_ACCEPT(header) EXPECT_BLOCK_REQ_SIMPLE("Accept:"header) #define TEST_ACCEPT_EXT(HEAD) \ @@ -1768,23 +1761,21 @@ TEST(http1_parser, accept) EXPECT_BLOCK_ACCEPT("*/,,"); /* HTML validations */ - FOR_ACCEPT_HTML(" text/html "); - FOR_ACCEPT_HTML(" text/html, application/xhtml+xml "); - FOR_ACCEPT_HTML(" text/html;q=0.8 "); - FOR_ACCEPT_HTML(" text/html,application/xhtml+xml,application/xml;" + FOR_ACCEPT(" text/html "); + FOR_ACCEPT(" text/html, application/xhtml+xml "); + FOR_ACCEPT(" text/html;q=0.8 "); + FOR_ACCEPT(" text/html,application/xhtml+xml,application/xml;" "q=0.9,image/webp,image/apng,*/*;q=0.8"); - FOR_ACCEPT_HTML(" text/html, */* "); - FOR_ACCEPT_HTML(" text/html, invalid/invalid ; key=val; q=0.5 "); - FOR_ACCEPT_HTML(" invalid/invalid; param=\"value value\", text/html"); + FOR_ACCEPT(" text/html, */* "); + FOR_ACCEPT(" text/html, invalid/invalid ; key=val; q=0.5 "); + FOR_ACCEPT(" invalid/invalid; param=\"value value\", text/html"); FOR_ACCEPT(" text/* "); FOR_ACCEPT(" invalid/invalid; q=0.5; key=val, */* "); FOR_ACCEPT(" textK/html"); #undef TEST_ACCEPT_EXT #undef EXPECT_BLOCK_ACCEPT -#undef FOR_ACCEPT_HTML #undef FOR_ACCEPT -#undef __FOR_ACCEPT } TEST(http1_parser, host) diff --git a/fw/t/unit/test_http2_parser.c b/fw/t/unit/test_http2_parser.c index 1058fb9e3b..9462660b02 100644 --- a/fw/t/unit/test_http2_parser.c +++ b/fw/t/unit/test_http2_parser.c @@ -1161,7 +1161,7 @@ TEST(http2_parser, ows) #undef EXPECT_BLOCK_REQ_H2_METHOD } -#define __FOR_ACCEPT(accept_val, EXPECT_HTML_MACRO) \ +#define FOR_ACCEPT(accept_val) \ FOR_REQ_H2( \ HEADERS_FRAME_BEGIN(); \ HEADER(WO_IND(NAME(":method"), VALUE("GET"))); \ @@ -1169,14 +1169,7 @@ TEST(http2_parser, ows) HEADER(WO_IND(NAME(":path"), VALUE("/"))); \ HEADER(WO_IND(NAME("accept"), VALUE(accept_val))); \ HEADERS_FRAME_END(); \ - ) \ - { \ - EXPECT_HTML_MACRO(test_bit(TFW_HTTP_B_ACCEPT_HTML, \ - req->flags)); \ - } - -#define FOR_ACCEPT(accept_val) __FOR_ACCEPT(accept_val, EXPECT_FALSE) -#define FOR_ACCEPT_HTML(accept_val) __FOR_ACCEPT(accept_val, EXPECT_TRUE) + ); #define EXPECT_BLOCK_REQ_H2_ACCEPT(header) \ EXPECT_BLOCK_REQ_H2( \ @@ -1301,14 +1294,14 @@ TEST_MPART(http2_parser, accept, 3) EXPECT_BLOCK_REQ_H2_ACCEPT("*/"); /* HTML validations */ - FOR_ACCEPT_HTML(" text/html "); - FOR_ACCEPT_HTML(" text/html, application/xhtml+xml "); - FOR_ACCEPT_HTML(" text/html;q=0.8 "); - FOR_ACCEPT_HTML(" text/html,application/xhtml+xml,application/xml;" + FOR_ACCEPT(" text/html "); + FOR_ACCEPT(" text/html, application/xhtml+xml "); + FOR_ACCEPT(" text/html;q=0.8 "); + FOR_ACCEPT(" text/html,application/xhtml+xml,application/xml;" "q=0.9,image/webp,image/apng,*/*;q=0.8"); - FOR_ACCEPT_HTML(" text/html, */* "); - FOR_ACCEPT_HTML(" text/html, invalid/invalid ; key=val; q=0.5 "); - FOR_ACCEPT_HTML(" invalid/invalid; param=\"value value\", text/html"); + FOR_ACCEPT(" text/html, */* "); + FOR_ACCEPT(" text/html, invalid/invalid ; key=val; q=0.5 "); + FOR_ACCEPT(" invalid/invalid; param=\"value value\", text/html"); FOR_ACCEPT(" text/* "); FOR_ACCEPT(" invalid/invalid; q=0.5; key=val, */* "); FOR_ACCEPT(" textK/html"); @@ -1317,9 +1310,7 @@ TEST_MPART(http2_parser, accept, 3) #undef TEST_ACCEPT_EXT #undef EXPECT_BLOCK_REQ_H2_ACCEPT -#undef FOR_ACCEPT_HTML #undef FOR_ACCEPT -#undef __FOR_ACCEPT TEST_MPART_DEFINE(http2_parser, accept, H2_ACCEPT_TCNT, TEST_MPART_NAME(http2_parser, accept, 0),