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.
This commit is contained in:
William Lallemand 2015-05-12 14:27:13 +02:00 committed by Willy Tarreau
parent 64e84516c4
commit b2f07451e5
2 changed files with 137 additions and 33 deletions

View File

@ -40,8 +40,9 @@ Summary
2. Configuring HAProxy 2. Configuring HAProxy
2.1. Configuration file format 2.1. Configuration file format
2.2. Quoting and escaping 2.2. Quoting and escaping
2.3. Time format 2.3. Environment variables
2.4. Examples 2.4. Time format
2.5. Examples
3. Global parameters 3. Global parameters
3.1. Process management and security 3.1. Process management and security
@ -397,7 +398,11 @@ the interpretation of:
' single quote as a strong quoting delimiter ' single quote as a strong quoting delimiter
# hash as a comment start # 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. quoting.
Strong quoting is achieved by using single quotes (''). Inside single quotes, Strong quoting is achieved by using single quotes (''). Inside single quotes,
@ -421,7 +426,26 @@ equivalent, it allows you to perform concatenation.
reqrep "^([^ :]*)\ /static/(.*)" "\1\ /\2" reqrep "^([^ :]*)\ /static/(.*)" "\1\ /\2"
2.3. Time format 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.4. Time format
---------------- ----------------
Some parameters involve values representing time, such as timeouts. These Some parameters involve values representing time, such as timeouts. These
@ -640,10 +664,8 @@ log <address> [len <length>] <facility> [max level [min level]]
the chroot) and uid/gid (be sure the path is appropriately the chroot) and uid/gid (be sure the path is appropriately
writeable). writeable).
Any part of the address string may reference any number of environment You may want to reference some environment variables in the address
variables by preceding their name with a dollar sign ('$') and parameter, see section 2.3 about environment variables.
optionally enclosing them with braces ('{}'), similarly to what is done
in Bourne shell.
<length> is an optional maximum line length. Log lines larger than this value <length> is an optional maximum line length. Log lines larger than this value
will be truncated before being sent. The reason is that syslog will be truncated before being sent. The reason is that syslog
@ -1318,9 +1340,8 @@ peer <peername> <ip>:<port>
peer name. This makes it easier to maintain coherent configuration files peer name. This makes it easier to maintain coherent configuration files
across all peers. across all peers.
Any part of the address string may reference any number of environment You may want to reference some environment variables in the address
variables by preceding their name with a dollar sign ('$') and optionally parameter, see section 2.3 about environment variables.
enclosing them with braces ('{}'), similarly to what is done in Bourne shell.
Example: Example:
peers mypeers peers mypeers
@ -2011,10 +2032,9 @@ bind /<path> [, ...] [param*]
- 'fd@<n>' -> use file descriptor <n> inherited from the - 'fd@<n>' -> use file descriptor <n> inherited from the
parent. The fd must be bound and may or may not already parent. The fd must be bound and may or may not already
be listening. be listening.
Any part of the address string may reference any number of You may want to reference some environment variables in the
environment variables by preceding their name with a dollar address parameter, see section 2.3 about environment
sign ('$') and optionally enclosing them with braces ('{}'), variables.
similarly to what is done in Bourne shell.
<port_range> is either a unique TCP port, or a port range for which the <port_range> is either a unique TCP port, or a port range for which the
proxy will accept connections for the IP address specified proxy will accept connections for the IP address specified
@ -2072,7 +2092,7 @@ bind /<path> [, ...] [param*]
bind unix@ssl-frontend.sock user root mode 600 accept-proxy bind unix@ssl-frontend.sock user root mode 600 accept-proxy
listen external_bind_app1 listen external_bind_app1
bind fd@${FD_APP1} bind "fd@${FD_APP1}"
See also : "source", "option forwardfor", "unix-bind" and the PROXY protocol See also : "source", "option forwardfor", "unix-bind" and the PROXY protocol
documentation, and section 5 about bind options. documentation, and section 5 about bind options.
@ -3846,10 +3866,8 @@ no log
inside the chroot) and uid/gid (be sure the path is inside the chroot) and uid/gid (be sure the path is
appropriately writeable). appropriately writeable).
Any part of the address string may reference any number of You may want to reference some environment variables in the
environment variables by preceding their name with a dollar address parameter, see section 2.3 about environment variables.
sign ('$') and optionally enclosing them with braces ('{}'),
similarly to what is done in Bourne shell.
<length> is an optional maximum line length. Log lines larger than this <length> is an optional maximum line length. Log lines larger than this
value will be truncated before being sent. The reason is that value will be truncated before being sent. The reason is that
@ -3896,7 +3914,7 @@ no log
log global log global
log 127.0.0.1:514 local0 notice # only send important events 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 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> log-format <string>
@ -6525,10 +6543,9 @@ server <name> <address>[:[port]] [param*]
- 'ipv6@' -> address is always IPv6 - 'ipv6@' -> address is always IPv6
- 'unix@' -> address is a path to a local unix socket - 'unix@' -> address is a path to a local unix socket
- 'abns@' -> address is in abstract namespace (Linux only) - 'abns@' -> address is in abstract namespace (Linux only)
Any part of the address string may reference any number of You may want to reference some environment variables in the
environment variables by preceding their name with a dollar address parameter, see section 2.3 about environment
sign ('$') and optionally enclosing them with braces ('{}'), variables.
similarly to what is done in Bourne shell.
<port> is an optional port specification. If set, all connections will <port> is an optional port specification. If set, all connections will
be sent to this port. If unset, the same port the client be sent to this port. If unset, the same port the client
@ -6544,9 +6561,9 @@ server <name> <address>[:[port]] [param*]
server first 10.1.1.1:1080 cookie first check inter 1000 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 second 10.1.1.2:1080 cookie second check inter 1000
server transp ipv4@ server transp ipv4@
server backup ${SRV_BACKUP}:1080 backup server backup "${SRV_BACKUP}:1080" backup
server www1_dc1 ${LAN_DC1}.101:80 server www1_dc1 "${LAN_DC1}.101:80"
server www1_dc2 ${LAN_DC2}.101:80 server www1_dc2 "${LAN_DC2}.101:80"
See also: "default-server", "http-send-name-header" and section 5 about See also: "default-server", "http-send-name-header" and section 5 about
server options server options
@ -6572,10 +6589,8 @@ source <addr>[:<port>] [interface <name>]
- 'ipv6@' -> address is always IPv6 - 'ipv6@' -> address is always IPv6
- 'unix@' -> address is a path to a local unix socket - 'unix@' -> address is a path to a local unix socket
- 'abns@' -> address is in abstract namespace (Linux only) - 'abns@' -> address is in abstract namespace (Linux only)
Any part of the address string may reference any number of You may want to reference some environment variables in the address
environment variables by preceding their name with a dollar parameter, see section 2.3 about environment variables.
sign ('$') and optionally enclosing them with braces ('{}'),
similarly to what is done in Bourne shell.
<port> is an optional port. It is normally not needed but may be useful <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 in some very specific contexts. The default value of zero means

View File

@ -6284,7 +6284,7 @@ int readcfgfile(const char *file)
int readbytes = 0; int readbytes = 0;
if ((thisline = malloc(sizeof(*thisline) * linesize)) == NULL) { 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; return -1;
} }
@ -6304,6 +6304,7 @@ int readcfgfile(const char *file)
if ((f=fopen(file,"r")) == NULL) if ((f=fopen(file,"r")) == NULL)
return -1; return -1;
next_line:
while (fgets(thisline + readbytes, linesize - readbytes, f) != NULL) { while (fgets(thisline + readbytes, linesize - readbytes, f) != NULL) {
int arg, kwm = KWM_STD; int arg, kwm = KWM_STD;
char *end; char *end;
@ -6404,6 +6405,9 @@ int readcfgfile(const char *file)
} else if (line[1] == '\'') { } else if (line[1] == '\'') {
*line = '\''; *line = '\'';
skip = 1; skip = 1;
} else if (line[1] == '$' && dquote) { /* escaping of $ only inside double quotes */
*line = '$';
skip = 1;
} }
if (skip) { if (skip) {
memmove(line + 1, line + 1 + skip, end - (line + skip)); memmove(line + 1, line + 1 + skip, end - (line + skip));
@ -6423,10 +6427,95 @@ int readcfgfile(const char *file)
line++; line++;
args[++arg] = 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 { else {
line++; line++;
} }
} }
if (dquote) { if (dquote) {
Alert("parsing [%s:%d] : Mismatched double quotes.\n", file, linenum); Alert("parsing [%s:%d] : Mismatched double quotes.\n", file, linenum);
err_code |= ERR_ALERT | ERR_FATAL; err_code |= ERR_ALERT | ERR_FATAL;