[MEDIUM] add the "except" keyword to the "forwardfor" option

Patch from Bryan Germann for 1.2.17.
In some circumstances, it is useful not to add the X-Forwarded-For
header, for instance when the client is another reverse-proxy or
stunnel running on the same machine and which already adds it. This
patch adds the "except" keyword to the "forwardfor" option, allowing
to specify an address or network which will not be added to this
header.
diff --git a/doc/haproxy-en.txt b/doc/haproxy-en.txt
index dbe78f5..eecc6c3 100644
--- a/doc/haproxy-en.txt
+++ b/doc/haproxy-en.txt
@@ -2142,7 +2142,12 @@
 
 Also, the 'forwardfor' option creates an HTTP 'X-Forwarded-For' header which
 contains the client's IP address. This is useful to let the final web server
-know what the client address was (eg for statistics on domains).
+know what the client address was (eg for statistics on domains). Starting with
+version 1.3.8, it is possible to specify the "except" keyword followed by a
+source IP address or network for which no header will be added. This is very
+useful when another reverse-proxy which already adds the header runs on the
+same machine or in a known DMZ, the most common case being the local use of
+stunnel on the same system.
 
 Last, the 'httpclose' option removes any 'Connection' header both ways, and
 adds a 'Connection: close' header in each direction. This makes it easier to
@@ -2155,7 +2160,7 @@
         log  global
         option httplog
         option dontlognull
-        option forwardfor
+        option forwardfor except 127.0.0.1/8
         option httpclose
 
 Note that some HTTP servers do not necessarily close the connections when they
diff --git a/doc/haproxy-fr.txt b/doc/haproxy-fr.txt
index 7f6d231..7a2faf5 100644
--- a/doc/haproxy-fr.txt
+++ b/doc/haproxy-fr.txt
@@ -2223,7 +2223,12 @@
 
 De plus, l'option 'forwardfor' ajoute l'adresse IP du client dans un champ
 'X-Forwarded-For' de la requête, ce qui permet à un serveur web final de
-connaître l'adresse IP du client initial.
+connaître l'adresse IP du client initial. Depuis la version 1.3.8, il est
+possible de préciser le mot-clé "except" suivi d'une adresse ou un réseau
+IP source pour lequel l'entête ne sera pas ajouté. C'est très pratique dans le
+cas où un autre reverse-proxy ajoutant déjà l'entête est installé sur la même
+machine ou dans une DMZ connue. Le cas le plus fréquent est lié à l'utilisation
+de stunnel en local.
 
 Enfin, l'option 'httpclose' apparue dans la version 1.1.28/1.2.1 supprime tout
 en-tête de type 'Connection:' et ajoute 'Connection: close' dans les deux sens.
@@ -2237,7 +2242,7 @@
         log  global
         option httplog
         option dontlognull
-        option forwardfor
+        option forwardfor except 127.0.0.1/8
         option httpclose
 
 Notons que certains serveurs HTTP ne referment pas nécessairement la session
diff --git a/include/types/proxy.h b/include/types/proxy.h
index a709ada..b598e4b 100644
--- a/include/types/proxy.h
+++ b/include/types/proxy.h
@@ -113,6 +113,7 @@
 	unsigned int cum_feconn, cum_beconn;	/* cumulated number of processed sessions */
 	unsigned int maxconn;			/* max # of active sessions on the frontend */
 	unsigned int fullconn;			/* #conns on backend above which servers are used at full load */
+	struct in_addr except_net, except_mask; /* don't x-forward-for for this address. FIXME: should support IPv6 */
 	unsigned failed_conns, failed_resp;	/* failed connect() and responses */
 	unsigned denied_req, denied_resp;	/* blocked requests/responses because of security concerns */
 	unsigned failed_req;			/* failed requests (eg: invalid or timeout) */
diff --git a/src/cfgparse.c b/src/cfgparse.c
index 4c1f032..885a01b 100644
--- a/src/cfgparse.c
+++ b/src/cfgparse.c
@@ -85,7 +85,6 @@
 #endif
 	{ "redispatch",   PR_O_REDISP,     PR_CAP_BE, 0 },
 	{ "keepalive",    PR_O_KEEPALIVE,  PR_CAP_NONE, 0 },
-	{ "forwardfor",   PR_O_FWDFOR,     PR_CAP_FE | PR_CAP_BE, 0 },
 	{ "httpclose",    PR_O_HTTP_CLOSE, PR_CAP_FE | PR_CAP_BE, 0 },
 	{ "logasap",      PR_O_LOGASAP,    PR_CAP_FE, 0 },
 	{ "abortonclose", PR_O_ABRT_CLOSE, PR_CAP_BE, 0 },
@@ -501,6 +500,8 @@
 		/* set default values */
 		curproxy->state = defproxy.state;
 		curproxy->options = defproxy.options;
+		curproxy->except_net = defproxy.except_net;
+		curproxy->except_mask = defproxy.except_mask;
 
 		if (curproxy->cap & PR_CAP_FE) {
 			curproxy->maxconn = defproxy.maxconn;
@@ -1022,6 +1023,27 @@
 			curproxy->options &= ~PR_O_HTTP_CHK;
 			curproxy->options |= PR_O_SSL3_CHK;
 		}
+		else if (!strcmp(args[1], "forwardfor")) {
+			/* insert x-forwarded-for field, but not for the
+			 * IP address listed as an except.
+			 */
+			if (*(args[2])) {
+				if (!strcmp(args[2], "except")) {
+					if (!*args[3] || !str2net(args[3], &curproxy->except_net, &curproxy->except_mask)) {
+						Alert("parsing [%s:%d] : '%s' only supports optional 'except' address[/mask].\n",
+						      file, linenum, args[0]);
+						return -1;
+					}
+					/* flush useless bits */
+					curproxy->except_net.s_addr &= curproxy->except_mask.s_addr;
+				} else {
+					Alert("parsing [%s:%d] : '%s' only supports optional 'except' address[/mask].\n",
+					      file, linenum, args[0]);
+					return -1;
+				}
+			}
+			curproxy->options |= PR_O_FWDFOR;
+		}
 		else {
 			Alert("parsing [%s:%d] : unknown option '%s'.\n", file, linenum, args[1]);
 			return -1;
diff --git a/src/proto_http.c b/src/proto_http.c
index d3bc5f6..b018590 100644
--- a/src/proto_http.c
+++ b/src/proto_http.c
@@ -1656,17 +1656,31 @@
 		 */
 		if ((t->fe->options | t->be->beprm->options) & PR_O_FWDFOR) {
 			if (t->cli_addr.ss_family == AF_INET) {
-				int len;
-				unsigned char *pn;
-				pn = (unsigned char *)&((struct sockaddr_in *)&t->cli_addr)->sin_addr;
-				len = sprintf(trash, "X-Forwarded-For: %d.%d.%d.%d",
-					      pn[0], pn[1], pn[2], pn[3]);
+				/* Add an X-Forwarded-For header unless the source IP is
+				 * in the 'except' network range.
+				 */
+				if ((!t->fe->except_mask.s_addr ||
+				     (((struct sockaddr_in *)&t->cli_addr)->sin_addr.s_addr & t->fe->except_mask.s_addr)
+				     != t->fe->except_net.s_addr) &&
+				    (!t->be->except_mask.s_addr ||
+				     (((struct sockaddr_in *)&t->cli_addr)->sin_addr.s_addr & t->be->except_mask.s_addr)
+				     != t->be->except_net.s_addr)) {
+					int len;
+					unsigned char *pn;
+					pn = (unsigned char *)&((struct sockaddr_in *)&t->cli_addr)->sin_addr;
 
-				if (unlikely(http_header_add_tail2(req, &txn->req,
-								   &txn->hdr_idx, trash, len)) < 0)
-					goto return_bad_req;
+					len = sprintf(trash, "X-Forwarded-For: %d.%d.%d.%d",
+						      pn[0], pn[1], pn[2], pn[3]);
+
+					if (unlikely(http_header_add_tail2(req, &txn->req,
+									   &txn->hdr_idx, trash, len)) < 0)
+						goto return_bad_req;
+				}
 			}
 			else if (t->cli_addr.ss_family == AF_INET6) {
+				/* FIXME: for the sake of completeness, we should also support
+				 * 'except' here, although it is mostly useless in this case.
+				 */
 				int len;
 				char pn[INET6_ADDRSTRLEN];
 				inet_ntop(AF_INET6,