diff --git a/include/proto/acl.h b/include/proto/acl.h index edd464803..6859b3d1c 100644 --- a/include/proto/acl.h +++ b/include/proto/acl.h @@ -59,7 +59,7 @@ struct acl_keyword *find_acl_kw(const char *kw); * Right now, the only accepted syntax is : * [...] */ -struct acl_expr *parse_acl_expr(const char **args, char **err); +struct acl_expr *parse_acl_expr(const char **args, char **err, struct arg_list *al); /* Purge everything in the acl , then return . */ struct acl *prune_acl(struct acl *acl); @@ -70,7 +70,7 @@ struct acl *prune_acl(struct acl *acl); * * args syntax: */ -struct acl *parse_acl(const char **args, struct list *known_acl, char **err); +struct acl *parse_acl(const char **args, struct list *known_acl, char **err, struct arg_list *al); /* Purge everything in the acl_cond , then return . */ struct acl_cond *prune_acl_cond(struct acl_cond *cond); @@ -79,7 +79,7 @@ struct acl_cond *prune_acl_cond(struct acl_cond *cond); * known ACLs passed in . The new condition is returned (or NULL in * case of low memory). Supports multiple conditions separated by "or". */ -struct acl_cond *parse_acl_cond(const char **args, struct list *known_acl, int pol, char **err); +struct acl_cond *parse_acl_cond(const char **args, struct list *known_acl, int pol, char **err, struct arg_list *al); /* Builds an ACL condition starting at the if/unless keyword. The complete * condition is returned. NULL is returned in case of error or if the first diff --git a/include/proto/arg.h b/include/proto/arg.h index 8e0a81b07..78c1105ab 100644 --- a/include/proto/arg.h +++ b/include/proto/arg.h @@ -59,8 +59,11 @@ */ extern struct arg empty_arg_list[8]; +struct arg_list *arg_list_clone(const struct arg_list *orig); +struct arg_list *arg_list_add(struct arg_list *orig, struct arg *arg, int pos); int make_arg_list(const char *in, int len, unsigned int mask, struct arg **argp, - char **err_msg, const char **err_ptr, int *err_arg); + char **err_msg, const char **err_ptr, int *err_arg, + struct arg_list *al); #endif /* _PROTO_ARG_H */ diff --git a/include/proto/sample.h b/include/proto/sample.h index a5696f6fb..3043662d9 100644 --- a/include/proto/sample.h +++ b/include/proto/sample.h @@ -26,7 +26,7 @@ #include #include -struct sample_expr *sample_parse_expr(char **str, int *idx, char *err, int err_size); +struct sample_expr *sample_parse_expr(char **str, int *idx, char *err, int err_size, struct arg_list *al); struct sample *sample_process(struct proxy *px, struct session *l4, void *l7, unsigned int dir, struct sample_expr *expr, struct sample *p); @@ -37,5 +37,6 @@ void sample_register_convs(struct sample_conv_kw_list *psl); const char *sample_src_names(unsigned int use); const char *sample_ckp_names(unsigned int use); struct sample_fetch *find_sample_fetch(const char *kw, int len); +int smp_resolve_args(struct proxy *p); #endif /* _PROTO_SAMPLE_H */ diff --git a/include/types/arg.h b/include/types/arg.h index 666a51f0f..a53d29375 100644 --- a/include/types/arg.h +++ b/include/types/arg.h @@ -26,6 +26,7 @@ #include #include +#include enum { ARGT_STOP = 0, /* end of the arg list */ @@ -47,6 +48,16 @@ enum { ARGT_NBTYPES /* no more values past 15 */ }; +/* context where arguments are used, in order to help error reporting */ +enum { + ARGC_ACL = 0, /* ACL */ + ARGC_STK, /* sticking rule */ + ARGC_TRK, /* tracking rule */ + ARGC_LOG, /* log-format */ + ARGC_HDR, /* add-header */ + ARGC_UIF, /* unique-id-format */ +}; + /* some types that are externally defined */ struct proxy; struct server; @@ -69,6 +80,21 @@ struct arg { union arg_data data; /* argument data */ }; +/* arg lists are used to store information about arguments that could not be + * resolved when parsing the configuration. The head is an arg_list which + * serves as a template to create new entries. Nothing here is allocated, + * so plain copies are OK. + */ +struct arg_list { + struct list list; /* chaining with other arg_list, or list head */ + struct arg *arg; /* pointer to the arg, NULL on list head */ + int arg_pos; /* argument position */ + int ctx; /* context where the arg is used (ARGC_*) */ + const char *kw; /* keyword making use of these args */ + const char *conv; /* conv keyword when in conv, otherwise NULL */ + const char *file; /* file name where the args are referenced */ + int line; /* line number where the args are referenced */ +}; #endif /* _TYPES_ARG_H */ diff --git a/include/types/proxy.h b/include/types/proxy.h index 5abf35e34..5729db80d 100644 --- a/include/types/proxy.h +++ b/include/types/proxy.h @@ -350,6 +350,7 @@ struct proxy { struct eb_root used_server_id; /* list of server IDs in use */ struct list bind; /* list of bind settings */ struct list listeners; /* list of listeners belonging to this frontend */ + struct arg_list args; /* sample arg list that need to be resolved */ } conf; /* config information */ void *parent; /* parent of the proxy when applicable */ struct comp *comp; /* http compression */ diff --git a/src/acl.c b/src/acl.c index 4a5307202..efd1ee662 100644 --- a/src/acl.c +++ b/src/acl.c @@ -1024,12 +1024,13 @@ static int acl_read_patterns_from_file(struct acl_expr *expr, /* Parse an ACL expression starting at [0], and return it. If is * not NULL, it will be filled with a pointer to an error message in case of - * error. This pointer must be freeable or NULL. + * error. This pointer must be freeable or NULL. is an arg_list serving + * as a list head to report missing dependencies. * * Right now, the only accepted syntax is : * [...] */ -struct acl_expr *parse_acl_expr(const char **args, char **err) +struct acl_expr *parse_acl_expr(const char **args, char **err, struct arg_list *al) { __label__ out_return, out_free_expr, out_free_pattern; struct acl_expr *expr; @@ -1088,10 +1089,14 @@ struct acl_expr *parse_acl_expr(const char **args, char **err) /* Parse the arguments. Note that currently we have no way to * report parsing errors, hence the NULL in the error pointers. * An error is also reported if some mandatory arguments are - * missing. + * missing. We prepare the args list to report unresolved + * dependencies. */ + al->ctx = ARGC_ACL; + al->kw = expr->kw; + al->conv = NULL; nbargs = make_arg_list(arg, end - arg, expr->smp->arg_mask, &expr->args, - err, NULL, NULL); + err, NULL, NULL, al); if (nbargs < 0) { /* note that make_arg_list will have set here */ memprintf(err, "in argument to '%s', %s", expr->kw, *err); @@ -1273,11 +1278,12 @@ struct acl *prune_acl(struct acl *acl) { * A pointer to that ACL is returned. If the ACL has an empty name, then it's * an anonymous one and it won't be merged with any other one. If is not * NULL, it will be filled with an appropriate error. This pointer must be - * freeable or NULL. + * freeable or NULL. is the arg_list serving as a head for unresolved + * dependencies. * * args syntax: */ -struct acl *parse_acl(const char **args, struct list *known_acl, char **err) +struct acl *parse_acl(const char **args, struct list *known_acl, char **err, struct arg_list *al) { __label__ out_return, out_free_acl_expr, out_free_name; struct acl *cur_acl; @@ -1290,7 +1296,7 @@ struct acl *parse_acl(const char **args, struct list *known_acl, char **err) goto out_return; } - acl_expr = parse_acl_expr(args + 1, err); + acl_expr = parse_acl_expr(args + 1, err, al); if (!acl_expr) { /* parse_acl_expr will have filled here */ goto out_return; @@ -1381,9 +1387,11 @@ const struct { * except when default ACLs are broken, in which case it will return NULL. * If is not NULL, the ACL will be queued at its tail. If is * not NULL, it will be filled with an error message if an error occurs. This - * pointer must be freeable or NULL. + * pointer must be freeable or NULL. is an arg_list serving as a list head + * to report missing dependencies. */ -struct acl *find_acl_default(const char *acl_name, struct list *known_acl, char **err) +static struct acl *find_acl_default(const char *acl_name, struct list *known_acl, + char **err, struct arg_list *al) { __label__ out_return, out_free_acl_expr, out_free_name; struct acl *cur_acl; @@ -1401,7 +1409,7 @@ struct acl *find_acl_default(const char *acl_name, struct list *known_acl, char return NULL; } - acl_expr = parse_acl_expr((const char **)default_acl_list[index].expr, err); + acl_expr = parse_acl_expr((const char **)default_acl_list[index].expr, err, al); if (!acl_expr) { /* parse_acl_expr must have filled err here */ goto out_return; @@ -1458,9 +1466,11 @@ struct acl_cond *prune_acl_cond(struct acl_cond *cond) * case of low memory). Supports multiple conditions separated by "or". If * is not NULL, it will be filled with a pointer to an error message in * case of error, that the caller is responsible for freeing. The initial - * location must either be freeable or NULL. + * location must either be freeable or NULL. The list serves as a list head + * for unresolved dependencies. */ -struct acl_cond *parse_acl_cond(const char **args, struct list *known_acl, int pol, char **err) +struct acl_cond *parse_acl_cond(const char **args, struct list *known_acl, + int pol, char **err, struct arg_list *al) { __label__ out_return, out_free_suite, out_free_term; int arg, neg; @@ -1533,7 +1543,7 @@ struct acl_cond *parse_acl_cond(const char **args, struct list *known_acl, int p args_new[0] = ""; memcpy(args_new + 1, args + arg + 1, (arg_end - arg) * sizeof(*args_new)); args_new[arg_end - arg] = ""; - cur_acl = parse_acl(args_new, known_acl, err); + cur_acl = parse_acl(args_new, known_acl, err, al); free(args_new); if (!cur_acl) { @@ -1551,7 +1561,7 @@ struct acl_cond *parse_acl_cond(const char **args, struct list *known_acl, int p */ cur_acl = find_acl_by_name(word, known_acl); if (cur_acl == NULL) { - cur_acl = find_acl_default(word, known_acl, err); + cur_acl = find_acl_default(word, known_acl, err, al); if (cur_acl == NULL) { /* note that find_acl_default() must have filled here */ goto out_free_suite; @@ -1635,7 +1645,7 @@ struct acl_cond *build_acl_cond(const char *file, int line, struct proxy *px, co return NULL; } - cond = parse_acl_cond(args, &px->acl, pol, err); + cond = parse_acl_cond(args, &px->acl, pol, err, &px->conf.args); if (!cond) { /* note that parse_acl_cond must have filled here */ return NULL; @@ -1838,191 +1848,29 @@ int acl_cond_kw_conflicts(const struct acl_cond *cond, unsigned int where, struc /* * Find targets for userlist and groups in acl. Function returns the number - * of errors or OK if everything is fine. + * of errors or OK if everything is fine. It must be called only once sample + * fetch arguments have been resolved (after smp_resolve_args()). */ -int -acl_find_targets(struct proxy *p) +int acl_find_targets(struct proxy *p) { struct acl *acl; struct acl_expr *expr; struct acl_pattern *pattern; - struct userlist *ul; - struct arg *arg; int cfgerr = 0; list_for_each_entry(acl, &p->acl, list) { list_for_each_entry(expr, &acl->expr, list) { - for (arg = expr->args; arg && arg->type != ARGT_STOP; arg++) { - if (!arg->unresolved) - continue; - else if (arg->type == ARGT_SRV) { - struct proxy *px; - struct server *srv; - char *pname, *sname; - - if (!arg->data.str.len) { - Alert("proxy %s: acl '%s' %s(): missing server name.\n", - p->id, acl->name, expr->kw); - cfgerr++; - continue; - } - - pname = arg->data.str.str; - sname = strrchr(pname, '/'); - - if (sname) - *sname++ = '\0'; - else { - sname = pname; - pname = NULL; - } - - px = p; - if (pname) { - px = findproxy(pname, PR_CAP_BE); - if (!px) { - Alert("proxy %s: acl '%s' %s(): unable to find proxy '%s'.\n", - p->id, acl->name, expr->kw, pname); - cfgerr++; - continue; - } - } - - srv = findserver(px, sname); - if (!srv) { - Alert("proxy %s: acl '%s' %s(): unable to find server '%s'.\n", - p->id, acl->name, expr->kw, sname); - cfgerr++; - continue; - } - - free(arg->data.str.str); - arg->data.str.str = NULL; - arg->unresolved = 0; - arg->data.srv = srv; - } - else if (arg->type == ARGT_FE) { - struct proxy *prx = p; - char *pname = p->id; - - if (arg->data.str.len) { - pname = arg->data.str.str; - prx = findproxy(pname, PR_CAP_FE); - } - - if (!prx) { - Alert("proxy %s: acl '%s' %s(): unable to find frontend '%s'.\n", - p->id, acl->name, expr->kw, pname); - cfgerr++; - continue; - } - - if (!(prx->cap & PR_CAP_FE)) { - Alert("proxy %s: acl '%s' %s(): proxy '%s' has no frontend capability.\n", - p->id, acl->name, expr->kw, pname); - cfgerr++; - continue; - } - - free(arg->data.str.str); - arg->data.str.str = NULL; - arg->unresolved = 0; - arg->data.prx = prx; - } - else if (arg->type == ARGT_BE) { - struct proxy *prx = p; - char *pname = p->id; - - if (arg->data.str.len) { - pname = arg->data.str.str; - prx = findproxy(pname, PR_CAP_BE); - } - - if (!prx) { - Alert("proxy %s: acl '%s' %s(): unable to find backend '%s'.\n", - p->id, acl->name, expr->kw, pname); - cfgerr++; - continue; - } - - if (!(prx->cap & PR_CAP_BE)) { - Alert("proxy %s: acl '%s' %s(): proxy '%s' has no backend capability.\n", - p->id, acl->name, expr->kw, pname); - cfgerr++; - continue; - } - - free(arg->data.str.str); - arg->data.str.str = NULL; - arg->unresolved = 0; - arg->data.prx = prx; - } - else if (arg->type == ARGT_TAB) { - struct proxy *prx = p; - char *pname = p->id; - - if (arg->data.str.len) { - pname = arg->data.str.str; - prx = find_stktable(pname); - } - - if (!prx) { - Alert("proxy %s: acl '%s' %s(): unable to find table '%s'.\n", - p->id, acl->name, expr->kw, pname); - cfgerr++; - continue; - } - - - if (!prx->table.size) { - Alert("proxy %s: acl '%s' %s(): no table in proxy '%s'.\n", - p->id, acl->name, expr->kw, pname); - cfgerr++; - continue; - } - - free(arg->data.str.str); - arg->data.str.str = NULL; - arg->unresolved = 0; - arg->data.prx = prx; - } - else if (arg->type == ARGT_USR) { - if (!arg->data.str.len) { - Alert("proxy %s: acl '%s' %s(): missing userlist name.\n", - p->id, acl->name, expr->kw); - cfgerr++; - continue; - } - - if (p->uri_auth && p->uri_auth->userlist && - !strcmp(p->uri_auth->userlist->name, arg->data.str.str)) - ul = p->uri_auth->userlist; - else - ul = auth_find_userlist(arg->data.str.str); - - if (!ul) { - Alert("proxy %s: acl '%s' %s(%s): unable to find userlist.\n", - p->id, acl->name, expr->kw, arg->data.str.str); - cfgerr++; - continue; - } - - free(arg->data.str.str); - arg->data.str.str = NULL; - arg->unresolved = 0; - arg->data.usr = ul; - } - } /* end of args processing */ - - /* don't try to resolve groups if we're not certain of having - * resolved userlists first. - */ - if (cfgerr) - break; - if (!strcmp(expr->kw, "http_auth_group")) { - /* note: argument resolved above thanks to ARGT_USR */ + /* Note: the ARGT_USR argument may only have been resolved earlier + * by smp_resolve_args(). + */ + if (expr->args->unresolved) { + Alert("Internal bug in proxy %s: %sacl %s %s() makes use of unresolved userlist '%s'. Please report this.\n", + p->id, *acl->name ? "" : "anonymous ", acl->name, expr->kw, expr->args->data.str.str); + cfgerr++; + continue; + } if (LIST_ISEMPTY(&expr->patterns)) { Alert("proxy %s: acl %s %s(): no groups specified.\n", @@ -2035,16 +1883,14 @@ acl_find_targets(struct proxy *p) /* this keyword only has one argument */ pattern->val.group_mask = auth_resolve_groups(expr->args->data.usr, pattern->ptr.str); + if (!pattern->val.group_mask) { + Alert("proxy %s: acl %s %s(): invalid group '%s'.\n", + p->id, acl->name, expr->kw, pattern->ptr.str); + cfgerr++; + } free(pattern->ptr.str); pattern->ptr.str = NULL; pattern->len = 0; - - if (!pattern->val.group_mask) { - Alert("proxy %s: acl %s %s(): invalid group(s).\n", - p->id, acl->name, expr->kw); - cfgerr++; - continue; - } } } } diff --git a/src/arg.c b/src/arg.c index f113ba303..1bf6444d3 100644 --- a/src/arg.c +++ b/src/arg.c @@ -41,6 +41,41 @@ static const char *arg_type_names[ARGT_NBTYPES] = { */ struct arg empty_arg_list[8] = { }; +/* This function clones a struct arg_list template into a new one which is + * returned. + */ +struct arg_list *arg_list_clone(const struct arg_list *orig) +{ + struct arg_list *new; + + if ((new = calloc(1, sizeof(*new))) != NULL) { + /* ->list will be set by the caller when inserting the element. + * ->arg and ->arg_pos will be set by the caller. + */ + new->ctx = orig->ctx; + new->kw = orig->kw; + new->conv = orig->conv; + new->file = orig->file; + new->line = orig->line; + } + return new; +} + +/* This function clones a struct template into a new one which is + * set to point to arg at pos , and which is returned if the caller + * wants to apply further changes. + */ +struct arg_list *arg_list_add(struct arg_list *orig, struct arg *arg, int pos) +{ + struct arg_list *new; + + new = arg_list_clone(orig); + new->arg = arg; + new->arg_pos = pos; + LIST_ADDQ(&orig->list, &new->list); + return new; +} + /* This function builds an argument list from a config line. It returns the * number of arguments found, or <0 in case of any error. Everything needed * it automatically allocated. A pointer to an error message might be returned @@ -48,21 +83,26 @@ struct arg empty_arg_list[8] = { }; * will have to check it and free it. The output arg list is returned in argp * which must be valid. The returned array is always terminated by an arg of * type ARGT_STOP (0), unless the mask indicates that no argument is supported. - * The mask is composed of a number of mandatory arguments in its lower 4 bits, - * and a concatenation of each argument type in each subsequent 4-bit block. If - * is not NULL, it must point to a freeable or NULL pointer. + * Unresolved arguments are appended to arg list , which also serves as a + * template to create new entries. The mask is composed of a number of + * mandatory arguments in its lower 4 bits, and a concatenation of each + * argument type in each subsequent 4-bit block. If is not NULL, it + * must point to a freeable or NULL pointer. */ int make_arg_list(const char *in, int len, unsigned int mask, struct arg **argp, - char **err_msg, const char **err_ptr, int *err_arg) + char **err_msg, const char **err_ptr, int *err_arg, + struct arg_list *al) { int nbarg; int pos; - struct arg *arg, *arg_list = NULL; + struct arg *arg; const char *beg; char *word = NULL; const char *ptr_err = NULL; int min_arg; + *argp = NULL; + min_arg = mask & 15; mask >>= 4; @@ -79,7 +119,7 @@ int make_arg_list(const char *in, int len, unsigned int mask, struct arg **argp, if (!len && !min_arg) goto end_parse; - arg = arg_list = calloc(nbarg + 1, sizeof(*arg)); + arg = *argp = calloc(nbarg + 1, sizeof(*arg)); /* Note: empty arguments after a comma always exist. */ while (pos < nbarg) { @@ -136,6 +176,8 @@ int make_arg_list(const char *in, int len, unsigned int mask, struct arg **argp, * parsing then resolved later. */ arg->unresolved = 1; + arg_list_add(al, arg, pos); + /* fall through */ case ARGT_STR: /* all types that must be resolved are stored as strings @@ -239,8 +281,6 @@ int make_arg_list(const char *in, int len, unsigned int mask, struct arg **argp, /* note that pos might be < nbarg and this is not an error, it's up to the * caller to decide what to do with optional args. */ - *argp = arg_list; - if (err_arg) *err_arg = pos; if (err_ptr) @@ -249,7 +289,7 @@ int make_arg_list(const char *in, int len, unsigned int mask, struct arg **argp, err: free(word); - free(arg_list); + free(*argp); if (err_arg) *err_arg = pos; if (err_ptr) diff --git a/src/cfgparse.c b/src/cfgparse.c index b82a2dc68..49a91c046 100644 --- a/src/cfgparse.c +++ b/src/cfgparse.c @@ -1572,8 +1572,8 @@ int cfg_parse_peers(const char *file, int linenum, char **args, int kwm) curpeers->peers_fe->timeout.connect = 5000; curpeers->peers_fe->accept = peer_accept; curpeers->peers_fe->options2 |= PR_O2_INDEPSTR | PR_O2_SMARTCON | PR_O2_SMARTACC; - curpeers->peers_fe->conf.file = strdup(file); - curpeers->peers_fe->conf.line = linenum; + curpeers->peers_fe->conf.args.file = curpeers->peers_fe->conf.file = strdup(file); + curpeers->peers_fe->conf.args.line = curpeers->peers_fe->conf.line = linenum; bind_conf = bind_conf_alloc(&curpeers->peers_fe->conf.bind, file, linenum, args[2]); @@ -1692,8 +1692,8 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm) init_new_proxy(curproxy); curproxy->next = proxy; proxy = curproxy; - curproxy->conf.file = strdup(file); - curproxy->conf.line = linenum; + curproxy->conf.args.file = curproxy->conf.file = strdup(file); + curproxy->conf.args.line = curproxy->conf.line = linenum; curproxy->last_change = now.tv_sec; curproxy->id = strdup(args[1]); curproxy->cap = rc; @@ -1925,6 +1925,8 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm) /* we cannot free uri_auth because it might already be used */ init_default_instance(); curproxy = &defproxy; + curproxy->conf.args.file = curproxy->conf.file = strdup(file); + curproxy->conf.args.line = curproxy->conf.line = linenum; defproxy.cap = PR_CAP_LISTEN; /* all caps for now */ goto out; } @@ -1933,7 +1935,10 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm) err_code |= ERR_ALERT | ERR_FATAL; goto out; } - + + /* update the current file and line being parsed */ + curproxy->conf.args.file = curproxy->conf.file; + curproxy->conf.args.line = linenum; /* Now let's parse the proxy-specific keywords */ if (!strcmp(args[0], "bind")) { /* new listen addresses */ @@ -2225,7 +2230,7 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm) err_code |= ERR_ALERT | ERR_FATAL; } - if (parse_acl((const char **)args + 1, &curproxy->acl, &errmsg) == NULL) { + if (parse_acl((const char **)args + 1, &curproxy->acl, &errmsg, &curproxy->conf.args) == NULL) { Alert("parsing [%s:%d] : error detected while parsing ACL '%s' : %s.\n", file, linenum, args[1], errmsg); err_code |= ERR_ALERT | ERR_FATAL; @@ -3023,7 +3028,8 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm) goto out; } - expr = sample_parse_expr(args, &myidx, trash.str, trash.size); + curproxy->conf.args.ctx = ARGC_STK; + expr = sample_parse_expr(args, &myidx, trash.str, trash.size, &curproxy->conf.args); if (!expr) { Alert("parsing [%s:%d] : '%s': %s\n", file, linenum, args[0], trash.str); err_code |= ERR_ALERT | ERR_FATAL; @@ -4825,6 +4831,18 @@ stats_error_parsing: } free(curproxy->uniqueid_format_string); curproxy->uniqueid_format_string = strdup(args[1]); + + /* get a chance to improve log-format error reporting by + * reporting the correct line-number when possible. + */ + if (curproxy != &defproxy) { + curproxy->conf.args.ctx = ARGC_UIF; + if (curproxy->uniqueid_format_string) + parse_logformat_string(curproxy->uniqueid_format_string, curproxy, &curproxy->format_unique_id, 0, + (proxy->cap & PR_CAP_FE) ? SMP_VAL_FE_HRQ_HDR : SMP_VAL_BE_HRQ_HDR); + free(curproxy->uniqueid_format_string); + curproxy->uniqueid_format_string = NULL; + } } else if (strcmp(args[0], "unique-id-header") == 0) { @@ -4854,6 +4872,23 @@ stats_error_parsing: curproxy->logformat_string != clf_http_log_format) free(curproxy->logformat_string); curproxy->logformat_string = strdup(args[1]); + + /* get a chance to improve log-format error reporting by + * reporting the correct line-number when possible. + */ + if (curproxy != &defproxy && !(curproxy->cap & PR_CAP_FE)) { + Warning("parsing [%s:%d] : backend '%s' : 'log-format' directive is ignored in backends.\n", + file, linenum, curproxy->id); + err_code |= ERR_WARN; + } + else if (curproxy->cap & PR_CAP_FE) { + curproxy->conf.args.ctx = ARGC_LOG; + if (curproxy->logformat_string) + parse_logformat_string(curproxy->logformat_string, curproxy, &curproxy->logformat, LOG_OPT_MANDATORY, + SMP_VAL_FE_LOG_END); + free(curproxy->logformat_string); + curproxy->logformat_string = NULL; + } } else if (!strcmp(args[0], "log") && kwm == KWM_NO) { @@ -6434,7 +6469,29 @@ int check_config_validity() } out_uri_auth_compat: - cfgerr += acl_find_targets(curproxy); + /* compile the log format */ + if (!(curproxy->cap & PR_CAP_FE)) { + if (curproxy->logformat_string != default_http_log_format && + curproxy->logformat_string != default_tcp_log_format && + curproxy->logformat_string != clf_http_log_format) + free(curproxy->logformat_string); + curproxy->logformat_string = NULL; + } + + curproxy->conf.args.ctx = ARGC_LOG; + if (curproxy->logformat_string) + parse_logformat_string(curproxy->logformat_string, curproxy, &curproxy->logformat, LOG_OPT_MANDATORY, + SMP_VAL_FE_LOG_END); + + curproxy->conf.args.ctx = ARGC_UIF; + if (curproxy->uniqueid_format_string) + parse_logformat_string(curproxy->uniqueid_format_string, curproxy, &curproxy->format_unique_id, 0, + (proxy->cap & PR_CAP_FE) ? SMP_VAL_FE_HRQ_HDR : SMP_VAL_BE_HRQ_HDR); + + /* only now we can check if some args remain unresolved */ + cfgerr += smp_resolve_args(curproxy); + if (!cfgerr) + cfgerr += acl_find_targets(curproxy); if ((curproxy->mode == PR_MODE_TCP || curproxy->mode == PR_MODE_HTTP) && (((curproxy->cap & PR_CAP_FE) && !curproxy->timeout.client) || @@ -6524,23 +6581,6 @@ out_uri_auth_compat: } } - /* compile the log format */ - if (!(curproxy->cap & PR_CAP_FE)) { - if (curproxy->logformat_string != default_http_log_format && - curproxy->logformat_string != default_tcp_log_format && - curproxy->logformat_string != clf_http_log_format) - free(curproxy->logformat_string); - curproxy->logformat_string = NULL; - } - - if (curproxy->logformat_string) - parse_logformat_string(curproxy->logformat_string, curproxy, &curproxy->logformat, LOG_OPT_MANDATORY, - SMP_VAL_FE_LOG_END); - - if (curproxy->uniqueid_format_string) - parse_logformat_string(curproxy->uniqueid_format_string, curproxy, &curproxy->format_unique_id, 0, - (proxy->cap & PR_CAP_FE) ? SMP_VAL_FE_HRQ_HDR : SMP_VAL_BE_HRQ_HDR); - /* first, we will invert the servers list order */ newsrv = NULL; while (curproxy->srv) { diff --git a/src/log.c b/src/log.c index 165fd724e..ed1f06122 100644 --- a/src/log.c +++ b/src/log.c @@ -307,7 +307,7 @@ void add_to_logformat_list(char *start, char *end, int type, struct list *list_f /* * Parse the sample fetch expression and add a node to upon * success. At the moment, sample converters are not yet supported but fetch arguments - * should work. + * should work. The curpx->conf.args.ctx must be set by the caller. */ void add_sample_to_logformat_list(char *text, char *arg, int arg_len, struct proxy *curpx, struct list *list_format, int options, int cap) { @@ -320,7 +320,7 @@ void add_sample_to_logformat_list(char *text, char *arg, int arg_len, struct pro cmd[1] = ""; cmd_arg = 0; - expr = sample_parse_expr(cmd, &cmd_arg, trash.str, trash.size); + expr = sample_parse_expr(cmd, &cmd_arg, trash.str, trash.size, &curpx->conf.args); if (!expr) { Warning("log-format: sample fetch <%s> failed with : %s\n", text, trash.str); return; @@ -360,7 +360,8 @@ void add_sample_to_logformat_list(char *text, char *arg, int arg_len, struct pro /* * Parse the log_format string and fill a linked list. * Variable name are preceded by % and composed by characters [a-zA-Z0-9]* : %varname - * You can set arguments using { } : %{many arguments}varname + * You can set arguments using { } : %{many arguments}varname. + * The curproxy->conf.args.ctx must be set by the caller. * * str: the string to parse * curproxy: the proxy affected diff --git a/src/proto_http.c b/src/proto_http.c index 162b8f493..ff71659aa 100644 --- a/src/proto_http.c +++ b/src/proto_http.c @@ -8139,6 +8139,8 @@ struct http_req_rule *parse_http_req_cond(const char **args, const char *file, i rule->arg.hdr_add.name = strdup(args[cur_arg]); rule->arg.hdr_add.name_len = strlen(rule->arg.hdr_add.name); LIST_INIT(&rule->arg.hdr_add.fmt); + + proxy->conf.args.ctx = ARGC_HDR; parse_logformat_string(args[cur_arg + 1], proxy, &rule->arg.hdr_add.fmt, 0, (proxy->cap & PR_CAP_FE) ? SMP_VAL_FE_HRQ_HDR : SMP_VAL_BE_HRQ_HDR); cur_arg += 2; diff --git a/src/proto_tcp.c b/src/proto_tcp.c index dc3817ea2..781b24a74 100644 --- a/src/proto_tcp.c +++ b/src/proto_tcp.c @@ -1112,7 +1112,8 @@ static int tcp_parse_request_rule(char **args, int arg, int section_type, arg++; - expr = sample_parse_expr(args, &arg, trash.str, trash.size); + curpx->conf.args.ctx = ARGC_TRK; + expr = sample_parse_expr(args, &arg, trash.str, trash.size, &curpx->conf.args); if (!expr) { memprintf(err, "'%s %s %s' : %s", diff --git a/src/proxy.c b/src/proxy.c index 1986a7c2b..7bfe954a4 100644 --- a/src/proxy.c +++ b/src/proxy.c @@ -449,6 +449,7 @@ void init_new_proxy(struct proxy *p) LIST_INIT(&p->format_unique_id); LIST_INIT(&p->conf.bind); LIST_INIT(&p->conf.listeners); + LIST_INIT(&p->conf.args.list); /* Timeouts are defined as -1 */ proxy_reset_timeouts(p); diff --git a/src/sample.c b/src/sample.c index 3067cc758..753b45737 100644 --- a/src/sample.c +++ b/src/sample.c @@ -19,9 +19,14 @@ #include #include +#include #include +#include +#include +#include #include +#include /* static sample used in sample_process() when

is NULL */ static struct sample temp_smp; @@ -525,8 +530,9 @@ static sample_cast_fct sample_casts[SMP_TYPES][SMP_TYPES] = { * Parse a sample expression configuration: * fetch keyword followed by format conversion keywords. * Returns a pointer on allocated sample expression structure. + * The caller must have set al->ctx. */ -struct sample_expr *sample_parse_expr(char **str, int *idx, char *err, int err_size) +struct sample_expr *sample_parse_expr(char **str, int *idx, char *err, int err_size, struct arg_list *al) { const char *endw; const char *end; @@ -598,7 +604,9 @@ struct sample_expr *sample_parse_expr(char **str, int *idx, char *err, int err_s goto out_error; } - if (make_arg_list(endw + 1, end - endw - 2, fetch->arg_mask, &expr->arg_p, &err_msg, NULL, &err_arg) < 0) { + al->kw = expr->fetch->kw; + al->conv = NULL; + if (make_arg_list(endw + 1, end - endw - 2, fetch->arg_mask, &expr->arg_p, &err_msg, NULL, &err_arg, al) < 0) { p = my_strndup(str[*idx], endw - str[*idx]); if (p) { snprintf(err, err_size, "invalid arg %d in fetch method '%s' : %s.", err_arg+1, p, err_msg); @@ -693,7 +701,9 @@ struct sample_expr *sample_parse_expr(char **str, int *idx, char *err, int err_s goto out_error; } - if (make_arg_list(endw + 1, end - endw - 2, conv->arg_mask, &conv_expr->arg_p, &err_msg, NULL, &err_arg) < 0) { + al->kw = expr->fetch->kw; + al->conv = conv_expr->conv->kw; + if (make_arg_list(endw + 1, end - endw - 2, conv->arg_mask, &conv_expr->arg_p, &err_msg, NULL, &err_arg, al) < 0) { p = my_strndup(str[*idx], endw - str[*idx]); if (p) { snprintf(err, err_size, "invalid arg %d in conv method '%s' : %s.", err_arg+1, p, err_msg); @@ -786,6 +796,214 @@ struct sample *sample_process(struct proxy *px, struct session *l4, void *l7, return p; } +/* + * Resolve all remaining arguments in proxy

. Returns the number of + * errors or 0 if everything is fine. + */ +int smp_resolve_args(struct proxy *p) +{ + struct arg_list *cur, *bak; + const char *ctx, *where; + const char *conv_ctx, *conv_pre, *conv_pos; + struct userlist *ul; + struct arg *arg; + int cfgerr = 0; + + list_for_each_entry_safe(cur, bak, &p->conf.args.list, list) { + struct proxy *px; + struct server *srv; + char *pname, *sname; + + arg = cur->arg; + + /* prepare output messages */ + conv_pre = conv_pos = conv_ctx = ""; + if (cur->conv) { + conv_ctx = cur->conv; + conv_pre = "conversion keyword '"; + conv_pos = "' for "; + } + + where = "in"; + ctx = "sample fetch keyword"; + switch (cur->ctx) { + case ARGC_STK:where = "in stick rule in"; break; + case ARGC_TRK: where = "in tracking rule in"; break; + case ARGC_LOG: where = "in log-format string in"; break; + case ARGC_HDR: where = "in HTTP header format string in"; break; + case ARGC_UIF: where = "in unique-id-format string in"; break; + case ARGC_ACL: ctx = "ACL keyword"; break; + } + + /* set a few default settings */ + px = p; + pname = p->id; + + switch (arg->type) { + case ARGT_SRV: + if (!arg->data.str.len) { + Alert("parsing [%s:%d] : missing server name in arg %d of %s%s%s%s '%s' %s proxy '%s'.\n", + cur->file, cur->line, + cur->arg_pos + 1, conv_pre, conv_ctx, conv_pos, ctx, cur->kw, where, p->id); + cfgerr++; + continue; + } + + /* we support two formats : "bck/srv" and "srv" */ + sname = strrchr(arg->data.str.str, '/'); + + if (sname) { + *sname++ = '\0'; + pname = arg->data.str.str; + + px = findproxy(pname, PR_CAP_BE); + if (!px) { + Alert("parsing [%s:%d] : unable to find proxy '%s' referenced in arg %d of %s%s%s%s '%s' %s proxy '%s'.\n", + cur->file, cur->line, pname, + cur->arg_pos + 1, conv_pre, conv_ctx, conv_pos, ctx, cur->kw, where, p->id); + cfgerr++; + break; + } + } + else + sname = arg->data.str.str; + + srv = findserver(px, sname); + if (!srv) { + Alert("parsing [%s:%d] : unable to find server '%s' in proxy '%s', referenced in arg %d of %s%s%s%s '%s' %s proxy '%s'.\n", + cur->file, cur->line, sname, pname, + cur->arg_pos + 1, conv_pre, conv_ctx, conv_pos, ctx, cur->kw, where, p->id); + cfgerr++; + break; + } + + free(arg->data.str.str); + arg->data.str.str = NULL; + arg->unresolved = 0; + arg->data.srv = srv; + break; + + case ARGT_FE: + if (arg->data.str.len) { + pname = arg->data.str.str; + px = findproxy(pname, PR_CAP_FE); + } + + if (!px) { + Alert("parsing [%s:%d] : unable to find frontend '%s' referenced in arg %d of %s%s%s%s '%s' %s proxy '%s'.\n", + cur->file, cur->line, pname, + cur->arg_pos + 1, conv_pre, conv_ctx, conv_pos, ctx, cur->kw, where, p->id); + cfgerr++; + break; + } + + if (!(px->cap & PR_CAP_FE)) { + Alert("parsing [%s:%d] : proxy '%s', referenced in arg %d of %s%s%s%s '%s' %s proxy '%s', has not frontend capability.\n", + cur->file, cur->line, pname, + cur->arg_pos + 1, conv_pre, conv_ctx, conv_pos, ctx, cur->kw, where, p->id); + cfgerr++; + break; + } + + free(arg->data.str.str); + arg->data.str.str = NULL; + arg->unresolved = 0; + arg->data.prx = px; + break; + + case ARGT_BE: + if (arg->data.str.len) { + pname = arg->data.str.str; + px = findproxy(pname, PR_CAP_BE); + } + + if (!px) { + Alert("parsing [%s:%d] : unable to find backend '%s' referenced in arg %d of %s%s%s%s '%s' %s proxy '%s'.\n", + cur->file, cur->line, pname, + cur->arg_pos + 1, conv_pre, conv_ctx, conv_pos, ctx, cur->kw, where, p->id); + cfgerr++; + break; + } + + if (!(px->cap & PR_CAP_BE)) { + Alert("parsing [%s:%d] : proxy '%s', referenced in arg %d of %s%s%s%s '%s' %s proxy '%s', has not backend capability.\n", + cur->file, cur->line, pname, + cur->arg_pos + 1, conv_pre, conv_ctx, conv_pos, ctx, cur->kw, where, p->id); + cfgerr++; + break; + } + + free(arg->data.str.str); + arg->data.str.str = NULL; + arg->unresolved = 0; + arg->data.prx = px; + break; + + case ARGT_TAB: + if (arg->data.str.len) { + pname = arg->data.str.str; + px = find_stktable(pname); + } + + if (!px) { + Alert("parsing [%s:%d] : unable to find table '%s' referenced in arg %d of %s%s%s%s '%s' %s proxy '%s'.\n", + cur->file, cur->line, pname, + cur->arg_pos + 1, conv_pre, conv_ctx, conv_pos, ctx, cur->kw, where, p->id); + cfgerr++; + break; + } + + if (!px->table.size) { + Alert("parsing [%s:%d] : no table in proxy '%s' referenced in arg %d of %s%s%s%s '%s' %s proxy '%s'.\n", + cur->file, cur->line, pname, + cur->arg_pos + 1, conv_pre, conv_ctx, conv_pos, ctx, cur->kw, where, p->id); + cfgerr++; + break; + } + + free(arg->data.str.str); + arg->data.str.str = NULL; + arg->unresolved = 0; + arg->data.prx = px; + break; + + case ARGT_USR: + if (!arg->data.str.len) { + Alert("parsing [%s:%d] : missing userlist name in arg %d of %s%s%s%s '%s' %s proxy '%s'.\n", + cur->file, cur->line, + cur->arg_pos + 1, conv_pre, conv_ctx, conv_pos, ctx, cur->kw, where, p->id); + cfgerr++; + break; + } + + if (p->uri_auth && p->uri_auth->userlist && + !strcmp(p->uri_auth->userlist->name, arg->data.str.str)) + ul = p->uri_auth->userlist; + else + ul = auth_find_userlist(arg->data.str.str); + + if (!ul) { + Alert("parsing [%s:%d] : unable to find userlist '%s' referenced in arg %d of %s%s%s%s '%s' %s proxy '%s'.\n", + cur->file, cur->line, arg->data.str.str, + cur->arg_pos + 1, conv_pre, conv_ctx, conv_pos, ctx, cur->kw, where, p->id); + cfgerr++; + break; + } + + free(arg->data.str.str); + arg->data.str.str = NULL; + arg->unresolved = 0; + arg->data.usr = ul; + break; + } + + LIST_DEL(&cur->list); + free(cur); + } /* end of args processing */ + + return cfgerr; +} + /* * Process a fetch + format conversion as defined by the sample expression * on request or response considering the parameter. The output is always of