MAJOR: sample: maintain a per-proxy list of the fetch args to resolve

While ACL args were resolved after all the config was parsed, it was not the
case with sample fetch args because they're almost everywhere now.

The issue is that ACLs now solely rely on sample fetches, so their args
resolving doesn't work anymore. And many fetches involving a server, a
proxy or a userlist don't work at all.

The real issue is that at the bottom layers we have no information about
proxies, line numbers, even ACLs in order to report understandable errors,
and that at the top layers we have no visibility over the locations where
fetches are referenced (think log node).

After failing multiple unsatisfying solutions attempts, we now have a new
concept of args list. The principle is that every proxy has a list head
which contains a number of indications such as the config keyword, the
context where it's used, the file and line number, etc... and a list of
arguments. This list head is of the same type as the elements, so it
serves as a template for adding new elements. This way, it is filled from
top to bottom by the callers with the information they have (eg: line
numbers, ACL name, ...) and the lower layers just have to duplicate it and
add an element when they face an argument they cannot resolve yet.

Then at the end of the configuration parsing, a loop passes over each
proxy's list and resolves all the args in sequence. And this way there is
all necessary information to report verbose errors.

The first immediate benefit is that for the first time we got very precise
location of issues (arg number in a keyword in its context, ...). Second,
in order to do this we had to parse log-format and unique-id-format a bit
earlier, so that was a great opportunity for doing so when the directives
are encountered (unless it's a default section). This way, the recorded
line numbers for these args are the ones of the place where the log format
is declared, not the end of the file.

Userlists report slightly more information now. They're the only remaining
ones in the ACL resolving function.
diff --git a/include/proto/acl.h b/include/proto/acl.h
index edd4648..6859b3d 100644
--- a/include/proto/acl.h
+++ b/include/proto/acl.h
@@ -59,7 +59,7 @@
  * Right now, the only accepted syntax is :
  * <subject> [<value>...]
  */
-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 <acl>, then return <acl>. */
 struct acl *prune_acl(struct acl *acl);
@@ -70,7 +70,7 @@
  *
  * args syntax: <aclname> <acl_expr>
  */
-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 <cond>, then return <cond>. */
 struct acl_cond *prune_acl_cond(struct acl_cond *cond);
@@ -79,7 +79,7 @@
  * known ACLs passed in <known_acl>. 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 8e0a81b..78c1105 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 a5696f6..3043662 100644
--- a/include/proto/sample.h
+++ b/include/proto/sample.h
@@ -26,7 +26,7 @@
 #include <types/sample.h>
 #include <types/stick_table.h>
 
-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 @@
 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 666a51f..a53d293 100644
--- a/include/types/arg.h
+++ b/include/types/arg.h
@@ -26,6 +26,7 @@
 #include <netinet/in.h>
 
 #include <common/chunk.h>
+#include <common/mini-clist.h>
 
 enum {
 	ARGT_STOP = 0, /* end of the arg list */
@@ -47,6 +48,16 @@
 	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 @@
 	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 5abf35e..5729db8 100644
--- a/include/types/proxy.h
+++ b/include/types/proxy.h
@@ -350,6 +350,7 @@
 		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 4a53072..efd1ee6 100644
--- a/src/acl.c
+++ b/src/acl.c
@@ -1024,12 +1024,13 @@
 
 /* Parse an ACL expression starting at <args>[0], and return it. If <err> 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. <al> is an arg_list serving
+ * as a list head to report missing dependencies.
  *
  * Right now, the only accepted syntax is :
  * <subject> [<value>...]
  */
-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 @@
 			/* 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 <err> here */
 				memprintf(err, "in argument to '%s', %s", expr->kw, *err);
@@ -1273,11 +1278,12 @@
  * 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 <err> is not
  * NULL, it will be filled with an appropriate error. This pointer must be
- * freeable or NULL.
+ * freeable or NULL. <al> is the arg_list serving as a head for unresolved
+ * dependencies.
  *
  * args syntax: <aclname> <acl_expr>
  */
-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 @@
 		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 <err> here */
 		goto out_return;
@@ -1381,9 +1387,11 @@
  * except when default ACLs are broken, in which case it will return NULL.
  * If <known_acl> is not NULL, the ACL will be queued at its tail. If <err> 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. <al> 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 @@
 		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 @@
  * case of low memory). Supports multiple conditions separated by "or". If
  * <err> 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 <al> 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 @@
 			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 @@
 			 */
 			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 <err> here */
 					goto out_free_suite;
@@ -1635,7 +1645,7 @@
 		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 <err> here */
 		return NULL;
@@ -1838,191 +1848,29 @@
 
 /*
  * 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)
+			if (!strcmp(expr->kw, "http_auth_group")) {
+				/* 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;
-				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 */
 
 				if (LIST_ISEMPTY(&expr->patterns)) {
 					Alert("proxy %s: acl %s %s(): no groups specified.\n",
@@ -2035,16 +1883,14 @@
 					/* this keyword only has one argument */
 					pattern->val.group_mask = auth_resolve_groups(expr->args->data.usr, pattern->ptr.str);
 
-					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);
+						Alert("proxy %s: acl %s %s(): invalid group '%s'.\n",
+						      p->id, acl->name, expr->kw, pattern->ptr.str);
 						cfgerr++;
-						continue;
 					}
+					free(pattern->ptr.str);
+					pattern->ptr.str = NULL;
+					pattern->len = 0;
 				}
 			}
 		}
diff --git a/src/arg.c b/src/arg.c
index f113ba3..1bf6444 100644
--- a/src/arg.c
+++ b/src/arg.c
@@ -41,6 +41,41 @@
  */
 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 <arg_list> template into a new one which is
+ * set to point to arg <arg> at pos <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 @@
  * 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
- * <err_msg> is not NULL, it must point to a freeable or NULL pointer.
+ * Unresolved arguments are appended to arg list <al>, 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 <err_msg> 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 @@
 	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 @@
 			 * 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 @@
 	/* 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 @@
 
  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 b82a2dc..49a91c0 100644
--- a/src/cfgparse.c
+++ b/src/cfgparse.c
@@ -1572,8 +1572,8 @@
 				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 @@
 		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 @@
 		/* 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 @@
 		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 @@
 			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 @@
 			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 @@
 		}
 		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 @@
 		    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 @@
 		}
 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;
+		}
+
+		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);
+
-		cfgerr += acl_find_targets(curproxy);
+		/* 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 @@
 			}
 		}
 
-		/* 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 165fd72..ed1f061 100644
--- a/src/log.c
+++ b/src/log.c
@@ -307,7 +307,7 @@
 /*
  * Parse the sample fetch expression <text> and add a node to <list_format> 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 @@
 	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 @@
 /*
  * 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 162b8f4..ff71659 100644
--- a/src/proto_http.c
+++ b/src/proto_http.c
@@ -8139,6 +8139,8 @@
 		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 dc3817e..781b24a 100644
--- a/src/proto_tcp.c
+++ b/src/proto_tcp.c
@@ -1112,7 +1112,8 @@
 
 		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 1986a7c..7bfe954 100644
--- a/src/proxy.c
+++ b/src/proxy.c
@@ -449,6 +449,7 @@
 	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 3067cc7..753b457 100644
--- a/src/sample.c
+++ b/src/sample.c
@@ -19,9 +19,14 @@
 
 #include <common/chunk.h>
 #include <common/standard.h>
+#include <common/uri_auth.h>
 
 #include <proto/arg.h>
+#include <proto/auth.h>
+#include <proto/log.h>
+#include <proto/proxy.h>
 #include <proto/sample.h>
+#include <proto/stick_table.h>
 
 /* static sample used in sample_process() when <p> is NULL */
 static struct sample temp_smp;
@@ -525,8 +530,9 @@
  * 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 @@
 			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 @@
 				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);
@@ -787,6 +797,214 @@
 }
 
 /*
+ * Resolve all remaining arguments in proxy <p>. 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 <expr>
  * on request or response considering the <opt> parameter. The output is always of
  * type string. Returns either NULL if no sample could be extracted, or a pointer