MINOR: cfgcond: insert an expression between the condition and the term

Now evaluating a condition will rely on an expression (or an empty string),
and this expression will support ORing a sub-expression with another
optional expression. The sub-expressions ANDs a term with another optional
sub-expression. With this alone precedence between && and || is respected,
and the following expression:

     A && B && C || D || E && F || G

will naturally evaluate as:

     (A && B && C) || D || (E && F) || G
diff --git a/doc/configuration.txt b/doc/configuration.txt
index ccb77a5..a66350f 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -805,14 +805,21 @@
 Conditions can also be evaluated on startup with the -cc parameter.
 See "3. Starting HAProxy" in the management doc.
 
-The conditions are currently limited to:
+The conditions are either an empty string (which then returns false), or an
+expression made of any combination of:
 
-  - an empty string, always returns "false"
   - the integer zero ('0'), always returns "false"
   - a non-nul integer (e.g. '1'), always returns "true".
   - a predicate optionally followed by argument(s) in parenthesis.
   - a question mark ('!') preceeding any of the non-empty elements above, and
     which will negate its status.
+  - expressions combined with a logical AND ('&&'), which will be evaluated
+    from left to right until one returns false
+  - expressions combined with a logical OR ('||'), which will be evaluated
+    from right to left until one returns true
+
+Note that like in other languages, the AND operator has precedence over the OR
+operator, so that "A && B || C && D" evalues as "(A && B) || (C && D)".
 
 The list of currently supported predicates is the following:
 
@@ -854,6 +861,10 @@
      .endif
    .endif
 
+   .if streq("$WITH_SSL",yes) && feature(OPENSSL)
+          bind :443 ssl crt ...
+   .endif
+
    .if version_atleast(2.4-dev19)
        profiling.memory on
    .endif
diff --git a/include/haproxy/cfgcond-t.h b/include/haproxy/cfgcond-t.h
index b154cb2..f4f2796 100644
--- a/include/haproxy/cfgcond-t.h
+++ b/include/haproxy/cfgcond-t.h
@@ -77,4 +77,22 @@
 	};
 };
 
+/* condition sub-expression for an AND:
+ *   expr_and = <term> '&&' <expr_and>
+ *            | <term>
+ */
+struct cfg_cond_and {
+	struct cfg_cond_term *left;
+	struct cfg_cond_and *right; // may be NULL
+};
+
+/* condition expression:
+ *   expr = <expr_and> '||' <expr>
+ *        | <expr_and>
+ */
+struct cfg_cond_expr {
+	struct cfg_cond_and *left;
+	struct cfg_cond_expr *right; // may be NULL
+};
+
 #endif /* _HAPROXY_CFGCOND_T_H */
diff --git a/include/haproxy/cfgcond.h b/include/haproxy/cfgcond.h
index d17772d..6af88a2 100644
--- a/include/haproxy/cfgcond.h
+++ b/include/haproxy/cfgcond.h
@@ -29,6 +29,15 @@
 int cfg_parse_cond_term(const char **text, struct cfg_cond_term **term, char **err, const char **errptr);
 int cfg_eval_cond_term(const struct cfg_cond_term *term, char **err);
 void cfg_free_cond_term(struct cfg_cond_term **term);
+
+int cfg_parse_cond_and(const char **text, struct cfg_cond_and **expr, char **err, const char **errptr);
+int cfg_eval_cond_and(struct cfg_cond_and *expr, char **err);
+void cfg_free_cond_and(struct cfg_cond_and **expr);
+
+int cfg_parse_cond_expr(const char **text, struct cfg_cond_expr **expr, char **err, const char **errptr);
+int cfg_eval_cond_expr(struct cfg_cond_expr *expr, char **err);
+void cfg_free_cond_expr(struct cfg_cond_expr **expr);
+
 int cfg_eval_condition(char **args, char **err, const char **errptr);
 
 #endif
diff --git a/src/cfgcond.c b/src/cfgcond.c
index 5b0aeec..a96832f 100644
--- a/src/cfgcond.c
+++ b/src/cfgcond.c
@@ -206,6 +206,178 @@
 }
 
 
+/* Frees <expr> and its terms and args. NULL is supported and does nothing. */
+void cfg_free_cond_and(struct cfg_cond_and **expr)
+{
+	while (expr && *expr) {
+		cfg_free_cond_term(&(*expr)->left);
+		expr = &(*expr)->right;
+	}
+}
+
+/* Frees <expr> and its terms and args. NULL is supported and does nothing. */
+void cfg_free_cond_expr(struct cfg_cond_expr **expr)
+{
+	while (expr && *expr) {
+		cfg_free_cond_and(&(*expr)->left);
+		expr = &(*expr)->right;
+	}
+}
+
+/* Parse an indirect input text as a possible config condition sub-expr.
+ * Returns <0 on parsing error, 0 if the parser is desynchronized, or >0 on
+ * success. <expr> is filled with the parsed info, and <text> is updated on
+ * success to point to the first unparsed character, or is left untouched
+ * on failure. On success, the caller will have to free all lower-level
+ * allocated structs using cfg_free_cond_expr(). An error will be set in
+ * <err> on error, and only in this case. In this case the first bad
+ * character will be reported in <errptr>.
+ */
+int cfg_parse_cond_and(const char **text, struct cfg_cond_and **expr, char **err, const char **errptr)
+{
+	struct cfg_cond_and *e;
+	const char *in = *text;
+	int ret = -1;
+
+	if (!*in) /* empty expr does not parse */
+		return 0;
+
+	e = *expr = calloc(1, sizeof(**expr));
+	if (!e) {
+		memprintf(err, "memory allocation error while parsing conditional expression '%s'", *text);
+		goto done;
+	}
+
+	ret = cfg_parse_cond_term(&in, &e->left, err, errptr);
+	if (ret == -1) // parse error, error already reported
+		goto done;
+
+	if (ret == 0) {
+		/* ret == 0, no other way to parse this */
+		memprintf(err, "unparsable conditional sub-expression '%s'", in);
+		if (errptr)
+			*errptr = in;
+		ret = -1;
+		goto done;
+	}
+
+	/* ret=1, we have a term in the left hand set */
+
+	/* find an optionnal '&&' */
+	while (*in == ' ' || *in == '\t')
+		in++;
+
+	*text = in;
+	if (in[0] != '&' || in[1] != '&')
+		goto done;
+
+	/* we have a '&&', let's parse the right handset's subexp */
+	in += 2;
+	while (*in == ' ' || *in == '\t')
+		in++;
+
+	ret = cfg_parse_cond_and(&in, &e->right, err, errptr);
+	if (ret > 0)
+		*text = in;
+ done:
+	if (ret < 0)
+		cfg_free_cond_and(expr);
+	return ret;
+}
+
+/* Parse an indirect input text as a possible config condition term.
+ * Returns <0 on parsing error, 0 if the parser is desynchronized, or >0 on
+ * success. <expr> is filled with the parsed info, and <text> is updated on
+ * success to point to the first unparsed character, or is left untouched
+ * on failure. On success, the caller will have to free all lower-level
+ * allocated structs using cfg_free_cond_expr(). An error will be set in
+ * <err> on error, and only in this case. In this case the first bad
+ * character will be reported in <errptr>.
+ */
+int cfg_parse_cond_expr(const char **text, struct cfg_cond_expr **expr, char **err, const char **errptr)
+{
+	struct cfg_cond_expr *e;
+	const char *in = *text;
+	int ret = -1;
+
+	if (!*in) /* empty expr does not parse */
+		return 0;
+
+	e = *expr = calloc(1, sizeof(**expr));
+	if (!e) {
+		memprintf(err, "memory allocation error while parsing conditional expression '%s'", *text);
+		goto done;
+	}
+
+	ret = cfg_parse_cond_and(&in, &e->left, err, errptr);
+	if (ret == -1) // parse error, error already reported
+		goto done;
+
+	if (ret == 0) {
+		/* ret == 0, no other way to parse this */
+		memprintf(err, "unparsable conditional expression '%s'", in);
+		if (errptr)
+			*errptr = in;
+		ret = -1;
+		goto done;
+	}
+
+	/* ret=1, we have a sub-expr in the left hand set */
+
+	/* find an optionnal '||' */
+	while (*in == ' ' || *in == '\t')
+		in++;
+
+	*text = in;
+	if (in[0] != '|' || in[1] != '|')
+		goto done;
+
+	/* we have a '||', let's parse the right handset's subexp */
+	in += 2;
+	while (*in == ' ' || *in == '\t')
+		in++;
+
+	ret = cfg_parse_cond_expr(&in, &e->right, err, errptr);
+	if (ret > 0)
+		*text = in;
+ done:
+	if (ret < 0)
+		cfg_free_cond_expr(expr);
+	return ret;
+}
+
+/* evaluate an sub-expression on a .if/.elif line. The expression is valid and
+ * was already parsed in <expr>. Returns -1 on error (in which case err is
+ * filled with a message, and only in this case), 0 if the condition is false,
+ * 1 if it's true.
+ */
+int cfg_eval_cond_and(struct cfg_cond_and *expr, char **err)
+{
+	int ret;
+
+	/* AND: loop on terms and sub-exp's terms as long as they're TRUE
+	 * (stop on FALSE and ERROR).
+	 */
+	while ((ret = cfg_eval_cond_term(expr->left, err)) > 0 && expr->right)
+		expr = expr->right;
+	return ret;
+}
+
+/* evaluate an expression on a .if/.elif line. The expression is valid and was
+ * already parsed in <expr>. Returns -1 on error (in which case err is filled
+ * with a message, and only in this case), 0 if the condition is false, 1 if
+ * it's true.
+ */
+int cfg_eval_cond_expr(struct cfg_cond_expr *expr, char **err)
+{
+	int ret;
+
+	/* OR: loop on sub-exps as long as they're FALSE (stop on TRUE and ERROR) */
+	while ((ret = cfg_eval_cond_and(expr->left, err)) == 0 && expr->right)
+		expr = expr->right;
+	return ret;
+}
+
 /* evaluate a condition on a .if/.elif line. The condition is already tokenized
  * in <err>. Returns -1 on error (in which case err is filled with a message,
  * and only in this case), 0 if the condition is false, 1 if it's true. If
@@ -213,14 +385,14 @@
  */
 int cfg_eval_condition(char **args, char **err, const char **errptr)
 {
-	struct cfg_cond_term *term = NULL;
+	struct cfg_cond_expr *expr = NULL;
 	const char *text = args[0];
 	int ret = -1;
 
 	if (!*text) /* note: empty = false */
 		return 0;
 
-	ret = cfg_parse_cond_term(&text, &term, err, errptr);
+	ret = cfg_parse_cond_expr(&text, &expr, err, errptr);
 	if (ret != 0) {
 		if (ret == -1) // parse error, error already reported
 			goto done;
@@ -234,7 +406,7 @@
 			goto fail;
 		}
 
-		ret = cfg_eval_cond_term(term, err);
+		ret = cfg_eval_cond_expr(expr, err);
 		goto done;
 	}
 
@@ -245,6 +417,6 @@
 	if (errptr)
 		*errptr = text;
  done:
-	cfg_free_cond_term(&term);
+	cfg_free_cond_expr(&expr);
 	return ret;
 }