diff --git a/.gitignore b/.gitignore index 080330348..ebd17ca44 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,9 @@ Module.symvers .settings .pydevproject +# vscode project settings +.vscode + # Python compiler cache files *.pyc diff --git a/fw/cache.c b/fw/cache.c index e53395d06..d1f37291f 100644 --- a/fw/cache.c +++ b/fw/cache.c @@ -356,6 +356,10 @@ tfw_cache_employ_req(TfwHttpReq *req) /* cache_fulfill - work as usual in cache mode. */ BUG_ON(cmd != TFW_D_CACHE_FULFILL); + /* No cache for request due to http chain '$cache' action */ + if (req->cache_ctl.flags & TFW_HTTP_CC_CHAIN_NO_CACHE) + return false; + if (req->cache_ctl.flags & TFW_HTTP_CC_NO_CACHE) /* * TODO: RFC 7234 4. "... a cache MUST NOT reuse a stored diff --git a/fw/http.c b/fw/http.c index 299476009..fafb5e9ae 100644 --- a/fw/http.c +++ b/fw/http.c @@ -4010,7 +4010,7 @@ tfw_http_hdr_split(TfwStr *hdr, TfwStr *name_out, TfwStr *val_out, bool inplace) val_out->len += chunk->len; - /* + /* * Skip OWS after the header value (RWS) - they must be in * separate chunks too. */ @@ -5402,6 +5402,11 @@ tfw_http_req_process(TfwConn *conn, TfwStream *stream, const TfwFsmData *data) */ req->cache_ctl.timestamp = tfw_current_timestamp(); req->jrxtstamp = jiffies; + /* + * Bypass cache if corresponding binary flag in request set + */ + if (unlikely(test_bit(TFW_HTTP_B_CHAIN_NO_CACHE, req->flags))) + req->cache_ctl.flags |= TFW_HTTP_CC_CHAIN_NO_CACHE; /* * Run frang checks first before any processing happen. Can't start diff --git a/fw/http.h b/fw/http.h index bfe911ac4..d7d2a4592 100644 --- a/fw/http.h +++ b/fw/http.h @@ -133,6 +133,7 @@ enum { #define TFW_HTTP_CC_MAX_STALE 0x00000010 #define TFW_HTTP_CC_MIN_FRESH 0x00000020 #define TFW_HTTP_CC_OIFCACHED 0x00000040 +#define TFW_HTTP_CC_CHAIN_NO_CACHE 0x00000080 /* Response only CC directives. */ #define TFW_HTTP_CC_MUST_REVAL 0x00000100 #define TFW_HTTP_CC_PROXY_REVAL 0x00000200 @@ -263,6 +264,8 @@ enum { TFW_HTTP_FLAGS_REQ, /* Sticky cookie is found and verified. */ TFW_HTTP_B_HAS_STICKY = TFW_HTTP_FLAGS_REQ, + /* Request fitted no cache cookie rule */ + TFW_HTTP_B_CHAIN_NO_CACHE, /* Request is non-idempotent. */ TFW_HTTP_B_NON_IDEMP, /* Request stated 'Accept: text/html' header */ diff --git a/fw/http_match.c b/fw/http_match.c index b93c8e0a9..990a9bfc4 100644 --- a/fw/http_match.c +++ b/fw/http_match.c @@ -375,7 +375,7 @@ match_hdr_raw(const TfwHttpReq *req, const TfwHttpMatchRule *rule) static bool match_hdr(const TfwHttpReq *req, const TfwHttpMatchRule *rule) { - tfw_http_hdr_t id = rule->hid; + tfw_http_hdr_t id = rule->val.hid; BUG_ON(id < 0); if (id == TFW_HTTP_HDR_RAW) @@ -408,6 +408,41 @@ match_mark(const TfwHttpReq *req, const TfwHttpMatchRule *rule) return mark == rule->arg.num; } +static bool +match_cookie(const TfwHttpReq *req, const TfwHttpMatchRule *rule) +{ + TfwStr cookie_val; + TfwStr *hdr, *end, *dup; + tfw_str_eq_flags_t flags; + if (unlikely(rule->val.type != TFW_HTTP_MATCH_V_COOKIE)) + return false; + hdr = &req->h_tbl->tbl[TFW_HTTP_HDR_COOKIE]; + if (TFW_STR_EMPTY(hdr)) + return 0; + TFW_STR_FOR_EACH_DUP(dup, hdr, end) { + TfwStr value = { 0 }; + int r; + tfw_http_msg_clnthdr_val(req, dup, TFW_HTTP_HDR_COOKIE, &value); + r = tfw_http_search_cookie(rule->val.ptn.str, + rule->val.ptn.len, + &value, &cookie_val, + rule->val.ptn.op, false); + if (r) + goto val_cmp; + } + return 0; + +val_cmp: + flags = map_op_to_str_eq_flags(rule->op); + if (rule->op == TFW_HTTP_MATCH_O_SUFFIX) + return tfw_str_eq_cstr_off(&cookie_val, + cookie_val.len - rule->arg.len, + rule->arg.str, rule->arg.len, + flags); + return tfw_str_eq_cstr(&cookie_val, rule->arg.str, rule->arg.len, + flags); +} + typedef bool (*match_fn)(const TfwHttpReq *, const TfwHttpMatchRule *); static const match_fn match_fn_tbl[_TFW_HTTP_MATCH_F_COUNT] = { @@ -417,6 +452,7 @@ static const match_fn match_fn_tbl[_TFW_HTTP_MATCH_F_COUNT] = { [TFW_HTTP_MATCH_F_METHOD] = match_method, [TFW_HTTP_MATCH_F_URI] = match_uri, [TFW_HTTP_MATCH_F_MARK] = match_mark, + [TFW_HTTP_MATCH_F_COOKIE] = match_cookie, }; /** @@ -425,7 +461,7 @@ static const match_fn match_fn_tbl[_TFW_HTTP_MATCH_F_COUNT] = { * has appropriate action type. */ static bool -do_eval(const TfwHttpReq *req, const TfwHttpMatchRule *rule) +do_eval(TfwHttpReq *req, const TfwHttpMatchRule *rule) { match_fn match_fn; tfw_http_match_fld_t field; @@ -458,6 +494,16 @@ do_eval(const TfwHttpReq *req, const TfwHttpMatchRule *rule) req->msg.skb_head->mark = rule->act.mark; return false; } + /* + * Evaluate binary flag setting action. + */ + if (rule->act.type == TFW_HTTP_MATCH_ACT_FLAG) { + if (likely(rule->act.flg.set)) + set_bit(rule->act.flg.fid, req->flags); + else + clear_bit(rule->act.flg.fid, req->flags); + return false; + } return true; } @@ -471,6 +517,7 @@ tfw_http_tbl_arg_type(tfw_http_match_fld_t field) [TFW_HTTP_MATCH_F_METHOD] = TFW_HTTP_MATCH_A_METHOD, [TFW_HTTP_MATCH_F_URI] = TFW_HTTP_MATCH_A_STR, [TFW_HTTP_MATCH_F_MARK] = TFW_HTTP_MATCH_A_NUM, + [TFW_HTTP_MATCH_F_COOKIE] = TFW_HTTP_MATCH_A_STR, }; BUG_ON(field <= 0 || field >= _TFW_HTTP_MATCH_F_COUNT); @@ -483,7 +530,7 @@ tfw_http_tbl_arg_type(tfw_http_match_fld_t field) * Return a first matching rule. */ TfwHttpMatchRule * -tfw_http_match_req(const TfwHttpReq *req, struct list_head *mlst) +tfw_http_match_req(TfwHttpReq *req, struct list_head *mlst) { TfwHttpMatchRule *rule; @@ -599,7 +646,8 @@ tfw_http_rule_arg_init(TfwHttpMatchRule *rule, const char *arg, size_t arg_len) rule->arg.len = arg_len; memcpy(rule->arg.str, arg, arg_len); if (rule->field == TFW_HTTP_MATCH_F_HDR - && rule->hid == TFW_HTTP_HDR_RAW) + && rule->val.type == TFW_HTTP_MATCH_V_HEADER + && rule->val.hid == TFW_HTTP_HDR_RAW) { char *p = rule->arg.str; while ((*p = tolower(*p))) @@ -609,14 +657,37 @@ tfw_http_rule_arg_init(TfwHttpMatchRule *rule, const char *arg, size_t arg_len) return 0; } +static +size_t +tfw_http_escape_pre_post(char *out , const char *str) +{ + int i; + size_t len = 0; + bool escaped = false; + + for (i = 0; str[i]; ++i) { + if (str[i] == '*' && !escaped && (i == 0 || !str[i + 1])) + continue; + if (str[i] != '\\' || escaped) { + escaped = false; + *out = str[i]; + ++len; + ++out; + } + else if (str[i] == '\\') { + escaped = true; + } + } + + return len; +} + const char * tfw_http_arg_adjust(const char *arg, tfw_http_match_fld_t field, const char *raw_hdr_name, size_t *size_out, tfw_http_match_arg_t *type_out, tfw_http_match_op_t *op_out) { - int i; - bool escaped; char *arg_out, *pos; size_t name_len = 0, full_name_len = 0, len = strlen(arg); bool wc_arg = (arg[0] == '*' && len == 1); @@ -631,7 +702,7 @@ tfw_http_arg_adjust(const char *arg, tfw_http_match_fld_t field, if (wc_arg && !raw_hdr_name) return NULL; - if (raw_hdr_name) { + if (raw_hdr_name && field != TFW_HTTP_MATCH_F_COOKIE) { name_len = strlen(raw_hdr_name); full_name_len = name_len + SLEN(S_DLM); } @@ -641,7 +712,7 @@ tfw_http_arg_adjust(const char *arg, tfw_http_match_fld_t field, return ERR_PTR(-ENOMEM); } - if (raw_hdr_name) { + if (raw_hdr_name && field != TFW_HTTP_MATCH_F_COOKIE) { memcpy(arg_out, raw_hdr_name, name_len); memcpy(arg_out + name_len, S_DLM, SLEN(S_DLM)); } @@ -661,42 +732,88 @@ tfw_http_arg_adjust(const char *arg, tfw_http_match_fld_t field, * pattern should be applied. */ if (!wc_arg && arg[0] == '*') { - if (*op_out == TFW_HTTP_MATCH_O_PREFIX) + if (*op_out == TFW_HTTP_MATCH_O_PREFIX) { T_WARN_NL("http_match: unable to match" " double-wildcard patterns '%s', so" " prefix pattern will be applied\n", arg); - - else if (raw_hdr_name) - T_WARN_NL("http_match: unable to match suffix" - " pattern '%s' in case of raw header" - " specification: '%s', so wildcard pattern" - " will not be applied\n", arg, raw_hdr_name); - - else + } + else if (raw_hdr_name) { + if (field != TFW_HTTP_MATCH_F_COOKIE) + T_WARN_NL("http_match: unable to match suffix" + " pattern '%s' in case of raw header" + " specification: '%s', so wildcard" + " pattern will not be applied\n", + arg, raw_hdr_name); + else + *op_out = TFW_HTTP_MATCH_O_SUFFIX; + } else { *op_out = TFW_HTTP_MATCH_O_SUFFIX; + } } - len = full_name_len; - escaped = false; pos = arg_out + full_name_len; - for (i = 0; arg[i]; ++i) { - if (arg[i] == '*' && !escaped && (i == 0 || !arg[i + 1])) - continue; - if (arg[i] != '\\' || escaped) { - escaped = false; - *pos = arg[i]; - ++len; - ++pos; - } - else if (arg[i] == '\\') { - escaped = true; + len = tfw_http_escape_pre_post(pos, arg); + *size_out += full_name_len + len + 1; + + return arg_out; +} + +const char * +tfw_http_val_adjust(const char *val, tfw_http_match_fld_t field, + unsigned int *len_out, + tfw_http_match_val_t *type_out, + tfw_http_match_op_t *op_out) +{ + size_t len, len_adjust; + char *val_out; + bool wc_val; + + if (field != TFW_HTTP_MATCH_F_COOKIE) { + *type_out = TFW_HTTP_MATCH_V_HEADER; + return NULL; + } + *type_out = TFW_HTTP_MATCH_V_COOKIE; + + if (!val) { + T_ERR_NL("http_tbl: cookie pattern is empty, must be filled\n"); + return ERR_PTR(-EINVAL); + } + + len = strlen(val); + wc_val = (val[0] == '*' && len == 1); + + if (!(val_out = kzalloc(len + SLEN("=") + 1, GFP_KERNEL))) { + T_ERR_NL("http_match: unable to allocate rule field value.\n"); + return ERR_PTR(-ENOMEM); + } + + *op_out = TFW_HTTP_MATCH_O_EQ; + if (wc_val) + *op_out = TFW_HTTP_MATCH_O_WILDCARD; + if (len > 1 && val[len - 1] == '*' && val[len - 2] != '\\') + *op_out = TFW_HTTP_MATCH_O_PREFIX; + if (!wc_val && val[0] == '*') { + if (*op_out == TFW_HTTP_MATCH_O_PREFIX) { + T_ERR_NL("http_match: unable to match" + " double-wildcard patterns '%s'\n", val); + return ERR_PTR(-EINVAL); + } else { + *op_out = TFW_HTTP_MATCH_O_SUFFIX; } } - *size_out = len + 1; - return arg_out; + len_adjust = tfw_http_escape_pre_post(val_out, val); + if (*op_out == TFW_HTTP_MATCH_O_EQ || + *op_out == TFW_HTTP_MATCH_O_SUFFIX) + { + val_out[len_adjust++] = '='; + } + *len_out = len_adjust; + + return val_out; } + int tfw_http_verify_hdr_field(tfw_http_match_fld_t field, const char **hdr_name, unsigned int *hid_out) @@ -730,3 +847,86 @@ tfw_http_verify_hdr_field(tfw_http_match_fld_t field, const char **hdr_name, return 0; } + +/* + * Search for cookie in `Set-Cookie`/`Cookie` header value @cookie + * and save the cookie value into @val. Prefix, suffix or wildacar compare + * @op is supported, pass TFW_HTTP_MATCH_O_EQ for default behaviour. + * Flag @is_resp_hdr identifies the header name: true for `Set-Cookie`, + * false for `Cookie`. + */ +int +tfw_http_search_cookie(const char *cstr, unsigned long clen, + const TfwStr *cookie, TfwStr *val, + tfw_http_match_op_t op, bool is_resp_hdr) +{ + TfwStr *chunk, *end; + TfwStr tmp = { .flags = 0, }; + unsigned int n = cookie->nchunks; + /* Search cookie name. */ + end = cookie->chunks + cookie->nchunks; + for (chunk = cookie->chunks; chunk != end; ++chunk, --n) { + if (!(chunk->flags & TFW_STR_NAME)) + continue; + if (unlikely(op == TFW_HTTP_MATCH_O_WILDCARD)) + break; + /* + * Create a temporary compound string, starting with this + * chunk. The total string length is not used here, so it + * is not set. + */ + tmp.chunks = chunk; + tmp.nchunks = n; + /* The ops are the same due to '=' at the end of cookie name */ + if (op == TFW_HTTP_MATCH_O_PREFIX || + op == TFW_HTTP_MATCH_O_EQ) + { + if (tfw_str_eq_cstr(&tmp, cstr, clen, + TFW_STR_EQ_PREFIX)) + { + break; + } + } + else if (op == TFW_HTTP_MATCH_O_SUFFIX) { + TfwStr *name; + unsigned int len = 0; + + for (name = chunk; name != end; ++name) { + if (!(name->flags & TFW_STR_NAME)) { + break; + } + len += name->len; + } + if (tfw_str_eq_cstr_off(&tmp, len - (unsigned int) clen, cstr, clen, + TFW_STR_EQ_PREFIX)) + { + break; + } + + } else { + continue; + } + /* + * 'Cookie' header has multiple name-value pairs while the + * 'Set-Cookie' has only one. + */ + if (unlikely(is_resp_hdr)) + return 0; + } + if (chunk == end) + return 0; + /* Search cookie value, starting with next chunk. */ + for (++chunk; chunk != end; ++chunk) + if (chunk->flags & TFW_STR_VALUE) + break; + /* + * The party can send us zero-value cookie, + * treat this as not found cookie. + */ + if (unlikely(chunk == end)) + return 0; + + tfw_str_collect_cmp(chunk, end, val, ";"); + + return 1; +} diff --git a/fw/http_match.h b/fw/http_match.h index 239928099..b4f9574fd 100644 --- a/fw/http_match.h +++ b/fw/http_match.h @@ -35,9 +35,17 @@ typedef enum { TFW_HTTP_MATCH_F_METHOD, TFW_HTTP_MATCH_F_URI, TFW_HTTP_MATCH_F_MARK, + TFW_HTTP_MATCH_F_COOKIE, _TFW_HTTP_MATCH_F_COUNT } tfw_http_match_fld_t; +typedef enum { + TFW_HTTP_MATCH_V_NA = 0, + TFW_HTTP_MATCH_V_HEADER, + TFW_HTTP_MATCH_V_COOKIE, + _TFW_HTTP_MATCH_V_COUNT +} tfw_http_match_val_t; + typedef enum { TFW_HTTP_MATCH_O_NA = 0, TFW_HTTP_MATCH_O_WILDCARD, @@ -65,6 +73,7 @@ typedef enum { TFW_HTTP_MATCH_ACT_VHOST, TFW_HTTP_MATCH_ACT_MARK, TFW_HTTP_MATCH_ACT_BLOCK, + TFW_HTTP_MATCH_ACT_FLAG, _TFW_HTTP_MATCH_ACT_COUNT } tfw_http_rule_act_t; @@ -79,12 +88,28 @@ typedef struct { }; } TfwHttpMatchArg; +typedef struct { + tfw_http_match_val_t type; + union { + unsigned int hid; + struct { + tfw_http_match_op_t op; + unsigned int len; /* String length for speedup */ + const char *str; /* Allocated sring, free it after */ + } ptn; /* Pattern */ + }; +} TfwHttpMatchVal; + typedef struct { tfw_http_rule_act_t type; union { TfwHttpChain *chain; TfwVhost *vhost; unsigned int mark; + struct { + unsigned int fid; + bool set; + } flg; }; } TfwHttpAction; @@ -93,8 +118,8 @@ typedef struct { tfw_http_match_fld_t field; /* Field of a HTTP message to compare. */ tfw_http_match_op_t op; /* Comparison operator. */ TfwHttpAction act; /* Rule action. */ - unsigned int hid; /* Header ID. */ - unsigned int inv; /* Comparison inversion (inequality) flag.*/ + TfwHttpMatchVal val; /* A field value to compare with arg. */ + unsigned int inv; /* Comparison inversion (!=) flag.*/ TfwHttpMatchArg arg; /* A value to be compared with the field. note: the @arg has variable length. */ } TfwHttpMatchRule; @@ -114,7 +139,7 @@ void tfw_http_table_free(TfwHttpTable *table); * Match a HTTP request against a list of rules in chain. * Return a matching rule. */ -TfwHttpMatchRule *tfw_http_match_req(const TfwHttpReq *req, +TfwHttpMatchRule *tfw_http_match_req(TfwHttpReq *req, struct list_head *mlst); /** @@ -130,9 +155,17 @@ const char *tfw_http_arg_adjust(const char *arg, tfw_http_match_fld_t field, const char *raw_hdr_name, size_t *size_out, tfw_http_match_arg_t *type_out, tfw_http_match_op_t *op_out); +const char *tfw_http_val_adjust(const char *val, tfw_http_match_fld_t field, + unsigned int *len_out, + tfw_http_match_val_t *type_out, + tfw_http_match_op_t *op_out); int tfw_http_verify_hdr_field(tfw_http_match_fld_t field, const char **h_name, unsigned int *hid_out); +int tfw_http_search_cookie(const char *cstr, unsigned long clen, + const TfwStr *cookie, TfwStr *val, + tfw_http_match_op_t op, bool is_resp_hdr); + #define tfw_http_chain_rules_for_each(chain, func) \ ({ \ int r = 0; \ diff --git a/fw/http_sess.c b/fw/http_sess.c index ecd89e9c4..e3a19031f 100644 --- a/fw/http_sess.c +++ b/fw/http_sess.c @@ -46,6 +46,7 @@ #include "client.h" #include "hash.h" #include "http_msg.h" +#include "http_match.h" #include "http_sess.h" #include "http_sess_conf.h" #include "vhost.h" @@ -269,63 +270,6 @@ tfw_http_sticky_build_redirect(TfwHttpReq *req, StickyVal *sv, RedirMarkVal *mv, return TFW_HTTP_SESS_REDIRECT_NEED; } -/* - * Search for cookie defined in @sticky configuration in `Set-Cookie`/`Cookie` - * header value @cookie and save the cookie value into @val. @is_resp_hdr flag - * identifies the header name: true for `Set-Cookie`, false for `Cookie`. - */ -static int -search_cookie(TfwStickyCookie *sticky, const TfwStr *cookie, TfwStr *val, - bool is_resp_hdr) -{ - const char *const cstr = sticky->name_eq.data; - const unsigned long clen = sticky->name_eq.len; - TfwStr *chunk, *end; - TfwStr tmp = { .flags = 0, }; - unsigned int n = cookie->nchunks; - - BUG_ON(!TFW_STR_PLAIN(&sticky->name_eq)); - - /* Search cookie name. */ - end = cookie->chunks + cookie->nchunks; - for (chunk = cookie->chunks; chunk != end; ++chunk, --n) { - if (!(chunk->flags & TFW_STR_NAME)) - continue; - /* - * Create a temporary compound string, starting with this - * chunk. The total string length is not used here, so it - * is not set. - */ - tmp.chunks = chunk; - tmp.nchunks = n; - if (tfw_str_eq_cstr(&tmp, cstr, clen, TFW_STR_EQ_PREFIX)) - break; - /* - * 'Cookie' header has multiple name-value pairs while the - * 'Set-Cookie' has only one. - */ - if (unlikely(is_resp_hdr)) - return 0; - } - if (chunk == end) - return 0; - - /* Search cookie value, starting with next chunk. */ - for (++chunk; chunk != end; ++chunk) - if (chunk->flags & TFW_STR_VALUE) - break; - /* - * The party can send us zero-value cookie, - * treat this as not found cookie. - */ - if (unlikely(chunk == end)) - return 0; - - tfw_str_collect_cmp(chunk, end, val, ";"); - - return 1; -} - /* * Find Tempesta sticky cookie in an HTTP request. * @@ -356,7 +300,11 @@ tfw_http_sticky_get_req(TfwHttpReq *req, TfwStr *cookie_val) int r; tfw_http_msg_clnthdr_val(req, dup, TFW_HTTP_HDR_COOKIE, &value); - r = search_cookie(req->vhost->cookie, &value, cookie_val, false); + BUG_ON(!TFW_STR_PLAIN(&req->vhost->cookie->name_eq)); + r = tfw_http_search_cookie(req->vhost->cookie->name_eq.data, + req->vhost->cookie->name_eq.len, + &value, cookie_val, + TFW_HTTP_MATCH_O_EQ, false); if (r) return r; } @@ -1231,6 +1179,8 @@ tfw_http_sticky_get_resp(TfwHttpResp *resp, TfwStr *cookie_val) { TfwStickyCookie *sticky = resp->req->vhost->cookie; TfwStr *hdr, *dup, *dup_end; + + BUG_ON(!TFW_STR_PLAIN(&sticky->name_eq)); /* * Each cookie in set header is placed in its own `Set-Cookie` header, * need to look through all of them @@ -1242,8 +1192,13 @@ tfw_http_sticky_get_resp(TfwHttpResp *resp, TfwStr *cookie_val) if (TFW_STR_EMPTY(dup)) continue; tfw_http_msg_srvhdr_val(dup, TFW_HTTP_HDR_SET_COOKIE, &value); - if (search_cookie(sticky, &value, cookie_val, true)) + if (tfw_http_search_cookie(sticky->name_eq.data, + sticky->name_eq.len, + &value, cookie_val, + TFW_HTTP_MATCH_O_EQ, true)) + { return 1; + } } return 0; diff --git a/fw/http_tbl.c b/fw/http_tbl.c index e980350b4..88d6bbecc 100644 --- a/fw/http_tbl.c +++ b/fw/http_tbl.c @@ -194,6 +194,7 @@ static const TfwCfgEnum tfw_http_tbl_cfg_field_enum[] = { { "hdr", TFW_HTTP_MATCH_F_HDR }, { "mark", TFW_HTTP_MATCH_F_MARK }, { "method", TFW_HTTP_MATCH_F_METHOD }, + { "cookie", TFW_HTTP_MATCH_F_COOKIE }, { 0 } }; @@ -337,6 +338,7 @@ tfw_cfgop_http_tbl_chain_finish(TfwCfgSpec *cs) * uri == "*.php" -> static; * mark == 2 -> waf_chain; * referer != "*hacked.com" -> mark = 7; + * hdr "Referer" == "http://badhost.com*" -> block; * -> mark = 3; * } * @@ -345,17 +347,21 @@ tfw_cfgop_http_tbl_chain_finish(TfwCfgSpec *cs) * the list of rules of current chain. * * Syntax: - * +------------------------ First operand of rule's condition part - * | (HTTP request field or 'mark'); - * | +-------------------- Condition type: equal ('==') or not - * | | equal ('!='); - * | | +-------------- Second operand of rule's condition part - * | | | (argument for the rule - any string); - * | | | +---- Action part of the rule (reference to - * | | | | other http_chain or vhost, 'block' or - * | | | | 'mark' action). - * V V V V - * uri == "*.php" -> static + * +--------------------------------- First operand of rule's condition part + * | (HTTP request field or 'mark'); + * | +---------------------------- Header or any other field value to do + * | | comparison with + * | | +---------------------- Condition type: equal ('==') or not + * | | | equal ('!='); + * | | | +--------------- Second operand of rule's condition part + * | | | | (argument for the rule - any string); + * | | | | +------- Action part of the rule (reference to + * | | | | | other http_chain or vhost, 'block' or + * | | | | | 'mark' action). + * | | | | | +- Possible value for specified action + * | | | | | | + * V V V V V V + * hdr "Host" == "bad.ru" -> mark = 7 * */ static int @@ -363,10 +369,14 @@ tfw_cfgop_http_rule(TfwCfgSpec *cs, TfwCfgEntry *e) { int r; TfwHttpMatchRule *rule; - const char *in_field, *hdr, *action, *action_val, *in_arg, *arg = NULL; - unsigned int invert, hid = TFW_HTTP_HDR_RAW; - tfw_http_match_op_t op = TFW_HTTP_MATCH_O_WILDCARD; + const char *in_field, *in_field_val, *action, *action_val, + *in_arg, *arg = NULL, *val = NULL; + unsigned int invert, hid = TFW_HTTP_HDR_RAW, + act_val_parsed, val_len; + tfw_http_match_op_t op = TFW_HTTP_MATCH_O_WILDCARD, + op_val = TFW_HTTP_MATCH_O_WILDCARD; tfw_http_match_fld_t field = TFW_HTTP_MATCH_F_WILDCARD; + tfw_http_match_val_t type_val = TFW_HTTP_MATCH_V_NA; tfw_http_match_arg_t type = TFW_HTTP_MATCH_A_WILDCARD; TfwCfgRule *cfg_rule = &e->rule; size_t arg_size = 0; @@ -383,7 +393,7 @@ tfw_cfgop_http_rule(TfwCfgSpec *cs, TfwCfgEntry *e) invert = cfg_rule->inv; in_field = cfg_rule->fst; - hdr = cfg_rule->fst_ext; + in_field_val = cfg_rule->fst_ext; in_arg = cfg_rule->snd; action = cfg_rule->act; action_val = cfg_rule->val; @@ -406,11 +416,23 @@ tfw_cfgop_http_rule(TfwCfgSpec *cs, TfwCfgEntry *e) in_field); return r; } - if ((r = tfw_http_verify_hdr_field(field, &hdr, &hid))) - return r; + if (field != TFW_HTTP_MATCH_F_COOKIE) { + if ((r = tfw_http_verify_hdr_field(field, + &in_field_val, + &hid))) + { + return r; + } + } + + + val = tfw_http_val_adjust(in_field_val, field, + &val_len, &type_val, &op_val); + if (IS_ERR(val)) + return PTR_ERR(val); - arg = tfw_http_arg_adjust(in_arg, field, hdr, &arg_size, - &type, &op); + arg = tfw_http_arg_adjust(in_arg, field, in_field_val, + &arg_size, &type, &op); if (IS_ERR(arg)) return PTR_ERR(arg); } @@ -425,7 +447,16 @@ tfw_cfgop_http_rule(TfwCfgSpec *cs, TfwCfgEntry *e) && type != TFW_HTTP_MATCH_A_NUM && type != TFW_HTTP_MATCH_A_METHOD && type != TFW_HTTP_MATCH_A_WILDCARD); - rule->hid = hid; + BUG_ON(type_val != TFW_HTTP_MATCH_V_HEADER + && type_val != TFW_HTTP_MATCH_V_COOKIE); + rule->val.type = type_val; + if (type_val == TFW_HTTP_MATCH_V_COOKIE) { + rule->val.ptn.op = op_val; + rule->val.ptn.str = val; + rule->val.ptn.len = val_len; + } else { + rule->val.hid = hid; + } rule->inv = invert; rule->field = field; rule->op = op; @@ -446,9 +477,28 @@ tfw_cfgop_http_rule(TfwCfgSpec *cs, TfwCfgEntry *e) return -EINVAL; } rule->act.type = TFW_HTTP_MATCH_ACT_MARK; + } else if (strlen(action) && action[0] == '$') { + if (!strcasecmp(action, "$cache")) { + tfw_cfg_parse_uint(action_val, &act_val_parsed); + if (act_val_parsed != 1 && act_val_parsed != 0) { + T_ERR_NL("http_tbl: '$cache' action value " + "must 0 or 1: '%s'\n", + action_val); + return -EINVAL; + } + rule->act.type = TFW_HTTP_MATCH_ACT_FLAG; + rule->act.flg.set = (act_val_parsed == 0); + rule->act.flg.fid = TFW_HTTP_B_CHAIN_NO_CACHE; + } else { + T_ERR_NL("http_tbl: only '$cache' flag setting action " + "supported for now: '%s'\n", + action); + return -EINVAL; + } } else if (action_val) { - T_ERR_NL("http_tbl: not 'mark' actions must not have any value:" - " '%s'\n", action_val); + T_ERR_NL("http_tbl: only 'mark' or '$..' actions " + "may have any value: '%s'\n", + action_val); return -EINVAL; } else if (!strcasecmp(action, "block")) { rule->act.type = TFW_HTTP_MATCH_ACT_BLOCK; @@ -475,6 +525,7 @@ tfw_cfgop_http_rule(TfwCfgSpec *cs, TfwCfgEntry *e) return 0; err: + kfree(val); kfree(arg); return r; } @@ -484,6 +535,8 @@ tfw_cfgop_release_rule(TfwHttpMatchRule *rule) { if (rule->act.type == TFW_HTTP_MATCH_ACT_VHOST) tfw_vhost_put(rule->act.vhost); + if (rule->val.type == TFW_HTTP_MATCH_V_COOKIE) + kfree(rule->val.ptn.str); return 0; } diff --git a/fw/str.c b/fw/str.c index bf76bd283..f1f1b9b9d 100644 --- a/fw/str.c +++ b/fw/str.c @@ -1239,7 +1239,6 @@ tfw_str_eq_cstr(const TfwStr *str, const char *cstr, int cstr_len, typeof(&strncmp) cmp = (flags & TFW_STR_EQ_CASEI) ? tfw_cstricmp : (typeof(&strncmp))memcmp_fast; - BUG_ON(str->len && !str->data); TFW_STR_FOR_EACH_CHUNK(chunk, str, end) { BUG_ON(chunk->len && !chunk->data); diff --git a/fw/t/unit/test_http_match.c b/fw/t/unit/test_http_match.c index b0e702c23..d2900b900 100644 --- a/fw/t/unit/test_http_match.c +++ b/fw/t/unit/test_http_match.c @@ -109,39 +109,55 @@ http_match_suite_teardown(void) static void test_chain_add_rule_str(int test_id, tfw_http_match_fld_t field, - const char *hdr, const char *in_arg) + const char *in_val, const char *in_arg) { MatchEntry *e; unsigned int hid = TFW_HTTP_HDR_RAW; - tfw_http_match_op_t op = TFW_HTTP_MATCH_O_WILDCARD; + tfw_http_match_op_t op = TFW_HTTP_MATCH_O_WILDCARD, + op_val = TFW_HTTP_MATCH_O_WILDCARD; tfw_http_match_arg_t type = TFW_HTTP_MATCH_A_WILDCARD; + tfw_http_match_val_t val_type = TFW_HTTP_MATCH_V_HEADER; size_t arg_size = 0; - const char *arg = NULL; + unsigned int val_len = 0; + const char *arg = NULL, *val = NULL; BUG_ON(in_arg && field == TFW_HTTP_MATCH_F_WILDCARD); BUG_ON(!in_arg && field != TFW_HTTP_MATCH_F_WILDCARD); - tfw_http_verify_hdr_field(field, &hdr, &hid); - arg = tfw_http_arg_adjust(in_arg, field, hdr, &arg_size, &type, &op); + if (field != TFW_HTTP_MATCH_F_COOKIE) { + tfw_http_verify_hdr_field(field, &in_val, &hid); + } + val = tfw_http_val_adjust(in_val, field, &val_len, &val_type, &op_val); + arg = tfw_http_arg_adjust(in_arg, field, in_val, &arg_size, &type, &op); EXPECT_NOT_NULL(arg); if (!arg) return; - e = test_rule_container_new(test_chain, MatchEntry, rule, type, arg_size); EXPECT_NOT_NULL(e); if (!e) goto err; - e->rule.hid = hid; e->rule.field = field; + e->rule.val.type = val_type; + if (val_type == TFW_HTTP_MATCH_V_COOKIE) { + e->rule.val.ptn.op = op_val; + e->rule.val.ptn.str = val; + e->rule.val.ptn.len = val_len; + } else { + e->rule.val.hid = hid; + } e->rule.op = op; e->rule.arg.type = type; tfw_http_rule_arg_init(&e->rule, arg, arg_size - 1); /* Just dummy action type to avoid BUG_ON in 'do_eval()'. */ e->rule.act.type = TFW_HTTP_MATCH_ACT_CHAIN; e->test_id = test_id; + + kfree(arg); + return; err: kfree(arg); + kfree(val); } int @@ -502,6 +518,179 @@ TEST(http_match, method_eq) EXPECT_EQ(42, match_id); } +#define TFW_TEST_MAX_COOKIE_NCHUNKS 20 +TfwStr test_cookie_chunks[TFW_TEST_MAX_COOKIE_NCHUNKS] = { 0 }; +TfwStr test_cookie = { .chunks = test_cookie_chunks }; + +static +void tfw_test_cookie(char* header, ...) +{ + char *field; + + va_list args; + va_start(args, header); + + test_cookie_chunks[0] = (TfwStr){ + .data = header, NULL, strlen(header), 0, 0, 0 + }; + test_cookie.nchunks = 1; + + do { + char *eq_p; + field = va_arg(args, char *); + if (!field) + break; + + eq_p = strchr(field, '='); + BUG_ON(!eq_p); + + test_cookie.chunks[test_cookie.nchunks++] = (TfwStr) { + .data = field, NULL, + eq_p - field + 1, 0, TFW_STR_NAME, 0 + }; + test_cookie.chunks[test_cookie.nchunks++] = (TfwStr) { + .data = eq_p + 1, NULL, + strlen(field) - (eq_p - field + 1), 0, TFW_STR_VALUE, 0 + }; + test_cookie.chunks[test_cookie.nchunks++] = (TfwStr) { + .data = "; ", NULL, + SLEN("; "), 0, 0, 0 + }; + + } while (true); + + va_end(args); + + { + unsigned int n; + + test_cookie.len = 0; + for (n = 0; n < test_cookie.nchunks; ++n) { + test_cookie.len += test_cookie.chunks[n].len; + } + } + + test_req->h_tbl->tbl[TFW_HTTP_HDR_COOKIE] = test_cookie; +} + +TEST(http_match, cookie) +{ + int match_id; + + test_chain_add_rule_str(1, TFW_HTTP_MATCH_F_HDR, + "User-Agent", "U880D/4.0 (CP/M; 8-bit)"); + test_chain_add_rule_str(2, TFW_HTTP_MATCH_F_COOKIE, + "name", "*"); + test_chain_add_rule_str(3, TFW_HTTP_MATCH_F_COOKIE, + "coo_*", "*"); + test_chain_add_rule_str(4, TFW_HTTP_MATCH_F_COOKIE, + "J_-*", "Val_"); + test_chain_add_rule_str(5, TFW_HTTP_MATCH_F_COOKIE, + "J_-*", "Val__*"); + test_chain_add_rule_str(6, TFW_HTTP_MATCH_F_COOKIE, + "name", "Val_"); + test_chain_add_rule_str(7, TFW_HTTP_MATCH_F_COOKIE, + "_09", "_088_*"); + test_chain_add_rule_str(8, TFW_HTTP_MATCH_F_COOKIE, + "_09*", "_088_*"); + test_chain_add_rule_str(9, TFW_HTTP_MATCH_F_COOKIE, + "some", "some_\\*_"); + test_chain_add_rule_str(10, TFW_HTTP_MATCH_F_COOKIE, + "*zxcv", "*dd"); + + + tfw_test_cookie("Cookie: ", + "name1=value1", + "NaMe=Val_", + NULL); + match_id = test_chain_match(); + EXPECT_EQ(-1, match_id); + + tfw_test_cookie("Cookie: ", + "name1=value1", + "name=Val_", + NULL); + match_id = test_chain_match(); + EXPECT_EQ(2, match_id); + + tfw_test_cookie("Cookie: ", + "coo_=value1", + "5678=Val_", + NULL); + match_id = test_chain_match(); + EXPECT_EQ(3, match_id); + + tfw_test_cookie("Cookie: ", + "coo_-DDD=value1", + "5678=Val_", + NULL); + match_id = test_chain_match(); + EXPECT_EQ(3, match_id); + + tfw_test_cookie("Cookie: ", + "name1=value1", + "J_-=Val_", + NULL); + match_id = test_chain_match(); + EXPECT_EQ(4, match_id); + + tfw_test_cookie("Cookie: ", + "name1=value1", + "J_-=Val", + NULL); + match_id = test_chain_match(); + EXPECT_EQ(-1, match_id); + + tfw_test_cookie("Cookie: ", + "name1=value1", + "J_----=Val__", + NULL); + match_id = test_chain_match(); + EXPECT_EQ(5, match_id); + + tfw_test_cookie("Cookie: ", + "name1=value1", + "J_----=Val__--", + NULL); + match_id = test_chain_match(); + EXPECT_EQ(5, match_id); + + tfw_test_cookie("Cookie: ", + "_09=_088_*", + "some=some_*", + NULL); + match_id = test_chain_match(); + EXPECT_EQ(7, match_id); + + tfw_test_cookie("Cookie: ", + "_0*9=_088_", + "some=some_*_", + NULL); + match_id = test_chain_match(); + EXPECT_EQ(9, match_id); + + tfw_test_cookie("Cookie: ", + "ggggggg=fffff", + "zxcv=dd", + NULL); + match_id = test_chain_match(); + EXPECT_EQ(10, match_id); + + tfw_test_cookie("Cookie: ", + "ggggggg=fffff", + "zxcv=d", + NULL); + match_id = test_chain_match(); + EXPECT_EQ(-1, match_id); + + tfw_test_cookie("Cookie: ", + "ggggggg=fffff", + "xcv=dddd", + NULL); + match_id = test_chain_match(); + EXPECT_EQ(-1, match_id); +} + TEST_SUITE(http_match) { TEST_SETUP(http_match_suite_setup); @@ -518,4 +707,5 @@ TEST_SUITE(http_match) TEST_RUN(http_match, raw_header_eq); TEST_RUN(http_match, raw_header_eq_ws); TEST_RUN(http_match, method_eq); + TEST_RUN(http_match, cookie); }