MEDIUM: cfgparse: expand environment variables

Environment variables were expandables only in adresses.
Now there are expandables everywhere in the configuration file within
double quotes.

This patch breaks compatibility with the previous behavior of
environment variables in adresses, you must enclose adresses with double
quotes to make it work.
diff --git a/doc/configuration.txt b/doc/configuration.txt
index 9c00943..c1e2741 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -40,8 +40,9 @@
 2.    Configuring HAProxy
 2.1.      Configuration file format
 2.2.      Quoting and escaping
-2.3.      Time format
-2.4.      Examples
+2.3.      Environment variables
+2.4.      Time format
+2.5.      Examples
 
 3.    Global parameters
 3.1.      Process management and security
@@ -397,7 +398,11 @@
   '    single quote as a strong quoting delimiter
   #    hash as a comment start
 
-But interpretation of escaping and special characters are not prevented by weak
+Weak quoting permits the interpretation of variables, if you want to use a non
+-interpreted dollar within a double quoted string, you should escape it with a
+backslash ("\$"), it does not work outside weak quoting.
+
+Interpretation of escaping and special characters are not prevented by weak
 quoting.
 
 Strong quoting is achieved by using single quotes (''). Inside single quotes,
@@ -421,7 +426,26 @@
       reqrep "^([^ :]*)\ /static/(.*)"     "\1\ /\2"
 
 
+2.3. Environment variables
+--------------------------
+
+HAProxy's configuration supports environment variables. Those variables are
+interpreted only within double quotes. Variables are expanded during the
+configuration parsing. Variable names must be preceded by a dollar ("$") and
+optionally enclosed with braces ("{}") similarly to what is done in Bourne
+shell. Variable names can contain alphanumerical characters or the character
+underscore ("_") but should not start with a digit.
+
+  Example:
+
+      bind "fd@${FD_APP1}"
+
+      log "${LOCAL_SYSLOG}:514" local0 notice   # send to local server
+
+      user "$HAPROXY_USER"
+
+
-2.3. Time format
+2.4. Time format
 ----------------
 
 Some parameters involve values representing time, such as timeouts. These
@@ -640,10 +664,8 @@
           the chroot) and uid/gid (be sure the path is appropriately
           writeable).
 
-        Any part of the address string may reference any number of environment
-        variables by preceding their name with a dollar sign ('$') and
-        optionally enclosing them with braces ('{}'), similarly to what is done
-        in Bourne shell.
+        You may want to reference some environment variables in the address
+        parameter, see section 2.3 about environment variables.
 
   <length> is an optional maximum line length. Log lines larger than this value
            will be truncated before being sent. The reason is that syslog
@@ -1318,9 +1340,8 @@
   peer name. This makes it easier to maintain coherent configuration files
   across all peers.
 
-  Any part of the address string may reference any number of environment
-  variables by preceding their name with a dollar sign ('$') and optionally
-  enclosing them with braces ('{}'), similarly to what is done in Bourne shell.
+  You may want to reference some environment variables in the address
+  parameter, see section 2.3 about environment variables.
 
   Example:
     peers mypeers
@@ -2011,10 +2032,9 @@
                     - 'fd@<n>' -> use file descriptor <n> inherited from the
                       parent. The fd must be bound and may or may not already
                       be listening.
-                  Any part of the address string may reference any number of
-                  environment variables by preceding their name with a dollar
-                  sign ('$') and optionally enclosing them with braces ('{}'),
-                  similarly to what is done in Bourne shell.
+                  You may want to reference some environment variables in the
+                  address parameter, see section 2.3 about environment
+                  variables.
 
     <port_range>  is either a unique TCP port, or a port range for which the
                   proxy will accept connections for the IP address specified
@@ -2072,7 +2092,7 @@
             bind unix@ssl-frontend.sock user root mode 600 accept-proxy
 
         listen external_bind_app1
-            bind fd@${FD_APP1}
+            bind "fd@${FD_APP1}"
 
   See also : "source", "option forwardfor", "unix-bind" and the PROXY protocol
              documentation, and section 5 about bind options.
@@ -3846,10 +3866,8 @@
                  inside the chroot) and uid/gid (be sure the path is
                  appropriately writeable).
 
-               Any part of the address string may reference any number of
-               environment variables by preceding their name with a dollar
-               sign ('$') and optionally enclosing them with braces ('{}'),
-               similarly to what is done in Bourne shell.
+              You may want to reference some environment variables in the
+              address parameter, see section 2.3 about environment variables.
 
     <length>   is an optional maximum line length. Log lines larger than this
                value will be truncated before being sent. The reason is that
@@ -3896,7 +3914,7 @@
     log global
     log 127.0.0.1:514 local0 notice         # only send important events
     log 127.0.0.1:514 local0 notice notice  # same but limit output level
-    log ${LOCAL_SYSLOG}:514 local0 notice   # send to local server
+    log "${LOCAL_SYSLOG}:514" local0 notice   # send to local server
 
 
 log-format <string>
@@ -6525,10 +6543,9 @@
                     - 'ipv6@'  -> address is always IPv6
                     - 'unix@'  -> address is a path to a local unix socket
                     - 'abns@'  -> address is in abstract namespace (Linux only)
-              Any part of the address string may reference any number of
-              environment variables by preceding their name with a dollar
-              sign ('$') and optionally enclosing them with braces ('{}'),
-              similarly to what is done in Bourne shell.
+              You may want to reference some environment variables in the
+              address parameter, see section 2.3 about environment
+              variables.
 
     <port>    is an optional port specification. If set, all connections will
               be sent to this port. If unset, the same port the client
@@ -6544,9 +6561,9 @@
         server first  10.1.1.1:1080 cookie first  check inter 1000
         server second 10.1.1.2:1080 cookie second check inter 1000
         server transp ipv4@
-        server backup ${SRV_BACKUP}:1080 backup
-        server www1_dc1 ${LAN_DC1}.101:80
-        server www1_dc2 ${LAN_DC2}.101:80
+        server backup "${SRV_BACKUP}:1080" backup
+        server www1_dc1 "${LAN_DC1}.101:80"
+        server www1_dc2 "${LAN_DC2}.101:80"
 
   See also: "default-server", "http-send-name-header" and section 5 about
              server options
@@ -6572,10 +6589,8 @@
                 - 'ipv6@' -> address is always IPv6
                 - 'unix@' -> address is a path to a local unix socket
                 - 'abns@' -> address is in abstract namespace (Linux only)
-              Any part of the address string may reference any number of
-              environment variables by preceding their name with a dollar
-              sign ('$') and optionally enclosing them with braces ('{}'),
-              similarly to what is done in Bourne shell.
+              You may want to reference some environment variables in the address
+              parameter, see section 2.3 about environment variables.
 
     <port>    is an optional port. It is normally not needed but may be useful
               in some very specific contexts. The default value of zero means
diff --git a/src/cfgparse.c b/src/cfgparse.c
index 04cbd1d..c18628a 100644
--- a/src/cfgparse.c
+++ b/src/cfgparse.c
@@ -6284,7 +6284,7 @@
 	int readbytes = 0;
 
 	if ((thisline = malloc(sizeof(*thisline) * linesize)) == NULL) {
-		Alert("parsing [%s:%d] : out of memory.\n", file, linenum);
+		Alert("parsing [%s] : out of memory.\n", file);
 		return -1;
 	}
 
@@ -6304,6 +6304,7 @@
 	if ((f=fopen(file,"r")) == NULL)
 		return -1;
 
+next_line:
 	while (fgets(thisline + readbytes, linesize - readbytes, f) != NULL) {
 		int arg, kwm = KWM_STD;
 		char *end;
@@ -6404,6 +6405,9 @@
 				} else if (line[1] == '\'') {
 					*line = '\'';
 					skip = 1;
+				} else if (line[1] == '$' && dquote) { /* escaping of $ only inside double quotes */
+					*line = '$';
+					skip = 1;
 				}
 				if (skip) {
 					memmove(line + 1, line + 1 + skip, end - (line + skip));
@@ -6423,10 +6427,95 @@
 					line++;
 				args[++arg] = line;
 			}
+			else if (dquote && *line == '$') {
+				/* environment variables are evaluated inside double quotes */
+				char *var_beg;
+				char *var_end;
+				char save_char;
+				char *value;
+				int val_len;
+				int newlinesize;
+				int braces = 0;
+
+				var_beg = line + 1;
+				var_end = var_beg;
+
+				if (*var_beg == '{') {
+					var_beg++;
+					var_end++;
+					braces = 1;
+				}
+
+				if (!isalpha((int)(unsigned char)*var_beg) && *var_beg != '_') {
+					Alert("parsing [%s:%d] : Variable expansion: Unrecognized character '%c' in variable name.\n", file, linenum, *var_beg);
+					err_code |= ERR_ALERT | ERR_FATAL;
+					goto next_line; /* skip current line */
+				}
+
+				while (isalnum((int)(unsigned char)*var_end) || *var_end == '_')
+					var_end++;
+
+				save_char = *var_end;
+				*var_end = '\0';
+				value = getenv(var_beg);
+				*var_end = save_char;
+				val_len = value ? strlen(value) : 0;
+
+				if (braces) {
+					if (*var_end == '}') {
+						var_end++;
+						braces = 0;
+					} else {
+						Alert("parsing [%s:%d] : Variable expansion: Mismatched braces.\n", file, linenum);
+						err_code |= ERR_ALERT | ERR_FATAL;
+						goto next_line; /* skip current line */
+					}
+				}
+
+				newlinesize = (end - thisline) - (var_end - line) + val_len + 1;
+
+				/* if not enough space in thisline */
+				if (newlinesize  > linesize) {
+					char *newline;
+
+					newline = realloc(thisline, newlinesize * sizeof(*thisline));
+					if (newline == NULL) {
+						Alert("parsing [%s:%d] : Variable expansion: Not enough memory.\n", file, linenum);
+						err_code |= ERR_ALERT | ERR_FATAL;
+						goto next_line; /* slip current line */
+					}
+					/* recompute pointers if realloc returns a new pointer */
+					if (newline != thisline) {
+						int i;
+						int diff;
+
+						for (i = 0; i <= arg; i++) {
+							diff = args[i] - thisline;
+							args[i] = newline + diff;
+						}
+
+						diff = var_end - thisline;
+						var_end = newline + diff;
+						diff = end - thisline;
+						end = newline + diff;
+						diff = line - thisline;
+						line = newline + diff;
+						thisline = newline;
+					}
+					linesize = newlinesize;
+				}
+
+				/* insert value inside the line */
+				memmove(line + val_len, var_end, end - var_end + 1);
+				memcpy(line, value, val_len);
+				end += val_len - (var_end - line);
+				line += val_len;
+			}
 			else {
 				line++;
 			}
 		}
+
 		if (dquote) {
 			Alert("parsing [%s:%d] : Mismatched double quotes.\n", file, linenum);
 			err_code |= ERR_ALERT | ERR_FATAL;