[MEDIUM] add ability to connect to a server from an IP found in a header

Using get_ip_from_hdr2() we can look for occurrence #X or #-X and
extract the IP it contains. This is typically designed for use with
the X-Forwarded-For header.

Using "usesrc hdr_ip(name,occ)", it becomes possible to use the IP address
found in <name>, and possibly specify occurrence number <occ>, as the
source to connect to a server. This is possible both in a server and in
a backend's source statement. This is typically used to use the source
IP previously set by a upstream proxy.
diff --git a/src/backend.c b/src/backend.c
index 845a436..ffd3bca 100644
--- a/src/backend.c
+++ b/src/backend.c
@@ -837,8 +837,19 @@
 			/* FIXME: what can we do if the client connects in IPv6 ? */
 			s->from_addr = *(struct sockaddr_in *)&s->cli_addr;
 			break;
+		case SRV_TPROXY_DYN:
+			if (s->srv->bind_hdr_occ) {
+				/* bind to the IP in a header */
+				s->from_addr.sin_port = 0;
+				s->from_addr.sin_addr.s_addr = htonl(get_ip_from_hdr2(&s->txn.req,
+										s->srv->bind_hdr_name,
+										s->srv->bind_hdr_len,
+										&s->txn.hdr_idx,
+										s->srv->bind_hdr_occ));
+			}
+			break;
 		default:
-			s->from_addr = *(struct sockaddr_in *)0;
+			memset(&s->from_addr, 0, sizeof(s->from_addr));
 		}
 	}
 	else if (s->be->options & PR_O_BIND_SRC) {
@@ -851,8 +862,19 @@
 			/* FIXME: what can we do if the client connects in IPv6 ? */
 			s->from_addr = *(struct sockaddr_in *)&s->cli_addr;
 			break;
+		case PR_O_TPXY_DYN:
+			if (s->be->bind_hdr_occ) {
+				/* bind to the IP in a header */
+				s->from_addr.sin_port = 0;
+				s->from_addr.sin_addr.s_addr = htonl(get_ip_from_hdr2(&s->txn.req,
+										s->be->bind_hdr_name,
+										s->be->bind_hdr_len,
+										&s->txn.hdr_idx,
+										s->be->bind_hdr_occ));
+			}
+			break;
 		default:
-			s->from_addr = *(struct sockaddr_in *)0;
+			memset(&s->from_addr, 0, sizeof(s->from_addr));
 		}
 	}
 #endif
diff --git a/src/cfgparse.c b/src/cfgparse.c
index b647ff2..f3783d4 100644
--- a/src/cfgparse.c
+++ b/src/cfgparse.c
@@ -3451,15 +3451,56 @@
 						}
 #endif
 						if (!*args[cur_arg + 1]) {
-							Alert("parsing [%s:%d] : '%s' expects <addr>[:<port>], 'client', or 'clientip' as argument.\n",
+							Alert("parsing [%s:%d] : '%s' expects <addr>[:<port>], 'client', 'clientip', or 'hdr_ip(name,#)' as argument.\n",
 							      file, linenum, "usesrc");
 							err_code |= ERR_ALERT | ERR_FATAL;
 							goto out;
 						}
 						if (!strcmp(args[cur_arg + 1], "client")) {
+							newsrv->state &= ~SRV_TPROXY_MASK;
 							newsrv->state |= SRV_TPROXY_CLI;
 						} else if (!strcmp(args[cur_arg + 1], "clientip")) {
+							newsrv->state &= ~SRV_TPROXY_MASK;
 							newsrv->state |= SRV_TPROXY_CIP;
+						} else if (!strncmp(args[cur_arg + 1], "hdr_ip(", 7)) {
+							char *name, *end;
+
+							name = args[cur_arg+1] + 7;
+							while (isspace(*name))
+								name++;
+
+							end = name;
+							while (*end && !isspace(*end) && *end != ',' && *end != ')')
+								end++;
+
+							newsrv->state &= ~SRV_TPROXY_MASK;
+							newsrv->state |= SRV_TPROXY_DYN;
+							newsrv->bind_hdr_name = calloc(1, end - name + 1);
+							newsrv->bind_hdr_len = end - name;
+							memcpy(newsrv->bind_hdr_name, name, end - name);
+							newsrv->bind_hdr_name[end-name] = '\0';
+							newsrv->bind_hdr_occ = -1;
+
+							/* now look for an occurrence number */
+							while (isspace(*end))
+								end++;
+							if (*end == ',') {
+								end++;
+								name = end;
+								if (*end == '-')
+									end++;
+								while (isdigit(*end))
+									end++;
+								newsrv->bind_hdr_occ = strl2ic(name, end-name);
+							}
+
+							if (newsrv->bind_hdr_occ < -MAX_HDR_HISTORY) {
+								Alert("parsing [%s:%d] : usesrc hdr_ip(name,num) does not support negative"
+								      " occurrences values smaller than %d.\n",
+								      file, linenum, MAX_HDR_HISTORY);
+								err_code |= ERR_ALERT | ERR_FATAL;
+								goto out;
+							}
 						} else {
 							struct sockaddr_in *sk = str2sa(args[cur_arg + 1]);
 							if (!sk) {
@@ -3725,9 +3766,50 @@
 				}
 
 				if (!strcmp(args[cur_arg + 1], "client")) {
+					curproxy->options &= ~PR_O_TPXY_MASK;
 					curproxy->options |= PR_O_TPXY_CLI;
 				} else if (!strcmp(args[cur_arg + 1], "clientip")) {
+					curproxy->options &= ~PR_O_TPXY_MASK;
 					curproxy->options |= PR_O_TPXY_CIP;
+				} else if (!strncmp(args[cur_arg + 1], "hdr_ip(", 7)) {
+					char *name, *end;
+
+					name = args[cur_arg+1] + 7;
+					while (isspace(*name))
+						name++;
+
+					end = name;
+					while (*end && !isspace(*end) && *end != ',' && *end != ')')
+						end++;
+
+					curproxy->options &= ~PR_O_TPXY_MASK;
+					curproxy->options |= PR_O_TPXY_DYN;
+					curproxy->bind_hdr_name = calloc(1, end - name + 1);
+					curproxy->bind_hdr_len = end - name;
+					memcpy(curproxy->bind_hdr_name, name, end - name);
+					curproxy->bind_hdr_name[end-name] = '\0';
+					curproxy->bind_hdr_occ = -1;
+
+					/* now look for an occurrence number */
+					while (isspace(*end))
+						end++;
+					if (*end == ',') {
+						end++;
+						name = end;
+						if (*end == '-')
+							end++;
+						while (isdigit(*end))
+							end++;
+						curproxy->bind_hdr_occ = strl2ic(name, end-name);
+					}
+
+					if (curproxy->bind_hdr_occ < -MAX_HDR_HISTORY) {
+						Alert("parsing [%s:%d] : usesrc hdr_ip(name,num) does not support negative"
+						      " occurrences values smaller than %d.\n",
+						      file, linenum, MAX_HDR_HISTORY);
+						err_code |= ERR_ALERT | ERR_FATAL;
+						goto out;
+					}
 				} else {
 					struct sockaddr_in *sk = str2sa(args[cur_arg + 1]);
 					if (!sk) {
@@ -5051,6 +5133,13 @@
 					curproxy->options2 &= ~cfg_opts2[optnum].val;
 				}
 			}
+
+			if (curproxy->bind_hdr_occ) {
+				curproxy->bind_hdr_occ = 0;
+				Warning("config : %s '%s' : ignoring use of header %s as source IP in non-HTTP mode.\n",
+					proxy_type_str(curproxy), curproxy->id, curproxy->bind_hdr_name);
+				err_code |= ERR_WARN;
+			}
 		}
 
 		/*
@@ -5074,6 +5163,13 @@
 				      proxy_type_str(curproxy), curproxy->id);
 				cfgerr++;
 			}
+
+			if (curproxy->mode != PR_MODE_HTTP && newsrv->bind_hdr_occ) {
+				newsrv->bind_hdr_occ = 0;
+				Warning("config : %s '%s' : server %s cannot use header %s as source IP in non-HTTP mode.\n",
+					proxy_type_str(curproxy), curproxy->id, newsrv->id, newsrv->bind_hdr_name);
+				err_code |= ERR_WARN;
+			}
 			newsrv = newsrv->next;
 		}
 
diff --git a/src/proto_http.c b/src/proto_http.c
index 694e98d..90f9848 100644
--- a/src/proto_http.c
+++ b/src/proto_http.c
@@ -6534,6 +6534,59 @@
 	es->src  = s->cli_addr;
 }
 
+/* return the IP address pointed to by occurrence <occ> of header <hname> in
+ * HTTP message <msg> indexed in <idx>. If <occ> is strictly positive, the
+ * occurrence number corresponding to this value is returned. If <occ> is
+ * strictly negative, the occurrence number before the end corresponding to
+ * this value is returned. If <occ> is null, any value is returned, so it is
+ * not recommended to use it that way. Negative occurrences are limited to
+ * a small value because it is required to keep them in memory while scanning.
+ * IP address 0.0.0.0 is returned if no match is found.
+ */
+unsigned int get_ip_from_hdr2(struct http_msg *msg, const char *hname, int hlen, struct hdr_idx *idx, int occ)
+{
+	struct hdr_ctx ctx;
+	unsigned int hdr_hist[MAX_HDR_HISTORY];
+	unsigned int hist_ptr;
+	int found = 0;
+
+	ctx.idx = 0;
+	if (occ >= 0) {
+		while (http_find_header2(hname, hlen, msg->sol, idx, &ctx)) {
+			occ--;
+			if (occ <= 0) {
+				found = 1;
+				break;
+			}
+		}
+		if (!found)
+			return 0;
+		return inetaddr_host_lim(ctx.line+ctx.val, ctx.line+ctx.val+ctx.vlen);
+	}
+
+	/* negative occurrence, we scan all the list then walk back */
+	if (-occ > MAX_HDR_HISTORY)
+		return 0;
+
+	hist_ptr = 0;
+	hdr_hist[hist_ptr] = 0;
+	while (http_find_header2(hname, hlen, msg->sol, idx, &ctx)) {
+		hdr_hist[hist_ptr++] = inetaddr_host_lim(ctx.line+ctx.val, ctx.line+ctx.val+ctx.vlen);
+		if (hist_ptr >= MAX_HDR_HISTORY)
+			hist_ptr = 0;
+		found++;
+	}
+	if (-occ > found)
+		return 0;
+	/* OK now we have the last occurrence in [hist_ptr-1], and we need to
+	 * find occurrence -occ, so we have to check [hist_ptr+occ].
+	 */
+	hist_ptr += occ;
+	if (hist_ptr >= MAX_HDR_HISTORY)
+		hist_ptr -= MAX_HDR_HISTORY;
+	return hdr_hist[hist_ptr];
+}
+
 /*
  * Print a debug line with a header
  */