MEDIUM: acl: report parsing errors to the caller

All parsing errors were known but impossible to return. Now by making use
of memprintf(), we're able to build meaningful error messages that the
caller can display.
diff --git a/include/proto/acl.h b/include/proto/acl.h
index 3a7606b..2c74e2d 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);
+struct acl_expr *parse_acl_expr(const char **args, char **err);
 
 /* 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);
+struct acl *parse_acl(const char **args, struct list *known_acl, char **err);
 
 /* Purge everything in the acl_cond <cond>, then return <cond>. */
 struct acl_cond *prune_acl_cond(struct acl_cond *cond);
@@ -79,15 +79,16 @@
  * 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);
+struct acl_cond *parse_acl_cond(const char **args, struct list *known_acl, int pol, char **err);
 
 /* 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
  * word is neither "if" nor "unless". It automatically sets the file name and
  * the line number in the condition for better error reporting, and adds the
- * ACL requirements to the proxy's acl_requires.
+ * ACL requirements to the proxy's acl_requires. If <err> is not NULL, it will
+ * be set to an error message upon errors, that the caller will have to free.
  */
-struct acl_cond *build_acl_cond(const char *file, int line, struct proxy *px, const char **args);
+struct acl_cond *build_acl_cond(const char *file, int line, struct proxy *px, const char **args, char **err);
 
 /* Execute condition <cond> and return either ACL_PAT_FAIL, ACL_PAT_MISS or
  * ACL_PAT_PASS depending on the test results. This function only computes the
diff --git a/src/acl.c b/src/acl.c
index 433a266..a040d87 100644
--- a/src/acl.c
+++ b/src/acl.c
@@ -1224,11 +1224,14 @@
 	return ret;
 }
 
-/* Parse an ACL expression starting at <args>[0], and return it.
+/* 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.
+ *
  * Right now, the only accepted syntax is :
  * <subject> [<value>...]
  */
-struct acl_expr *parse_acl_expr(const char **args)
+struct acl_expr *parse_acl_expr(const char **args, char **err)
 {
 	__label__ out_return, out_free_expr, out_free_pattern;
 	struct acl_expr *expr;
@@ -1238,12 +1241,18 @@
 	const char *arg;
 
 	aclkw = find_acl_kw(args[0]);
-	if (!aclkw || !aclkw->parse)
+	if (!aclkw || !aclkw->parse) {
+		if (err)
+			memprintf(err, "unknown ACL keyword '%s'", *args);
 		goto out_return;
+	}
 
 	expr = (struct acl_expr *)calloc(1, sizeof(*expr));
-	if (!expr)
+	if (!expr) {
+		if (err)
+			memprintf(err, "out of memory when parsing ACL expression");
 		goto out_return;
+	}
 
 	expr->kw = aclkw;
 	aclkw->use_cnt++;
@@ -1259,8 +1268,11 @@
 			/* there are 0 or more arguments in the form "subject(arg[,arg]*)" */
 			arg++;
 			end = strchr(arg, ')');
-			if (!end)
+			if (!end) {
+				if (err)
+					memprintf(err, "missing closing ')' after arguments to ACL keyword '%s'", aclkw->kw);
 				goto out_free_expr;
+			}
 
 			/* Parse the arguments. Note that currently we have no way to
 			 * report parsing errors, hence the NULL in the error pointers.
@@ -1268,12 +1280,22 @@
 			 * missing.
 			 */
 			nbargs = make_arg_list(arg, end - arg, aclkw->arg_mask, &expr->args,
-					       NULL, NULL, NULL);
-			if (nbargs < 0)
+					       err, NULL, NULL);
+			if (nbargs < 0) {
+				/* note that make_arg_list will have set <err> here */
+				if (err)
+					memprintf(err, "in argument to '%s', %s", aclkw->kw, *err);
 				goto out_free_expr;
+			}
 
-			if (aclkw->val_args && !aclkw->val_args(expr->args, NULL))
-				goto out_free_expr; /* invalid keyword argument */
+			if (aclkw->val_args && !aclkw->val_args(expr->args, err)) {
+				/* invalid keyword argument, error must have been
+				 * set by val_args().
+				 */
+				if (err)
+					memprintf(err, "in argument to '%s', %s", aclkw->kw, *err);
+				goto out_free_expr;
+			}
 		}
 		else if (ARGM(aclkw->arg_mask) == 1) {
 			int type = (aclkw->arg_mask >> 4) & 15;
@@ -1282,8 +1304,11 @@
 			 * an empty one so that acl_find_targets() resolves it as
 			 * the current one later.
 			 */
-			if (type != ARGT_FE && type != ARGT_BE && type != ARGT_TAB)
+			if (type != ARGT_FE && type != ARGT_BE && type != ARGT_TAB) {
+				if (err)
+					memprintf(err, "ACL keyword '%s' expects %d arguments", aclkw->kw, ARGM(aclkw->arg_mask));
 				goto out_free_expr;
+			}
 
 			/* Build an arg list containing the type as an empty string
 			 * and the usual STOP.
@@ -1297,12 +1322,16 @@
 		}
 		else if (ARGM(aclkw->arg_mask)) {
 			/* there were some mandatory arguments */
+			if (err)
+				memprintf(err, "ACL keyword '%s' expects %d arguments", aclkw->kw, ARGM(aclkw->arg_mask));
 			goto out_free_expr;
 		}
 	}
 	else {
 		if (arg) {
 			/* no argument expected */
+			if (err)
+				memprintf(err, "ACL keyword '%s' takes no argument", aclkw->kw);
 			goto out_free_expr;
 		}
 	}
@@ -1319,8 +1348,11 @@
 		if ((*args)[1] == 'i')
 			patflags |= ACL_PAT_F_IGNORE_CASE;
 		else if ((*args)[1] == 'f') {
-			if (!acl_read_patterns_from_file(aclkw, expr, args[1], patflags | ACL_PAT_F_FROM_FILE))
+			if (!acl_read_patterns_from_file(aclkw, expr, args[1], patflags | ACL_PAT_F_FROM_FILE)) {
+				if (err)
+					memprintf(err, "failed to load some ACL patterns from file '%s'", args[1]);
 				goto out_free_expr;
+			}
 			args++;
 		}
 		else if ((*args)[1] == '-') {
@@ -1337,13 +1369,19 @@
 	while (**args) {
 		int ret;
 		pattern = (struct acl_pattern *)calloc(1, sizeof(*pattern));
-		if (!pattern)
+		if (!pattern) {
+			if (err)
+				memprintf(err, "out of memory when parsing ACL pattern");
 			goto out_free_expr;
+		}
 		pattern->flags = patflags;
 
 		ret = aclkw->parse(args, pattern, &opaque);
-		if (!ret)
+		if (!ret) {
+			if (err)
+				memprintf(err, "failed to parse some ACL patterns");
 			goto out_free_pattern;
+		}
 		LIST_ADDQ(&expr->patterns, &pattern->list);
 		args += ret;
 	}
@@ -1378,23 +1416,31 @@
 /* Parse an ACL with the name starting at <args>[0], and with a list of already
  * known ACLs in <acl>. If the ACL was not in the list, it will be added.
  * 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.
+ * 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.
  *
  * args syntax: <aclname> <acl_expr>
  */
-struct acl *parse_acl(const char **args, struct list *known_acl)
+struct acl *parse_acl(const char **args, struct list *known_acl, char **err)
 {
 	__label__ out_return, out_free_acl_expr, out_free_name;
 	struct acl *cur_acl;
 	struct acl_expr *acl_expr;
 	char *name;
+	const char *pos;
 
-	if (**args && invalid_char(*args))
+	if (**args && (pos = invalid_char(*args))) {
+		if (err)
+			memprintf(err, "invalid character in ACL name : '%c'", *pos);
 		goto out_return;
+	}
 
-	acl_expr = parse_acl_expr(args + 1);
-	if (!acl_expr)
+	acl_expr = parse_acl_expr(args + 1, err);
+	if (!acl_expr) {
+		/* parse_acl_expr will have filled <err> here */
 		goto out_return;
+	}
 
 	/* Check for args beginning with an opening parenthesis just after the
 	 * subject, as this is almost certainly a typo. Right now we can only
@@ -1415,11 +1461,17 @@
 
 	if (!cur_acl) {
 		name = strdup(args[0]);
-		if (!name)
+		if (!name) {
+			if (err)
+				memprintf(err, "out of memory when parsing ACL");
 			goto out_free_acl_expr;
+		}
 		cur_acl = (struct acl *)calloc(1, sizeof(*cur_acl));
-		if (cur_acl == NULL)
+		if (cur_acl == NULL) {
+			if (err)
+				memprintf(err, "out of memory when parsing ACL");
 			goto out_free_name;
+		}
 
 		LIST_INIT(&cur_acl->expr);
 		LIST_ADDQ(known_acl, &cur_acl->list);
@@ -1470,9 +1522,11 @@
 /* Find a default ACL from the default_acl list, compile it and return it.
  * If the ACL is not found, NULL is returned. In theory, it cannot fail,
  * 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 <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.
  */
-struct acl *find_acl_default(const char *acl_name, struct list *known_acl)
+struct acl *find_acl_default(const char *acl_name, struct list *known_acl, char **err)
 {
 	__label__ out_return, out_free_acl_expr, out_free_name;
 	struct acl *cur_acl;
@@ -1485,19 +1539,31 @@
 			break;
 	}
 
-	if (default_acl_list[index].name == NULL)
+	if (default_acl_list[index].name == NULL) {
+		if (err)
+			memprintf(err, "no such ACL : '%s'", acl_name);
 		return NULL;
+	}
 
-	acl_expr = parse_acl_expr((const char **)default_acl_list[index].expr);
-	if (!acl_expr)
+	acl_expr = parse_acl_expr((const char **)default_acl_list[index].expr, err);
+	if (!acl_expr) {
+		/* parse_acl_expr must have filled err here */
 		goto out_return;
+	}
 
 	name = strdup(acl_name);
-	if (!name)
+	if (!name) {
+		if (err)
+			memprintf(err, "out of memory when building default ACL '%s'", acl_name);
 		goto out_free_acl_expr;
+	}
+
 	cur_acl = (struct acl *)calloc(1, sizeof(*cur_acl));
-	if (cur_acl == NULL)
+	if (cur_acl == NULL) {
+		if (err)
+			memprintf(err, "out of memory when building default ACL '%s'", acl_name);
 		goto out_free_name;
+	}
 
 	cur_acl->name = name;
 	cur_acl->requires |= acl_expr->kw->requires;
@@ -1534,9 +1600,12 @@
 
 /* Parse an ACL condition starting at <args>[0], relying on a list of already
  * known ACLs passed in <known_acl>. The new condition is returned (or NULL in
- * case of low memory). Supports multiple conditions separated by "or".
+ * 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.
  */
-struct acl_cond *parse_acl_cond(const char **args, struct list *known_acl, int pol)
+struct acl_cond *parse_acl_cond(const char **args, struct list *known_acl, int pol, char **err)
 {
 	__label__ out_return, out_free_suite, out_free_term;
 	int arg, neg;
@@ -1547,8 +1616,11 @@
 	struct acl_cond *cond;
 
 	cond = (struct acl_cond *)calloc(1, sizeof(*cond));
-	if (cond == NULL)
+	if (cond == NULL) {
+		if (err)
+			memprintf(err, "out of memory when parsing condition");
 		goto out_return;
+	}
 
 	LIST_INIT(&cond->list);
 	LIST_INIT(&cond->suites);
@@ -1588,21 +1660,29 @@
 			while (*args[arg_end] && strcmp(args[arg_end], "}") != 0)
 				arg_end++;
 
-			if (!*args[arg_end])
+			if (!*args[arg_end]) {
+				if (err)
+					memprintf(err, "missing closing '}' in condition");
 				goto out_free_suite;
+			}
 
 			args_new = calloc(1, (arg_end - arg + 1) * sizeof(*args_new));
-			if (!args_new)
+			if (!args_new) {
+				if (err)
+					memprintf(err, "out of memory when parsing condition");
 				goto out_free_suite;
+			}
 
 			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);
+			cur_acl = parse_acl(args_new, known_acl, err);
 			free(args_new);
 
-			if (!cur_acl)
+			if (!cur_acl) {
+				/* note that parse_acl() must have filled <err> here */
 				goto out_free_suite;
+			}
 			arg = arg_end;
 		}
 		else {
@@ -1613,15 +1693,20 @@
 			 */
 			cur_acl = find_acl_by_name(word, known_acl);
 			if (cur_acl == NULL) {
-				cur_acl = find_acl_default(word, known_acl);
-				if (cur_acl == NULL)
+				cur_acl = find_acl_default(word, known_acl, err);
+				if (cur_acl == NULL) {
+					/* note that find_acl_default() must have filled <err> here */
 					goto out_free_suite;
+				}
 			}
 		}
 
 		cur_term = (struct acl_term *)calloc(1, sizeof(*cur_term));
-		if (cur_term == NULL)
+		if (cur_term == NULL) {
+			if (err)
+				memprintf(err, "out of memory when parsing condition");
 			goto out_free_suite;
+		}
 
 		cur_term->acl = cur_acl;
 		cur_term->neg = neg;
@@ -1629,8 +1714,11 @@
 
 		if (!cur_suite) {
 			cur_suite = (struct acl_term_suite *)calloc(1, sizeof(*cur_suite));
-			if (cur_term == NULL)
+			if (cur_term == NULL) {
+				if (err)
+					memprintf(err, "out of memory when parsing condition");
 				goto out_free_term;
+			}
 			LIST_INIT(&cur_suite->terms);
 			LIST_ADDQ(&cond->suites, &cur_suite->list);
 		}
@@ -1653,13 +1741,19 @@
  * condition is returned. NULL is returned in case of error or if the first
  * word is neither "if" nor "unless". It automatically sets the file name and
  * the line number in the condition for better error reporting, and adds the
- * ACL requirements to the proxy's acl_requires.
+ * ACL requirements to the proxy's acl_requires. 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.
  */
-struct acl_cond *build_acl_cond(const char *file, int line, struct proxy *px, const char **args)
+struct acl_cond *build_acl_cond(const char *file, int line, struct proxy *px, const char **args, char **err)
 {
 	int pol = ACL_COND_NONE;
 	struct acl_cond *cond = NULL;
 
+	if (err)
+		*err = NULL;
+
 	if (!strcmp(*args, "if")) {
 		pol = ACL_COND_IF;
 		args++;
@@ -1668,12 +1762,17 @@
 		pol = ACL_COND_UNLESS;
 		args++;
 	}
-	else
+	else {
+		if (err)
+			memprintf(err, "conditions must start with either 'if' or 'unless'");
 		return NULL;
+	}
 
-	cond = parse_acl_cond(args, &px->acl, pol);
-	if (!cond)
+	cond = parse_acl_cond(args, &px->acl, pol, err);
+	if (!cond) {
+		/* note that parse_acl_cond must have filled <err> here */
 		return NULL;
+	}
 
 	cond->file = file;
 	cond->line = line;
diff --git a/src/cfgparse.c b/src/cfgparse.c
index a3ac661..8b5eae3 100644
--- a/src/cfgparse.c
+++ b/src/cfgparse.c
@@ -1085,6 +1085,7 @@
 				  const char **cond_start)
 {
 	regex_t *preg = NULL;
+	char *errmsg = NULL;
 	const char *err;
 	int err_code = 0;
 	struct acl_cond *cond = NULL;
@@ -1106,9 +1107,10 @@
 
 	if (cond_start &&
 	    (strcmp(*cond_start, "if") == 0 || strcmp(*cond_start, "unless") == 0)) {
-		if ((cond = build_acl_cond(file, line, px, cond_start)) == NULL) {
-			Alert("parsing [%s:%d] : error detected while parsing a '%s' condition.\n",
-			      file, line, cmd);
+		if ((cond = build_acl_cond(file, line, px, cond_start, &errmsg)) == NULL) {
+			Alert("parsing [%s:%d] : error detected while parsing a '%s' condition : %s.\n",
+			      file, line, cmd, errmsg);
+			free(errmsg);
 			err_code |= ERR_ALERT | ERR_FATAL;
 			goto err;
 		}
@@ -2073,6 +2075,8 @@
 		curproxy->bind_proc = set;
 	}
 	else if (!strcmp(args[0], "acl")) {  /* add an ACL */
+		char *errmsg = NULL;
+
 		if (curproxy == &defproxy) {
 			Alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]);
 			err_code |= ERR_ALERT | ERR_FATAL;
@@ -2086,9 +2090,10 @@
 			err_code |= ERR_ALERT | ERR_FATAL;
 		}
 
-		if (parse_acl((const char **)args + 1, &curproxy->acl) == NULL) {
-			Alert("parsing [%s:%d] : error detected while parsing ACL '%s'.\n",
-			      file, linenum, args[1]);
+		if (parse_acl((const char **)args + 1, &curproxy->acl, &errmsg) == NULL) {
+			Alert("parsing [%s:%d] : error detected while parsing ACL '%s' : %s.\n",
+			      file, linenum, args[1], errmsg);
+			free(errmsg);
 			err_code |= ERR_ALERT | ERR_FATAL;
 			goto out;
 		}
@@ -2507,6 +2512,8 @@
 		curproxy->server_id_hdr_len  = strlen(curproxy->server_id_hdr_name);
 	}
 	else if (!strcmp(args[0], "block")) {  /* early blocking based on ACLs */
+		char *errmsg = NULL;
+
 		if (curproxy == &defproxy) {
 			Alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]);
 			err_code |= ERR_ALERT | ERR_FATAL;
@@ -2520,9 +2527,10 @@
 			goto out;
 		}
 
-		if ((cond = build_acl_cond(file, linenum, curproxy, (const char **)args + 1)) == NULL) {
-			Alert("parsing [%s:%d] : error detected while parsing blocking condition.\n",
-			      file, linenum);
+		if ((cond = build_acl_cond(file, linenum, curproxy, (const char **)args + 1, &errmsg)) == NULL) {
+			Alert("parsing [%s:%d] : error detected while parsing blocking condition : %s.\n",
+			      file, linenum, errmsg);
+			free(errmsg);
 			err_code |= ERR_ALERT | ERR_FATAL;
 			goto out;
 		}
@@ -2620,10 +2628,13 @@
 			}
 			else if (strcmp(args[cur_arg], "if") == 0 ||
 				 strcmp(args[cur_arg], "unless") == 0) {
-				cond = build_acl_cond(file, linenum, curproxy, (const char **)args + cur_arg);
+				char *errmsg = NULL;
+
+				cond = build_acl_cond(file, linenum, curproxy, (const char **)args + cur_arg, &errmsg);
 				if (!cond) {
-					Alert("parsing [%s:%d] : '%s': error detected while parsing redirect condition.\n",
-					      file, linenum, args[0]);
+					Alert("parsing [%s:%d] : '%s': error detected while parsing redirect condition : %s.\n",
+					      file, linenum, args[0], errmsg);
+					free(errmsg);
 					err_code |= ERR_ALERT | ERR_FATAL;
 					goto out;
 				}
@@ -2676,6 +2687,7 @@
 	}
 	else if (!strcmp(args[0], "use_backend")) {
 		struct switching_rule *rule;
+		char *errmsg = NULL;
 
 		if (curproxy == &defproxy) {
 			Alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]);
@@ -2699,9 +2711,10 @@
 			goto out;
 		}
 
-		if ((cond = build_acl_cond(file, linenum, curproxy, (const char **)args + 2)) == NULL) {
-			Alert("parsing [%s:%d] : error detected while parsing switching rule.\n",
-			      file, linenum);
+		if ((cond = build_acl_cond(file, linenum, curproxy, (const char **)args + 2, &errmsg)) == NULL) {
+			Alert("parsing [%s:%d] : error detected while parsing switching rule : %s.\n",
+			      file, linenum, errmsg);
+			free(errmsg);
 			err_code |= ERR_ALERT | ERR_FATAL;
 			goto out;
 		}
@@ -2716,6 +2729,7 @@
 	}
 	else if (strcmp(args[0], "use-server") == 0) {
 		struct server_rule *rule;
+		char *errmsg = NULL;
 
 		if (curproxy == &defproxy) {
 			Alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]);
@@ -2739,9 +2753,10 @@
 			goto out;
 		}
 
-		if ((cond = build_acl_cond(file, linenum, curproxy, (const char **)args + 2)) == NULL) {
-			Alert("parsing [%s:%d] : error detected while parsing switching rule.\n",
-			      file, linenum);
+		if ((cond = build_acl_cond(file, linenum, curproxy, (const char **)args + 2, &errmsg)) == NULL) {
+			Alert("parsing [%s:%d] : error detected while parsing switching rule : %s.\n",
+			      file, linenum, errmsg);
+			free(errmsg);
 			err_code |= ERR_ALERT | ERR_FATAL;
 			goto out;
 		}
@@ -2758,6 +2773,7 @@
 	else if ((!strcmp(args[0], "force-persist")) ||
 		 (!strcmp(args[0], "ignore-persist"))) {
 		struct persist_rule *rule;
+		char *errmsg = NULL;
 
 		if (curproxy == &defproxy) {
 			Alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]);
@@ -2775,9 +2791,10 @@
 			goto out;
 		}
 
-		if ((cond = build_acl_cond(file, linenum, curproxy, (const char **)args + 1)) == NULL) {
-			Alert("parsing [%s:%d] : error detected while parsing a '%s' rule.\n",
-			      file, linenum, args[0]);
+		if ((cond = build_acl_cond(file, linenum, curproxy, (const char **)args + 1, &errmsg)) == NULL) {
+			Alert("parsing [%s:%d] : error detected while parsing a '%s' rule : %s.\n",
+			      file, linenum, args[0], errmsg);
+			free(errmsg);
 			err_code |= ERR_ALERT | ERR_FATAL;
 			goto out;
 		}
@@ -2953,6 +2970,7 @@
 		struct sticking_rule *rule;
 		struct pattern_expr *expr;
 		int myidx = 0;
+		char *errmsg = NULL;
 		const char *name = NULL;
 		int flags;
 
@@ -3028,9 +3046,10 @@
 		}
 
 		if (strcmp(args[myidx], "if") == 0 || strcmp(args[myidx], "unless") == 0) {
-			if ((cond = build_acl_cond(file, linenum, curproxy, (const char **)args + myidx)) == NULL) {
-				Alert("parsing [%s:%d] : '%s': error detected while parsing sticking condition.\n",
-				      file, linenum, args[0]);
+			if ((cond = build_acl_cond(file, linenum, curproxy, (const char **)args + myidx, &errmsg)) == NULL) {
+				Alert("parsing [%s:%d] : '%s': error detected while parsing sticking condition : %s.\n",
+				      file, linenum, args[0], errmsg);
+				free(errmsg);
 				err_code |= ERR_ALERT | ERR_FATAL;
 				free(expr);
 				goto out;
@@ -3070,6 +3089,7 @@
 			goto stats_error_parsing;
 		} else if (!strcmp(args[1], "admin")) {
 			struct stats_admin_rule *rule;
+			char *errmsg = NULL;
 
 			if (curproxy == &defproxy) {
 				Alert("parsing [%s:%d]: '%s %s' not allowed in 'defaults' section.\n", file, linenum, args[0], args[1]);
@@ -3089,9 +3109,10 @@
 				err_code |= ERR_ALERT | ERR_FATAL;
 				goto out;
 			}
-			if ((cond = build_acl_cond(file, linenum, curproxy, (const char **)args + 2)) == NULL) {
-				Alert("parsing [%s:%d] : error detected while parsing a '%s %s' rule.\n",
-				file, linenum, args[0], args[1]);
+			if ((cond = build_acl_cond(file, linenum, curproxy, (const char **)args + 2, &errmsg)) == NULL) {
+				Alert("parsing [%s:%d] : error detected while parsing a '%s %s' rule : %s.\n",
+				      file, linenum, args[0], args[1], errmsg);
+				free(errmsg);
 				err_code |= ERR_ALERT | ERR_FATAL;
 				goto out;
 			}
@@ -3846,6 +3867,8 @@
 			err_code |= ERR_WARN;
 
 		if (strcmp(args[1], "fail") == 0) {
+			char *errmsg = NULL;
+
 			/* add a condition to fail monitor requests */
 			if (strcmp(args[2], "if") != 0 && strcmp(args[2], "unless") != 0) {
 				Alert("parsing [%s:%d] : '%s %s' requires either 'if' or 'unless' followed by a condition.\n",
@@ -3854,9 +3877,10 @@
 				goto out;
 			}
 
-			if ((cond = build_acl_cond(file, linenum, curproxy, (const char **)args + 2)) == NULL) {
-				Alert("parsing [%s:%d] : error detected while parsing a '%s %s' condition.\n",
-				      file, linenum, args[0], args[1]);
+			if ((cond = build_acl_cond(file, linenum, curproxy, (const char **)args + 2, &errmsg)) == NULL) {
+				Alert("parsing [%s:%d] : error detected while parsing a '%s %s' condition : %s.\n",
+				      file, linenum, args[0], args[1], errmsg);
+				free(errmsg);
 				err_code |= ERR_ALERT | ERR_FATAL;
 				goto out;
 			}
@@ -4987,6 +5011,7 @@
 	}
 	else if (!strcmp(args[0], "reqadd")) {  /* add request header */
 		struct cond_wordlist *wl;
+		char *errmsg = NULL;
 
 		if (curproxy == &defproxy) {
 			Alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]);
@@ -5003,9 +5028,10 @@
 		}
 
 		if ((strcmp(args[2], "if") == 0 || strcmp(args[2], "unless") == 0)) {
-			if ((cond = build_acl_cond(file, linenum, curproxy, (const char **)args+2)) == NULL) {
-				Alert("parsing [%s:%d] : error detected while parsing a '%s' condition.\n",
-				      file, linenum, args[0]);
+			if ((cond = build_acl_cond(file, linenum, curproxy, (const char **)args+2, &errmsg)) == NULL) {
+				Alert("parsing [%s:%d] : error detected while parsing a '%s' condition : %s.\n",
+				      file, linenum, args[0], errmsg);
+				free(errmsg);
 				err_code |= ERR_ALERT | ERR_FATAL;
 				goto out;
 			}
@@ -5082,6 +5108,7 @@
 	}
 	else if (!strcmp(args[0], "rspadd")) {  /* add response header */
 		struct cond_wordlist *wl;
+		char *errmsg = NULL;
 
 		if (curproxy == &defproxy) {
 			Alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]);
@@ -5098,9 +5125,10 @@
 		}
 	
 		if ((strcmp(args[2], "if") == 0 || strcmp(args[2], "unless") == 0)) {
-			if ((cond = build_acl_cond(file, linenum, curproxy, (const char **)args+2)) == NULL) {
-				Alert("parsing [%s:%d] : error detected while parsing a '%s' condition.\n",
-				      file, linenum, args[0]);
+			if ((cond = build_acl_cond(file, linenum, curproxy, (const char **)args+2, &errmsg)) == NULL) {
+				Alert("parsing [%s:%d] : error detected while parsing a '%s' condition : %s.\n",
+				      file, linenum, args[0], errmsg);
+				free(errmsg);
 				err_code |= ERR_ALERT | ERR_FATAL;
 				goto out;
 			}
diff --git a/src/proto_http.c b/src/proto_http.c
index a173f88..07e0eb5 100644
--- a/src/proto_http.c
+++ b/src/proto_http.c
@@ -7506,10 +7506,12 @@
 
 	if (strcmp(args[cur_arg], "if") == 0 || strcmp(args[cur_arg], "unless") == 0) {
 		struct acl_cond *cond;
+		char *errmsg = NULL;
 
-		if ((cond = build_acl_cond(file, linenum, proxy, args+cur_arg)) == NULL) {
-			Alert("parsing [%s:%d] : error detected while parsing an 'http-request %s' condition.\n",
-			      file, linenum, args[0]);
+		if ((cond = build_acl_cond(file, linenum, proxy, args+cur_arg, &errmsg)) == NULL) {
+			Alert("parsing [%s:%d] : error detected while parsing an 'http-request %s' condition : %s.\n",
+			      file, linenum, args[0], errmsg);
+			free(errmsg);
 			return NULL;
 		}
 		rule->cond = cond;
diff --git a/src/proto_tcp.c b/src/proto_tcp.c
index 4aa4729..dc7812d 100644
--- a/src/proto_tcp.c
+++ b/src/proto_tcp.c
@@ -962,10 +962,13 @@
 	}
 
 	if (strcmp(args[arg], "if") == 0 || strcmp(args[arg], "unless") == 0) {
-		if ((rule->cond = build_acl_cond(NULL, 0, curpx, (const char **)args+arg)) == NULL) {
+		char *errmsg = NULL;
+
+		if ((rule->cond = build_acl_cond(NULL, 0, curpx, (const char **)args+arg, &errmsg)) == NULL) {
 			snprintf(err, errlen,
-				 "error detected in %s '%s' while parsing '%s' condition",
-				 proxy_type_str(curpx), curpx->id, args[arg]);
+				 "error detected in %s '%s' while parsing '%s' condition : %s",
+				 proxy_type_str(curpx), curpx->id, args[arg], errmsg);
+			free(errmsg);
 			return -1;
 		}
 	}
@@ -1032,10 +1035,13 @@
 	}
 
 	if (strcmp(args[arg], "if") == 0 || strcmp(args[arg], "unless") == 0) {
-		if ((rule->cond = build_acl_cond(NULL, 0, curpx, (const char **)args+arg)) == NULL) {
+		char *errmsg = NULL;
+
+		if ((rule->cond = build_acl_cond(NULL, 0, curpx, (const char **)args+arg, &errmsg)) == NULL) {
 			snprintf(err, errlen,
-				 "error detected in %s '%s' while parsing '%s' condition",
-				 proxy_type_str(curpx), curpx->id, args[arg]);
+				 "error detected in %s '%s' while parsing '%s' condition : %s",
+				 proxy_type_str(curpx), curpx->id, args[arg], errmsg);
+			free(errmsg);
 			return -1;
 		}
 	}