diff --git a/Makefile b/Makefile index f36ed5b3c..3438ab4af 100644 --- a/Makefile +++ b/Makefile @@ -733,7 +733,7 @@ OBJS = src/haproxy.o src/sessionhash.o src/base64.o src/protocol.o \ src/session.o src/stream.o src/hdr_idx.o src/ev_select.o src/signal.o \ src/acl.o src/sample.o src/memory.o src/freq_ctr.o src/auth.o src/proto_udp.o \ src/compression.o src/payload.o src/hash.o src/pattern.o src/map.o \ - src/namespace.o src/mailers.o src/dns.o + src/namespace.o src/mailers.o src/dns.o src/vars.o EBTREE_OBJS = $(EBTREE_DIR)/ebtree.o \ $(EBTREE_DIR)/eb32tree.o $(EBTREE_DIR)/eb64tree.o \ diff --git a/doc/configuration.txt b/doc/configuration.txt index 163e873c6..a2133cdc9 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -585,6 +585,10 @@ The following keywords are supported in the "global" section : - tune.ssl.maxrecord - tune.ssl.default-dh-param - tune.ssl.ssl-ctx-cache-size + - tune.vars.global-max-size + - tune.vars.reqres-max-size + - tune.vars.sess-max-size + - tune.vars.txn-max-size - tune.zlib.memlevel - tune.zlib.windowsize @@ -1288,6 +1292,21 @@ tune.ssl.ssl-ctx-cache-size dynamically is expensive, they are cached. The default cache size is set to 1000 entries. +tune.vars.global-max-size +tune.vars.reqres-max-size +tune.vars.sess-max-size +tune.vars.txn-max-size + These four tunes helps to manage the allowed amount of memory used by the + variables system. "global" limits the memory for all the systems. "sess" limit + the memory by session, "txn" limits the memory by transaction and "reqres" + limits the memory for each request or response processing. during the + accounting, "sess" embbed "txn" and "txn" embed "reqres". + + By example, we considers that "tune.vars.sess-max-size" is fixed to 100, + "tune.vars.txn-max-size" is fixed to 100, "tune.vars.reqres-max-size" is + also fixed to 100. If we create a variable "txn.var" that contains 100 bytes, + we cannot create any more variable in the other contexts. + tune.zlib.memlevel Sets the memLevel parameter in zlib initialization for each session. It defines how much memory should be allocated for the internal compression @@ -3350,6 +3369,7 @@ http-request { allow | deny | tarpit | auth [realm ] | redirect | del-acl() | del-map() | set-map() | + set-var() | { track-sc0 | track-sc1 | track-sc2 } [table ] | lua } @@ -3618,6 +3638,30 @@ http-request { allow | deny | tarpit | auth [realm ] | redirect | parameter is the name of the function to run. The prototype of the function is documented in the API documentation. + - set-var() : + Is used to set the contents of a variable. The variable is declared + inline. + + The name of the variable starts by an indication about its + scope. The allowed scopes are: + "sess" : the variable is shared with all the session, + "txn" : the variable is shared with all the transaction + (request and response) + "req" : the variable is shared only during the request + processing + "res" : the variable is shared only during the response + processing. + This prefix is followed by a name. The separator is a '.'. + The name may only contain characters 'a-z', 'A-Z', '0-9', + and '_'. + + Is a standard HAProxy expression formed by a sample-fetch + followed by some converters. + + Example: + + http-request set-var(req.my_var) req.fhdr(user-agent),lower + There is no limit to the number of http-request statements per instance. It is important to know that http-request rules are processed very early in @@ -3683,6 +3727,7 @@ http-response { allow | deny | add-header | set-nice | del-acl() | del-map() | set-map() | + set-var() | lua } [ { if | unless } ] @@ -3857,6 +3902,30 @@ http-response { allow | deny | add-header | set-nice | a redirect rule is applied during a response, connections to the server are closed so that no data can be forwarded from the server to the client. + - set-var() expr: + Is used to set the contents of a variable. The variable is declared + inline. + + The name of the variable starts by an indication about its + scope. The allowed scopes are: + "sess" : the variable is shared with all the session, + "txn" : the variable is shared with all the transaction + (request and response) + "req" : the variable is shared only during the request + processing + "res" : the variable is shared only during the response + processing. + This prefix is followed by a name. The separator is a '.'. + The name may only contain characters 'a-z', 'A-Z', '0-9', + and '_'. + + Is a standard HAProxy expression formed by a sample-fetch + followed by some converters. + + Example: + + http-response set-var(sess.last_redir) res.hdr(location) + There is no limit to the number of http-response statements per instance. It is important to know that http-response rules are processed very early in @@ -8209,6 +8278,7 @@ tcp-request content [{if | unless} ] - capture : the specified sample expression is captured - { track-sc0 | track-sc1 | track-sc2 } [table
] - lua + - set-var() They have the same meaning as their counter-parts in "tcp-request connection" so please refer to that section for a complete description. @@ -8243,6 +8313,28 @@ tcp-request content [{if | unless} ] function to run. The prototype of the function is documented in the API documentation. + The "set-var" is used to set the content of a variable. The variable is + declared inline. + + The name of the variable starts by an indication about its scope. + The allowed scopes are: + "sess" : the variable is shared with all the session, + "txn" : the variable is shared with all the transaction + (request and response) + "req" : the variable is shared only during the request + processing + "res" : the variable is shared only during the response + processing. + This prefix is followed by a name. The separator is a '.'. + The name may only contain characters 'a-z', 'A-Z', '0-9' and '_'. + + Is a standard HAProxy expression formed by a sample-fetch + followed by some converters. + + Example: + + tcp-request content set-var(sess.my_var) src + Example: # Accept HTTP requests containing a Host header saying "example.com" # and reject everything else. @@ -8393,6 +8485,9 @@ tcp-response content [{if | unless} ] - lua Executes Lua. + - set-var() + Sets a variable. + Note that the "if/unless" condition is optional. If no condition is set on the action, it is simply performed unconditionally. That can be useful for for changing the default action to a reject. @@ -8408,6 +8503,28 @@ tcp-response content [{if | unless} ] function to run. The prototype of the function is documented in the API documentation. + The "set-var" is used to set the content of a variable. The variable is + declared inline. + + The name of the variable starts by an indication about its scope. + The allowed scopes are: + "sess" : the variable is shared with all the session, + "txn" : the variable is shared with all the transaction + (request and response) + "req" : the variable is shared only during the request + processing + "res" : the variable is shared only during the response + processing. + This prefix is followed by a name. The separator is a '.'. + The name may only contain characters 'a-z', 'A-Z', '0-9' and '_'. + + Is a standard HAProxy expression formed by a sample-fetch + followed by some converters. + + Example: + + tcp-request content set-var(sess.my_var) src + See section 7 about ACL usage. See also : "tcp-request content", "tcp-response inspect-delay" @@ -11208,6 +11325,18 @@ sdbm([]) 32-bit hash is trivial to break. See also "crc32", "djb2", "wt6" and the "hash-type" directive. +set-var() + Sets a variable with the input content and return the content on the output as + is. The variable keep the value and the associated input type. The name of the + variable starts by an indication about it scope. The scope allowed are: + "sess" : the variable is shared with all the session, + "txn" : the variable is shared with all the transaction (request and + response), + "req" : the variable is shared only during the request processing, + "res" : the variable is shared only during the response processing. + This prefix is followed by a name. The separator is a '.'. The name may only + contain characters 'a-z', 'A-Z', '0-9' and '_'. + sub() Subtracts from the input value of type unsigned integer, and returns the result as an unsigned integer. Note: in order to subtract the input from @@ -11617,6 +11746,18 @@ table_cnt([
]) : integer stick-table or in the designated stick-table. See also src_conn_cnt and table_avl for other entry counting methods. +var() : undefined + Returns a variable with the stored type. If the variable is not set, the + sample fetch fails. The name of the variable starts by an indication about its + scope. The scope allowed are: + "sess" : the variable is shared with all the session, + "txn" : the variable is shared with all the transaction (request and + response), + "req" : the variable is shared only during the request processing, + "res" : the variable is shared only during the response processing. + This prefix is followed by a name. The separator is a '.'. The name may only + contain characters 'a-z', 'A-Z', '0-9' and '_'. + 7.3.3. Fetching samples at Layer 4 ---------------------------------- diff --git a/include/proto/vars.h b/include/proto/vars.h new file mode 100644 index 000000000..dadbf3b14 --- /dev/null +++ b/include/proto/vars.h @@ -0,0 +1,10 @@ +#ifndef _PROTO_VARS_H +#define _PROTO_VARS_H + +#include + +void vars_init(struct vars *vars, enum vars_scope scope); +void vars_prune(struct vars *vars, struct stream *strm); +int vars_check_arg(struct arg *arg, char **err); + +#endif diff --git a/include/types/arg.h b/include/types/arg.h index c62102578..cccc5655e 100644 --- a/include/types/arg.h +++ b/include/types/arg.h @@ -28,6 +28,8 @@ #include #include +#include + /* encoding of each arg type : up to 31 types are supported */ #define ARGT_BITS 5 #define ARGT_NBTYPES (1 << ARGT_BITS) @@ -58,6 +60,7 @@ enum { ARGT_USR, /* a pointer to a user list */ ARGT_MAP, /* a pointer to a map descriptor */ ARGT_REG, /* a pointer to a regex */ + ARGT_VAR, /* contains a variable description. */ }; /* context where arguments are used, in order to help error reporting */ @@ -94,6 +97,7 @@ union arg_data { struct userlist *usr; struct map_descriptor *map; struct my_regex *reg; + struct var_desc var; }; struct arg { diff --git a/include/types/stream.h b/include/types/stream.h index 4203af8db..953f9d1d7 100644 --- a/include/types/stream.h +++ b/include/types/stream.h @@ -43,6 +43,7 @@ #include #include #include +#include /* Various Stream Flags, bits values 0x01 to 0x100 (shift 0) */ @@ -143,6 +144,9 @@ struct stream { char **req_cap; /* array of captures from the request (may be NULL) */ char **res_cap; /* array of captures from the response (may be NULL) */ + struct vars vars_sess; /* list of variables for the session scope. */ + struct vars vars_txn; /* list of variables for the txn scope. */ + struct vars vars_reqres; /* list of variables for the request and resp scope. */ struct stream_interface si[2]; /* client and server stream interfaces */ struct strm_logs logs; /* logs for this stream */ diff --git a/include/types/vars.h b/include/types/vars.h new file mode 100644 index 000000000..c387a7776 --- /dev/null +++ b/include/types/vars.h @@ -0,0 +1,33 @@ +#ifndef _TYPES_VARS_H +#define _TYPES_VARS_H + +#include + +#include + +enum vars_scope { + SCOPE_SESS = 0, + SCOPE_TXN, + SCOPE_REQ, + SCOPE_RES, +}; + +struct vars { + struct list head; + enum vars_scope scope; + unsigned int size; +}; + +/* This struct describes a variable. */ +struct var_desc { + const char *name; /* Contains the normalized variable name. */ + enum vars_scope scope; +}; + +struct var { + struct list l; /* Used for chaining vars. */ + const char *name; /* Contains the variable name. */ + struct sample_storage data; /* data storage. */ +}; + +#endif diff --git a/src/proto_http.c b/src/proto_http.c index 5b06233c6..1c30b3bf4 100644 --- a/src/proto_http.c +++ b/src/proto_http.c @@ -65,6 +65,7 @@ #include #include #include +#include const char HTTP_100[] = "HTTP/1.1 100 Continue\r\n\r\n"; @@ -9006,6 +9007,9 @@ void http_init_txn(struct stream *s) if (txn->hdr_idx.v) hdr_idx_init(&txn->hdr_idx); + + vars_init(&s->vars_txn, SCOPE_TXN); + vars_init(&s->vars_reqres, SCOPE_REQ); } /* to be used at the end of a transaction */ @@ -9047,6 +9051,8 @@ void http_end_txn(struct stream *s) memset(s->res_cap, 0, fe->nb_rsp_cap * sizeof(void *)); } + vars_prune(&s->vars_txn, s); + vars_prune(&s->vars_reqres, s); } /* to be used at the end of a transaction to prepare a new one */ @@ -9533,7 +9539,7 @@ struct http_req_rule *parse_http_req_cond(const char **args, const char *file, i goto out_err; } } else { - Alert("parsing [%s:%d]: 'http-request' expects 'allow', 'deny', 'auth', 'redirect', 'tarpit', 'add-header', 'set-header', 'replace-header', 'replace-value', 'set-nice', 'set-tos', 'set-mark', 'set-log-level', 'add-acl', 'del-acl', 'del-map', 'set-map', but got '%s'%s.\n", + Alert("parsing [%s:%d]: 'http-request' expects 'allow', 'deny', 'auth', 'redirect', 'tarpit', 'add-header', 'set-header', 'replace-header', 'replace-value', 'set-nice', 'set-tos', 'set-mark', 'set-log-level', 'add-acl', 'del-acl', 'del-map', 'set-map', 'set-var', but got '%s'%s.\n", file, linenum, args[0], *args[0] ? "" : " (missing argument)"); goto out_err; } @@ -9888,7 +9894,7 @@ struct http_res_rule *parse_http_res_cond(const char **args, const char *file, i goto out_err; } } else { - Alert("parsing [%s:%d]: 'http-response' expects 'allow', 'deny', 'redirect', 'add-header', 'del-header', 'set-header', 'replace-header', 'replace-value', 'set-nice', 'set-tos', 'set-mark', 'set-log-level', 'del-acl', 'add-acl', 'del-map', 'set-map', but got '%s'%s.\n", + Alert("parsing [%s:%d]: 'http-response' expects 'allow', 'deny', 'redirect', 'add-header', 'del-header', 'set-header', 'replace-header', 'replace-value', 'set-nice', 'set-tos', 'set-mark', 'set-log-level', 'del-acl', 'add-acl', 'del-map', 'set-map', 'set-var' but got '%s'%s.\n", file, linenum, args[0], *args[0] ? "" : " (missing argument)"); goto out_err; } diff --git a/src/proto_tcp.c b/src/proto_tcp.c index 036191b48..737a32ee0 100644 --- a/src/proto_tcp.c +++ b/src/proto_tcp.c @@ -1473,7 +1473,7 @@ static int tcp_parse_response_rule(char **args, int arg, int section_type, return -1; } else { memprintf(err, - "'%s %s' expects 'accept', 'close' or 'reject' in %s '%s' (got '%s')", + "'%s %s' expects 'accept', 'close', 'reject' or 'set-var' in %s '%s' (got '%s')", args[0], args[1], proxy_type_str(curpx), curpx->id, args[arg]); return -1; } @@ -1678,8 +1678,8 @@ static int tcp_parse_request_rule(char **args, int arg, int section_type, return -1; } else { memprintf(err, - "'%s %s' expects 'accept', 'reject', 'track-sc0' ... 'track-sc%d' " - " in %s '%s' (got '%s')", + "'%s %s' expects 'accept', 'reject', 'track-sc0' ... 'track-sc%d', " + " or 'set-var' in %s '%s' (got '%s')", args[0], args[1], MAX_SESS_STKCTR-1, proxy_type_str(curpx), curpx->id, args[arg]); return -1; diff --git a/src/stream.c b/src/stream.c index 0f9fbe25a..0b70e28b4 100644 --- a/src/stream.c +++ b/src/stream.c @@ -50,6 +50,7 @@ #include #include #include +#include struct pool_head *pool2_stream; struct list streams; @@ -136,6 +137,13 @@ struct stream *stream_new(struct session *sess, struct task *t, enum obj_type *o s->req_cap = NULL; s->res_cap = NULL; + /* Initialise alle the variable context even if will not use. + * This permits to prune these context without errors. + */ + vars_init(&s->vars_sess, SCOPE_SESS); + vars_init(&s->vars_txn, SCOPE_TXN); + vars_init(&s->vars_reqres, SCOPE_REQ); + /* this part should be common with other protocols */ si_reset(&s->si[0]); si_set_state(&s->si[0], SI_ST_EST); @@ -293,6 +301,11 @@ static void stream_free(struct stream *s) pool_free2(fe->req_cap_pool, s->req_cap); } + /* Cleanup all variable contexts. */ + vars_prune(&s->vars_sess, s); + vars_prune(&s->vars_txn, s); + vars_prune(&s->vars_reqres, s); + stream_store_counters(s); list_for_each_entry_safe(bref, back, &s->back_refs, users) { @@ -2060,6 +2073,13 @@ struct task *process_stream(struct task *t) * for completion. */ if (si_b->state >= SI_ST_REQ && si_b->state < SI_ST_CON) { + + /* prune the request variables and swap to the response variables. */ + if (s->vars_reqres.scope != SCOPE_RES) { + vars_prune(&s->vars_reqres, s); + vars_init(&s->vars_reqres, SCOPE_RES); + } + do { /* nb: step 1 might switch from QUE to ASS, but we first want * to give a chance to step 2 to perform a redirect if needed. diff --git a/src/vars.c b/src/vars.c new file mode 100644 index 000000000..74033a52e --- /dev/null +++ b/src/vars.c @@ -0,0 +1,685 @@ +#include + +#include +#include + +#include + +#include +#include +#include +#include +#include + +/* This contains a pool of struct vars */ +static struct pool_head *var_pool = NULL; + +/* This array contain all the names of all the HAProxy vars. + * This permits to identify two variables name with + * only one pointer. It permits to not using strdup() for + * each variable name used during the runtime. + */ +static char **var_names = NULL; +static int var_names_nb = 0; + +/* This array of int contains the system limits per context. */ +static unsigned int var_global_limit = 0; +static unsigned int var_global_size = 0; +static unsigned int var_sess_limit = 0; +static unsigned int var_txn_limit = 0; +static unsigned int var_reqres_limit = 0; + +/* This function adds or remove memory size from the accounting. */ +static void var_accounting_diff(struct vars *vars, struct stream *strm, int size) +{ + switch (vars->scope) { + case SCOPE_REQ: + case SCOPE_RES: + strm->vars_reqres.size += size; + case SCOPE_TXN: + strm->vars_txn.size += size; + case SCOPE_SESS: + strm->vars_sess.size += size; + var_global_size += size; + } +} + +/* This function returns 1 if the is available in the var + * pool , otherwise returns 0. If the space is avalaible, + * the size is reserved. + */ +static int var_accounting_add(struct vars *vars, struct stream *strm, int size) +{ + switch (vars->scope) { + case SCOPE_REQ: + case SCOPE_RES: + if (var_reqres_limit && strm->vars_reqres.size + size > var_reqres_limit) + return 0; + case SCOPE_TXN: + if (var_txn_limit && strm->vars_txn.size + size > var_txn_limit) + return 0; + case SCOPE_SESS: + if (var_sess_limit && strm->vars_sess.size + size > var_sess_limit) + return 0; + if (var_global_limit && var_global_size + size > var_global_limit) + return 0; + } + var_accounting_diff(vars, strm, size); + return 1; +} + +/* This function free all the memory used by all the varaibles + * in the list. + */ +void vars_prune(struct vars *vars, struct stream *strm) +{ + struct var *var, *tmp; + + list_for_each_entry_safe(var, tmp, &vars->head, l) { + if (var->data.type == SMP_T_STR || + var->data.type == SMP_T_BIN) { + free(var->data.data.str.str); + var_accounting_diff(vars, strm, -var->data.data.str.len); + } + else if (var->data.type == SMP_T_METH) { + free(var->data.data.meth.str.str); + var_accounting_diff(vars, strm, -var->data.data.meth.str.len); + } + LIST_DEL(&var->l); + pool_free2(var_pool, var); + var_accounting_diff(vars, strm, -(int)sizeof(struct var)); + } +} + +/* This function init a list of variabes. */ +void vars_init(struct vars *vars, enum vars_scope scope) +{ + LIST_INIT(&vars->head); + vars->scope = scope; + vars->size = 0; +} + +/* This function declares a new variable name. It returns a pointer + * on the string identifying the name. This function assures that + * the same name exists only once. + * + * This function check if the variable name is acceptable. + * + * The function returns NULL if an error occurs, and is filled. + * In this case, the HAProxy must be stopped because the structs are + * left inconsistent. Otherwise, it returns the pointer on the global + * name. + */ +static char *register_name(const char *name, int len, enum vars_scope *scope, char **err) +{ + int i; + const char *tmp; + + /* Check length. */ + if (len == 0) { + memprintf(err, "Empty variable name cannot be accepted"); + return NULL; + } + + /* Check scope. */ + if (len > 5 && strncmp(name, "sess.", 5) == 0) { + name += 5; + len -= 5; + *scope = SCOPE_SESS; + } + else if (len > 4 && strncmp(name, "txn.", 4) == 0) { + name += 4; + len -= 4; + *scope = SCOPE_TXN; + } + else if (len > 4 && strncmp(name, "req.", 4) == 0) { + name += 4; + len -= 4; + *scope = SCOPE_REQ; + } + else if (len > 4 && strncmp(name, "res.", 4) == 0) { + name += 4; + len -= 4; + *scope = SCOPE_RES; + } + else { + memprintf(err, "invalid variable name '%s'. A variable name must be start by its scope. " + "The scope can be 'sess', 'txn', 'req' or 'res'", name); + return NULL; + } + + /* Look for existing variable name. */ + for (i = 0; i < var_names_nb; i++) + if (strncmp(var_names[i], name, len) == 0) + return var_names[i]; + + /* Store variable name. */ + var_names_nb++; + var_names = realloc(var_names, var_names_nb * sizeof(*var_names)); + if (!var_names) { + memprintf(err, "out of memory error"); + return NULL; + } + var_names[var_names_nb - 1] = malloc(len + 1); + if (!var_names[var_names_nb - 1]) { + memprintf(err, "out of memory error"); + return NULL; + } + memcpy(var_names[var_names_nb - 1], name, len); + var_names[var_names_nb - 1][len] = '\0'; + + /* Check variable name syntax. */ + tmp = var_names[var_names_nb - 1]; + while (*tmp) { + if (!isalnum((int)(unsigned char)*tmp) && *tmp != '_') { + memprintf(err, "invalid syntax at char '%s'", tmp); + return NULL; + } + tmp++; + } + + /* Return the result. */ + return var_names[var_names_nb - 1]; +} + +/* This function returns an existing variable or returns NULL. */ +static inline struct var *var_get(struct vars *vars, const char *name) +{ + struct var *var; + + list_for_each_entry(var, &vars->head, l) + if (var->name == name) + return var; + return NULL; +} + +/* Returns 0 if fails, else returns 1. */ +static int smp_fetch_var(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + const struct var_desc *var_desc = &args[0].data.var; + struct var *var; + struct vars *vars; + + /* Check the availibity of the variable. */ + switch (var_desc->scope) { + case SCOPE_SESS: vars = &smp->strm->vars_sess; break; + case SCOPE_TXN: vars = &smp->strm->vars_txn; break; + case SCOPE_REQ: + case SCOPE_RES: vars = &smp->strm->vars_reqres; break; + } + if (vars->scope != var_desc->scope) + return 0; + var = var_get(vars, var_desc->name); + + /* check for the variable avalaibility */ + if (!var) + return 0; + + /* Copy sample. */ + smp->type = var->data.type; + smp->flags |= SMP_F_CONST; + memcpy(&smp->data, &var->data.data, sizeof(smp->data)); + return 1; +} + +/* This function search in the a variable with the same + * pointer value that the . If the variable doesn't exists, + * create it. The function stores a copy of smp> if the variable. + * It returns 0 if fails, else returns 1. + */ +static int sample_store(struct vars *vars, const char *name, struct stream *strm, struct sample *smp) +{ + struct var *var; + + /* Look for existing variable name. */ + var = var_get(vars, name); + + if (var) { + /* free its used memory. */ + if (var->data.type == SMP_T_STR || + var->data.type == SMP_T_BIN) { + free(var->data.data.str.str); + var_accounting_diff(vars, strm, -var->data.data.str.len); + } + else if (var->data.type == SMP_T_METH) { + free(var->data.data.meth.str.str); + var_accounting_diff(vars, strm, -var->data.data.meth.str.len); + } + } else { + + /* Check memory avalaible. */ + if (!var_accounting_add(vars, strm, sizeof(struct var))) + return 0; + + /* Create new entry. */ + var = pool_alloc2(var_pool); + if (!var) + return 0; + LIST_ADDQ(&vars->head, &var->l); + var->name = name; + } + + /* Set type. */ + var->data.type = smp->type; + + /* Copy data. If the data needs memory, the function can fail. */ + switch (var->data.type) { + case SMP_T_BOOL: + case SMP_T_UINT: + case SMP_T_SINT: + var->data.data.sint = smp->data.sint; + break; + case SMP_T_IPV4: + var->data.data.ipv4 = smp->data.ipv4; + break; + case SMP_T_IPV6: + var->data.data.ipv6 = smp->data.ipv6; + break; + case SMP_T_STR: + case SMP_T_BIN: + if (!var_accounting_add(vars, strm, smp->data.str.len)) { + var->data.type = SMP_T_BOOL; /* This type doesn't use additional memory. */ + return 0; + } + var->data.data.str.str = malloc(smp->data.str.len); + if (!var->data.data.str.str) { + var_accounting_diff(vars, strm, -smp->data.str.len); + var->data.type = SMP_T_BOOL; /* This type doesn't use additional memory. */ + return 0; + } + var->data.data.str.len = smp->data.str.len; + memcpy(var->data.data.str.str, smp->data.str.str, var->data.data.str.len); + break; + case SMP_T_METH: + if (!var_accounting_add(vars, strm, smp->data.meth.str.len)) { + var->data.type = SMP_T_BOOL; /* This type doesn't use additional memory. */ + return 0; + } + var->data.data.meth.str.str = malloc(smp->data.meth.str.len); + if (!var->data.data.meth.str.str) { + var_accounting_diff(vars, strm, -smp->data.meth.str.len); + var->data.type = SMP_T_BOOL; /* This type doesn't use additional memory. */ + return 0; + } + var->data.data.meth.meth = smp->data.meth.meth; + var->data.data.meth.str.len = smp->data.meth.str.len; + var->data.data.meth.str.size = smp->data.meth.str.len; + memcpy(var->data.data.meth.str.str, smp->data.meth.str.str, var->data.data.meth.str.len); + break; + } + return 1; +} + +/* Returns 0 if fails, else returns 1. */ +static inline int sample_store_stream(const char *name, enum vars_scope scope, + struct stream *strm, struct sample *smp) +{ + struct vars *vars; + + switch (scope) { + case SCOPE_SESS: vars = &strm->vars_sess; break; + case SCOPE_TXN: vars = &strm->vars_txn; break; + case SCOPE_REQ: + case SCOPE_RES: vars = &strm->vars_reqres; break; + } + if (vars->scope != scope) + return 0; + return sample_store(vars, name, strm, smp); +} + +/* Returns 0 if fails, else returns 1. */ +static int smp_conv_store(const struct arg *args, struct sample *smp, void *private) +{ + return sample_store_stream(args[0].data.var.name, args[1].data.var.scope, smp->strm, smp); +} + +/* This fucntions check an argument entry and fill it with a variable + * type. The argumen must be a string. If the variable lookup fails, + * the function retuns 0 and fill , otherwise it returns 1. + */ +int vars_check_arg(struct arg *arg, char **err) +{ + char *name; + enum vars_scope scope; + + /* Check arg type. */ + if (arg->type != ARGT_STR) { + memprintf(err, "unexpected argument type"); + return 0; + } + + /* Register new variable name. */ + name = register_name(arg->data.str.str, arg->data.str.len, &scope, err); + if (!name) + return 0; + + /* Use the global variable name pointer. */ + arg->type = ARGT_VAR; + arg->data.var.name = name; + arg->data.var.scope = scope; + return 1; +} + +/* Returns 0 if miss data, else returns 1. */ +static inline int action_store(struct sample_expr *expr, const char *name, + enum vars_scope scope, struct proxy *px, + struct stream *s, int sens) +{ + struct sample smp; + + /* Process the expression. */ + memset(&smp, 0, sizeof(smp)); + if (!sample_process(px, s->sess, s, sens|SMP_OPT_FINAL, expr, &smp)) + return 0; + + /* Store the sample, and ignore errors. */ + sample_store_stream(name, scope, s, &smp); + return 1; +} + +/* Returns 0 if miss data, else returns 1. */ +static int action_tcp_req_store(struct tcp_rule *rule, struct proxy *px, struct stream *s) +{ + struct sample_expr *expr = rule->act_prm.data[0]; + const char *name = rule->act_prm.data[1]; + int scope = (long)rule->act_prm.data[2]; + + return action_store(expr, name, scope, px, s, SMP_OPT_DIR_REQ); +} + +/* Returns 0 if miss data, else returns 1. */ +static int action_tcp_res_store(struct tcp_rule *rule, struct proxy *px, struct stream *s) +{ + struct sample_expr *expr = rule->act_prm.data[0]; + const char *name = rule->act_prm.data[1]; + int scope = (long)rule->act_prm.data[2]; + + return action_store(expr, name, scope, px, s, SMP_OPT_DIR_RES); +} + +/* Returns 0 if miss data, else returns 1. */ +static int action_http_req_store(struct http_req_rule *rule, struct proxy *px, struct stream *s) +{ + struct sample_expr *expr = rule->arg.act.p[0]; + const char *name = rule->arg.act.p[1]; + int scope = (long)rule->arg.act.p[2]; + + return action_store(expr, name, scope, px, s, SMP_OPT_DIR_REQ); +} + +/* Returns 0 if miss data, else returns 1. */ +static int action_http_res_store(struct http_res_rule *rule, struct proxy *px, struct stream *s) +{ + struct sample_expr *expr = rule->arg.act.p[0]; + const char *name = rule->arg.act.p[1]; + int scope = (long)rule->arg.act.p[2]; + + return action_store(expr, name, scope, px, s, SMP_OPT_DIR_RES); +} + +/* This two function checks the variable name and replace the + * configuration string name by the global string name. its + * the same string, but the global pointer can be easy to + * compare. + * + * The first function checks a sample-fetch and the second + * checks a converter. + */ +static int smp_check_var(struct arg *args, char **err) +{ + return vars_check_arg(&args[0], err); +} + +static int conv_check_var(struct arg *args, struct sample_conv *conv, + const char *file, int line, char **err_msg) +{ + return vars_check_arg(&args[0], err_msg); +} + +/* This function is a common parser for using variables. It understands + * the format: + * + * set-var() + * + * It returns 0 if fails and is filled with an error message. Otherwise, + * it returns 1 and the variable is filled with the pointer to the + * expression to execute. + */ +static int parse_vars(const char **args, int *arg, struct proxy *px, + struct sample_expr **expr, char **name, + enum vars_scope *scope, char **err) +{ + const char *var_name = args[*arg-1]; + int var_len; + + var_name += strlen("set-var"); + if (*var_name != '(') { + memprintf(err, "invalid variable '%s'. Expects 'set-var()'", args[*arg-1]); + return 0; + } + var_name++; /* jump the '(' */ + var_len = strlen(var_name); + var_len--; /* remove the ')' */ + if (var_name[var_len] != ')') { + memprintf(err, "invalid variable '%s'. Expects 'set-var()'", args[*arg-1]); + return 0; + } + + *name = register_name(var_name, var_len, scope, err); + if (!*name) + return 0; + + *expr = sample_parse_expr((char **)args, arg, px->conf.args.file, px->conf.args.line, + err, &px->conf.args); + if (!*expr) + return 0; + + return 1; +} + +static int parse_tcp_req_store(const char **args, int *arg, struct proxy *px, + struct tcp_rule *rule, char **err) +{ + struct sample_expr *expr; + int cur_arg = *arg; + char *name; + enum vars_scope scope; + + if (!parse_vars(args, arg, px, &expr, &name, &scope, err)) + return 0; + + if (!(expr->fetch->val & SMP_VAL_FE_REQ_CNT)) { + memprintf(err, + "fetch method '%s' extracts information from '%s', none of which is available here", + args[cur_arg-1], sample_src_names(expr->fetch->use)); + free(expr); + return 0; + } + + rule->action = TCP_ACT_CUSTOM_CONT; + rule->action_ptr = action_tcp_req_store; + rule->act_prm.data[0] = expr; + rule->act_prm.data[1] = name; + rule->act_prm.data[2] = (void *)(long)scope; + + return 1; +} + +static int parse_tcp_res_store(const char **args, int *arg, struct proxy *px, + struct tcp_rule *rule, char **err) +{ + struct sample_expr *expr; + int cur_arg = *arg; + char *name; + enum vars_scope scope; + + if (!parse_vars(args, arg, px, &expr, &name, &scope, err)) + return 0; + + if (!(expr->fetch->val & SMP_VAL_BE_RES_CNT)) { + memprintf(err, + "fetch method '%s' extracts information from '%s', none of which is available here", + args[cur_arg-1], sample_src_names(expr->fetch->use)); + free(expr); + return 0; + } + + rule->action = TCP_ACT_CUSTOM_CONT; + rule->action_ptr = action_tcp_res_store; + rule->act_prm.data[0] = expr; + rule->act_prm.data[1] = name; + rule->act_prm.data[2] = (void *)(long)scope; + + return 1; +} + +static int parse_http_req_store(const char **args, int *arg, struct proxy *px, + struct http_req_rule *rule, char **err) +{ + struct sample_expr *expr; + int cur_arg = *arg; + char *name; + enum vars_scope scope; + + if (!parse_vars(args, arg, px, &expr, &name, &scope, err)) + return -1; + + if (!(expr->fetch->val & SMP_VAL_FE_HRQ_HDR)) { + memprintf(err, + "fetch method '%s' extracts information from '%s', none of which is available here", + args[cur_arg-1], sample_src_names(expr->fetch->use)); + free(expr); + return -1; + } + + rule->action = HTTP_REQ_ACT_CUSTOM_CONT; + rule->action_ptr = action_http_req_store; + rule->arg.act.p[0] = expr; + rule->arg.act.p[1] = name; + rule->arg.act.p[2] = (void *)(long)scope; + + return 0; +} + +static int parse_http_res_store(const char **args, int *arg, struct proxy *px, + struct http_res_rule *rule, char **err) +{ + struct sample_expr *expr; + int cur_arg = *arg; + char *name; + enum vars_scope scope; + + if (!parse_vars(args, arg, px, &expr, &name, &scope, err)) + return -1; + + if (!(expr->fetch->val & SMP_VAL_BE_HRS_HDR)) { + memprintf(err, + "fetch method '%s' extracts information from '%s', none of which is available here", + args[cur_arg-1], sample_src_names(expr->fetch->use)); + free(expr); + return -1; + } + + rule->action = HTTP_RES_ACT_CUSTOM_CONT; + rule->action_ptr = action_http_res_store; + rule->arg.act.p[0] = expr; + rule->arg.act.p[1] = name; + rule->arg.act.p[2] = (void *)(long)scope; + + return 0; +} + +static int vars_max_size(char **args, int section_type, struct proxy *curpx, + struct proxy *defpx, const char *file, int line, + char **err, unsigned int *limit) +{ + char *error; + + *limit = strtol(args[1], &error, 10); + if (*error != 0) { + memprintf(err, "%s: '%s' is an invalid size", args[0], args[1]); + return -1; + } + return 0; +} + +static int vars_max_size_global(char **args, int section_type, struct proxy *curpx, + struct proxy *defpx, const char *file, int line, + char **err) +{ + return vars_max_size(args, section_type, curpx, defpx, file, line, err, &var_global_limit); +} + +static int vars_max_size_sess(char **args, int section_type, struct proxy *curpx, + struct proxy *defpx, const char *file, int line, + char **err) +{ + return vars_max_size(args, section_type, curpx, defpx, file, line, err, &var_sess_limit); +} + +static int vars_max_size_txn(char **args, int section_type, struct proxy *curpx, + struct proxy *defpx, const char *file, int line, + char **err) +{ + return vars_max_size(args, section_type, curpx, defpx, file, line, err, &var_txn_limit); +} + +static int vars_max_size_reqres(char **args, int section_type, struct proxy *curpx, + struct proxy *defpx, const char *file, int line, + char **err) +{ + return vars_max_size(args, section_type, curpx, defpx, file, line, err, &var_reqres_limit); +} + +static struct sample_fetch_kw_list sample_fetch_keywords = {ILH, { + + { "var", smp_fetch_var, ARG1(1,STR), smp_check_var, SMP_T_STR, SMP_USE_HTTP_ANY }, + { /* END */ }, +}}; + +static struct sample_conv_kw_list sample_conv_kws = {ILH, { + { "set-var", smp_conv_store, ARG1(1,STR), conv_check_var, SMP_T_ANY, SMP_T_ANY }, + { /* END */ }, +}}; + +static struct tcp_action_kw_list tcp_req_kws = {"vars", { }, { + { "set-var", parse_tcp_req_store, 1 }, + { /* END */ } +}}; + +static struct tcp_action_kw_list tcp_res_kws = {"vars", { }, { + { "set-var", parse_tcp_res_store, 1 }, + { /* END */ } +}}; + +static struct http_req_action_kw_list http_req_kws = {"vars", { }, { + { "set-var", parse_http_req_store, 1 }, + { /* END */ } +}}; + +static struct http_res_action_kw_list http_res_kws = {"vars", { }, { + { "set-var", parse_http_res_store, 1 }, + { /* END */ } +}}; + +static struct cfg_kw_list cfg_kws = {{ },{ + { CFG_GLOBAL, "tune.vars.global-max-size", vars_max_size_global }, + { CFG_GLOBAL, "tune.vars.sess-max-size", vars_max_size_sess }, + { CFG_GLOBAL, "tune.vars.txn-max-size", vars_max_size_txn }, + { CFG_GLOBAL, "tune.vars.reqres-max-size", vars_max_size_reqres }, + { /* END */ } +}}; + +__attribute__((constructor)) +static void __http_protocol_init(void) +{ + var_pool = create_pool("vars", sizeof(struct var), MEM_F_SHARED); + + sample_register_fetches(&sample_fetch_keywords); + sample_register_convs(&sample_conv_kws); + tcp_req_cont_keywords_register(&tcp_req_kws); + tcp_res_cont_keywords_register(&tcp_res_kws); + http_req_keywords_register(&http_req_kws); + http_res_keywords_register(&http_res_kws); + cfg_register_keywords(&cfg_kws); +}