* released 1.1.6
* regex are now chained and not limited anymore.
* unavailable server now returns HTTP/502.
* increased per-line args limit to 40
* added reqallow/reqdeny to block some request on matches
* added HTTP 400/403 responses
* added a 'NOTES' file
diff --git a/NOTES b/NOTES
new file mode 100644
index 0000000..d462195
--- /dev/null
+++ b/NOTES
@@ -0,0 +1,13 @@
+1.1.5 -> 1.1.6
+  * added reqdeny / reqallow rules
+  * added HTTP 400 and 403 responses
+  * chain regex in a list
+  * reply 502 when no server is available
+1.1.6 -> 1.1.7
+  - implement global logging
+  - have a single log function
+  - handle parametrable HTTP health-checks replies
+  - differentiate http headers and http uris
+  - log http requests on demand, and destination server IP
+  - add x-forwarded-for
+
diff --git a/examples/rc.highsock b/examples/rc.highsock
index 76875ea..9325ee0 100644
--- a/examples/rc.highsock
+++ b/examples/rc.highsock
@@ -8,12 +8,12 @@
 	echo 65536 > /proc/sys/net/ipv4/ip_conntrack_max
 fi
 
-if [ -e /proc/sys/net/ipv4/netfilter/ip_ct_tcp_timeout_fin_wait ]; then
+if [ -e /proc/sys/net/ipv4/netfilter/ip_conntrack_tcp_timeout_fin_wait ]; then
 	# 30 seconds for fin, 15 for time wait
-	echo 3000 > /proc/sys/net/ipv4/netfilter/ip_ct_tcp_timeout_fin_wait
-	echo 1500 > /proc/sys/net/ipv4/netfilter/ip_ct_tcp_timeout_time_wait
-	echo 0 > /proc/sys/net/ipv4/netfilter/ip_ct_tcp_log_invalid_scale
-	echo 0 > /proc/sys/net/ipv4/netfilter/ip_ct_tcp_log_out_of_window
+	echo 3000 > /proc/sys/net/ipv4/netfilter/ip_conntrack_tcp_timeout_fin_wait
+	echo 1500 > /proc/sys/net/ipv4/netfilter/ip_conntrack_tcp_timeout_time_wait
+	echo 0 > /proc/sys/net/ipv4/netfilter/ip_conntrack_tcp_log_invalid_scale
+	echo 0 > /proc/sys/net/ipv4/netfilter/ip_conntrack_tcp_log_out_of_window
 fi
 
 echo 1024 60999 > /proc/sys/net/ipv4/ip_local_port_range
diff --git a/haproxy.c b/haproxy.c
index f2832de..bc17e3e 100644
--- a/haproxy.c
+++ b/haproxy.c
@@ -13,21 +13,23 @@
  *
  * ChangeLog :
  *
- * 2002/04/03
- *   - released 1.1.5
+ * 2002/04/08 : 1.1.6
+ *   - regex are now chained and not limited anymore.
+ *   - unavailable server now returns HTTP/502.
+ *   - increased per-line args limit to 40
+ *   - added reqallow/reqdeny to block some request on matches
+ *   - added HTTP 400/403 responses
+ * 2002/04/03 : 1.1.5
  *   - connection logging displayed incorrect source address.
  *   - added proxy start/stop and server up/down log events.
  *   - replaced log message short buffers with larger trash.
  *   - enlarged buffer to 8 kB and replace buffer to 4 kB.
- * 2002/03/25
- *   - released 1.1.4
+ * 2002/03/25 : 1.1.4
  *   - made rise/fall/interval time configurable
- * 2002/03/22
- *   - released 1.1.3
+ * 2002/03/22 : 1.1.3
  *   - fixed a bug : cr_expire and cw_expire were inverted in CL_STSHUT[WR]
  *     which could lead to loops.
- * 2002/03/21
- *   - released 1.1.2
+ * 2002/03/21 : 1.1.2
  *   - fixed a bug in buffer management where we could have a loop
  *     between event_read() and process_{cli|srv} if R==BUFSIZE-MAXREWRITE.
  *     => implemented an adjustable buffer limit.
@@ -35,12 +37,10 @@
  *     and running tasks are skipped.
  *   - added some debug lines for accept events.
  *   - send warnings for servers up/down.
- * 2002/03/12
- *   - released 1.1.1
+ * 2002/03/12 : 1.1.1
  *   - fixed a bug in total failure handling
  *   - fixed a bug in timestamp comparison within same second (tv_cmp_ms)
- * 2002/03/10
- *   - released 1.1.0
+ * 2002/03/10 : 1.1.0
  *   - fixed a few timeout bugs
  *   - rearranged the task scheduler subsystem to improve performance,
  *     add new tasks, and make it easier to later port to librt ;
@@ -108,8 +108,8 @@
 #include <linux/netfilter_ipv4.h>
 #endif
 
-#define HAPROXY_VERSION "1.1.5"
-#define HAPROXY_DATE	"2002/04/03"
+#define HAPROXY_VERSION "1.1.6pre4"
+#define HAPROXY_DATE	"2002/04/07"
 
 /* this is for libc5 for example */
 #ifndef TCP_NODELAY
@@ -130,10 +130,10 @@
 #define	MAXREWRITE	4096
 
 // max # args on a configuration line
-#define MAX_LINE_ARGS	10
+#define MAX_LINE_ARGS	40
 
-// max # of regexps per proxy
-#define	MAX_REGEXP	10
+// max # of added headers per request
+#define MAX_NEWHDR	10
 
 // max # of matches per regexp
 #define	MAX_MATCH	10
@@ -271,8 +271,12 @@
 #define PR_O_BALANCE_RR	32	/* balance in round-robin mode */
 #define PR_O_BALANCE	(PR_O_BALANCE_RR)
 
-/* various task flags */
-#define TF_DIRECT	1	/* connection made on the server matching the client cookie */
+/* various session flags */
+#define SN_DIRECT	1	/* connection made on the server matching the client cookie */
+#define SN_CLDENY	2	/* a client header matches a deny regex */
+#define SN_CLALLOW	4	/* a client header matches an allow regex */
+#define SN_SVDENY	8	/* a server header matches a deny regex */
+#define SN_SVALLOW	16	/* a server header matches an allow regex */
 
 /* different possible states for the client side */
 #define CL_STHEADERS	0
@@ -306,6 +310,12 @@
 /* server flags */
 #define SRV_RUNNING	1
 
+/* what to do when a header matches a regex */
+#define ACT_ALLOW	0	/* allow the request */
+#define ACT_REPLACE	1	/* replace the matching header */
+#define ACT_REMOVE	2	/* remove the matching header */
+#define ACT_DENY	3	/* deny the request */
+
 /*********************************************************************/
 
 #define LIST_HEAD(a)	((void *)(&(a)))
@@ -313,8 +323,10 @@
 /*********************************************************************/
 
 struct hdr_exp {
-    regex_t *preg;	/* expression to look for */
-    char *replace;	/* expression to set instead */
+    struct hdr_exp *next;
+    regex_t *preg;			/* expression to look for */
+    int action;				/* ACT_ALLOW, ACT_REPLACE, ACT_REMOVE, ACT_DENY */
+    char *replace;			/* expression to set instead */
 };
 
 struct buffer {
@@ -395,10 +407,10 @@
     struct sockaddr_in logsrv1, logsrv2; /* 2 syslog servers */
     char logfac1, logfac2;		/* log facility for both servers. -1 = disabled */
     struct timeval stop_time;		/* date to stop listening, when stopping != 0 */
-    int nb_reqexp, nb_rspexp, nb_reqadd, nb_rspadd;
-    struct hdr_exp req_exp[MAX_REGEXP];	/* regular expressions for request headers */
-    struct hdr_exp rsp_exp[MAX_REGEXP];	/* regular expressions for response headers */
-    char *req_add[MAX_REGEXP], *rsp_add[MAX_REGEXP]; /* headers to be added */
+    int nb_reqadd, nb_rspadd;
+    struct hdr_exp *req_exp;		/* regular expressions for request headers */
+    struct hdr_exp *rsp_exp;		/* regular expressions for response headers */
+    char *req_add[MAX_NEWHDR], *rsp_add[MAX_NEWHDR]; /* headers to be added */
     int grace;				/* grace time after stop request */
 };
 
@@ -479,6 +491,27 @@
 #define MAX_HOSTNAME_LEN	32
 static char hostname[MAX_HOSTNAME_LEN] = "";
 
+const char *HTTP_403 =
+	"HTTP/1.0 403 Forbidden\r\n"
+	"Cache-Control: no-cache\r\n"
+	"Connection: close\r\n"
+	"\r\n"
+	"403 Forbidden : Request forbidden by administrative rules.\r\n";
+
+const char *HTTP_400 =
+	"HTTP/1.0 400 Bad request\r\n"
+	"Cache-Control: no-cache\r\n"
+	"Connection: close\r\n"
+	"\r\n"
+	"400 Bad request : Your browser sent an invalid request.\r\n";
+
+const char *HTTP_502 =
+	"HTTP/1.0 502 Proxy Error\r\n"
+	"Cache-Control: no-cache\r\n"
+	"Connection: close\r\n"
+	"\r\n"
+	"502 Proxy Error : No server is available to handle this request.\r\n";
+
 /*********************************************************************/
 /*  statistics  ******************************************************/
 /*********************************************************************/
@@ -1075,7 +1108,7 @@
 
     //    fprintf(stderr,"connect_server : s=%p\n",s);
 
-    if (s->flags & TF_DIRECT) { /* srv cannot be null */
+    if (s->flags & SN_DIRECT) { /* srv cannot be null */
 	s->srv_addr = s->srv->addr;
     }
     else if (s->proxy->options & PR_O_BALANCE) {
@@ -1511,6 +1544,38 @@
 
 
 /*
+ * returns a message to the client ; the connection is shut down for read,
+ * and the request is cleared so that no server connection can be initiated.
+ * The client must be in a valid state for this (HEADER, DATA ...).
+ * Nothing is performed on the server side.
+ * The reply buffer must be empty before this.
+ */
+void client_retnclose(struct session *s, int len, const char *msg) {
+    FD_CLR(s->cli_fd, StaticReadEvent);
+    FD_SET(s->cli_fd, StaticWriteEvent);
+    tv_eternity(&s->crexpire);
+    shutdown(s->cli_fd, SHUT_RD);
+    s->cli_state = CL_STSHUTR;
+    strcpy(s->rep->data, msg);
+    s->rep->l = len;
+    s->rep->r += len;
+    s->req->l = 0;
+}
+
+
+/*
+ * returns a message into the rep buffer, and flushes the req buffer.
+ * The reply buffer must be empty before this.
+ */
+void client_return(struct session *s, int len, const char *msg) {
+    strcpy(s->rep->data, msg);
+    s->rep->l = len;
+    s->rep->r += len;
+    s->req->l = 0;
+}
+
+
+/*
  * this function is called on a read event from a listen socket, corresponding
  * to an accept. It tries to accept as many connections as possible.
  * It returns 0.
@@ -1638,15 +1703,7 @@
 	fdtab[cfd].state = FD_STREADY;
 
 	if (p->mode == PR_MODE_HEALTH) {  /* health check mode, no client reading */
-	    FD_CLR(cfd, StaticReadEvent);
-	    FD_SET(cfd, StaticWriteEvent);
-	    tv_eternity(&s->crexpire);
-	    shutdown(s->cli_fd, SHUT_RD);
-	    s->cli_state = CL_STSHUTR;
-
-	    strcpy(s->rep->data, "OK\n"); /* forge an "OK" response */
-	    s->rep->l = 3;
-	    s->rep->r += 3;
+	    client_retnclose(s, 3, "OK\n"); /* forge an "OK" response */
 	}
 	else {
 	    FD_SET(cfd, StaticReadEvent);
@@ -1835,6 +1892,12 @@
 		/* we can only get here after an end of headers */
 		/* we'll have something else to do here : add new headers ... */
 
+		if (t->flags & SN_CLDENY) {
+		    /* no need to go further */
+		    client_retnclose(t, strlen(HTTP_403), HTTP_403);
+		    return 1;
+		}
+
 		for (line = 0; line < t->proxy->nb_reqadd; line++) {
 		    len = sprintf(newhdr, "%s\r\n", t->proxy->req_add[line]);
 		    buffer_replace2(req, req->h, req->h, newhdr, len);
@@ -1888,25 +1951,38 @@
 	    }
 
 	    /* try headers regexps */
-	    if (t->proxy->nb_reqexp) {
-		struct proxy *p = t->proxy;
-		int exp;
+	    if (t->proxy->req_exp != NULL && !(t->flags & SN_CLDENY)) {
+		struct hdr_exp *exp;
 		char term;
 		
 		term = *ptr;
 		*ptr = '\0';
-		for (exp=0; exp < p->nb_reqexp; exp++) {
-		    if (regexec(p->req_exp[exp].preg, req->h, MAX_MATCH, pmatch, 0) == 0) {
-			if (p->req_exp[exp].replace != NULL) {
-			    int len = exp_replace(trash, req->h, p->req_exp[exp].replace, pmatch);
-			    ptr += buffer_replace2(req, req->h, ptr, trash, len);
-			}
-			else {
-			    delete_header = 1;
+		exp = t->proxy->req_exp;
+		do {
+		    if (regexec(exp->preg, req->h, MAX_MATCH, pmatch, 0) == 0) {
+			switch (exp->action) {
+			case ACT_ALLOW:
+			    if (!(t->flags & SN_CLDENY))
+				t->flags |= SN_CLALLOW;
+			    break;
+			case ACT_REPLACE:
+			    if (!(t->flags & SN_CLDENY)) {
+				int len = exp_replace(trash, req->h, exp->replace, pmatch);
+				ptr += buffer_replace2(req, req->h, ptr, trash, len);
+			    }
+			    break;
+			case ACT_REMOVE:
+			    if (!(t->flags & SN_CLDENY))
+				delete_header = 1;
+			    break;
+			case ACT_DENY:
+			    if (!(t->flags & SN_CLALLOW))
+				t->flags |= SN_CLDENY;
+			    break;
 			}
 			break;
 		    }
-		}
+		} while ((exp = exp->next) != NULL);
 		*ptr = term; /* restore the string terminator */
 	    }
 	    
@@ -1965,7 +2041,7 @@
 			}
 
 			if (srv) { /* we found the server */
-			    t->flags |= TF_DIRECT;
+			    t->flags |= SN_DIRECT;
 			    t->srv = srv;
 			}
 
@@ -1984,7 +2060,7 @@
 	    } /* end of cookie processing */
 
 	    /* let's look if we have to delete this header */
-	    if (delete_header) {
+	    if (delete_header && !(t->flags & SN_CLDENY)) {
 		buffer_replace2(req, req->h, req->lr, "", 0);
 	    }
 	    req->h = req->lr;
@@ -1992,7 +2068,6 @@
 
 	/* end of header processing (even if incomplete) */
 
-
 	if ((req->l < req->rlim - req->data) && ! FD_ISSET(t->cli_fd, StaticReadEvent)) {
 	    /* fd in StaticReadEvent was disabled, perhaps because of a previous buffer
 	     * full. We cannot loop here since event_cli_read will disable it only if
@@ -2005,12 +2080,20 @@
 		tv_eternity(&t->crexpire);
 	}
 
-	/* read timeout, read error, or last read : give up.
-	 * since we are in header mode, if there's no space left for headers, we
+	/* Since we are in header mode, if there's no space left for headers, we
 	 * won't be able to free more later, so the session will never terminate.
 	 */
-	if (t->res_cr == RES_ERROR || t->res_cr == RES_NULL
-	    || req->l >= req->rlim - req->data || tv_cmp2_ms(&t->crexpire, &now) <= 0) {
+	if (req->l >= req->rlim - req->data) {
+	    client_retnclose(t, strlen(HTTP_400), HTTP_400);
+	    return 1;
+	}
+	else if (t->res_cr == RES_ERROR || t->res_cr == RES_NULL
+	         || tv_cmp2_ms(&t->crexpire, &now) <= 0) {
+
+	    /* read timeout, read error, or last read : give up.
+	     * since we are in header mode, if there's no space left for headers, we
+	     * won't be able to free more later, so the session will never terminate.
+	     */
 	    tv_eternity(&t->crexpire);
 	    fd_delete(t->cli_fd);
 	    t->cli_state = CL_STCLOSE;
@@ -2186,7 +2269,7 @@
 	    else { /* try again */
 		while (t->conn_retries-- > 0) {
 		    if ((t->proxy->options & PR_O_REDISP) && (t->conn_retries == 0)) {
-			t->flags &= ~TF_DIRECT; /* ignore cookie and force to use the dispatcher */
+			t->flags &= ~SN_DIRECT; /* ignore cookie and force to use the dispatcher */
 			t->srv = NULL; /* it's left to the dispatcher to choose a server */
 		    }
 
@@ -2199,6 +2282,7 @@
 		    /* if conn_retries < 0 or other error, let's abort */
 		    tv_eternity(&t->cnexpire);
 		    t->srv_state = SV_STCLOSE;
+		    client_return(t, strlen(HTTP_502), HTTP_502);
 		}
 	    }
 	    return 1;
@@ -2218,7 +2302,7 @@
 	    t->conn_retries--;
 	    if (t->conn_retries >= 0) {
 		    if ((t->proxy->options & PR_O_REDISP) && (t->conn_retries == 0)) {
-			t->flags &= ~TF_DIRECT; /* ignore cookie and force to use the dispatcher */
+			t->flags &= ~SN_DIRECT; /* ignore cookie and force to use the dispatcher */
 			t->srv = NULL; /* it's left to the dispatcher to choose a server */
 		    }
 		    if (connect_server(t) == 0)
@@ -2274,7 +2358,7 @@
 		/* we can only get here after an end of headers */
 		/* we'll have something else to do here : add new headers ... */
 
-		if ((t->srv) && !(t->flags & TF_DIRECT) && (t->proxy->options & PR_O_COOK_INS)) {
+		if ((t->srv) && !(t->flags & SN_DIRECT) && (t->proxy->options & PR_O_COOK_INS)) {
 		    /* the server is known, it's not the one the client requested, we have to
 		     * insert a set-cookie here.
 		     */
@@ -2334,25 +2418,38 @@
 	    }
 
 	    /* try headers regexps */
-	    if (t->proxy->nb_rspexp) {
-		struct proxy *p = t->proxy;
-		int exp;
+	    if (t->proxy->rsp_exp != NULL && !(t->flags & SN_SVDENY)) {
+		struct hdr_exp *exp;
 		char term;
 		
 		term = *ptr;
 		*ptr = '\0';
-		for (exp=0; exp < p->nb_rspexp; exp++) {
-		    if (regexec(p->rsp_exp[exp].preg, rep->h, MAX_MATCH, pmatch, 0) == 0) {
-			if (p->rsp_exp[exp].replace != NULL) {
-			    int len = exp_replace(trash, rep->h, p->rsp_exp[exp].replace, pmatch);
-			    ptr += buffer_replace2(rep, rep->h, ptr, trash, len);
-			}
-			else {
-			    delete_header = 1;
+		exp = t->proxy->rsp_exp;
+		do {
+		    if (regexec(exp->preg, rep->h, MAX_MATCH, pmatch, 0) == 0) {
+			switch (exp->action) {
+			case ACT_ALLOW:
+			    if (!(t->flags & SN_SVDENY))
+				t->flags |= SN_SVALLOW;
+			    break;
+			case ACT_REPLACE:
+			    if (!(t->flags & SN_SVDENY)) {
+				int len = exp_replace(trash, rep->h, exp->replace, pmatch);
+				ptr += buffer_replace2(rep, rep->h, ptr, trash, len);
+			    }
+			    break;
+			case ACT_REMOVE:
+			    if (!(t->flags & SN_SVDENY))
+				delete_header = 1;
+			    break;
+			case ACT_DENY:
+			    if (!(t->flags & SN_SVALLOW))
+				t->flags |= SN_SVDENY;
+			    break;
 			}
 			break;
 		    }
-		}
+		} while ((exp = exp->next) != NULL);
 		*ptr = term; /* restore the string terminator */
 	    }
 	    
@@ -2401,7 +2498,7 @@
 			 * We'll delete it too if the "indirect" option is set and we're in
 			 * a direct access. */
 			if (((t->srv) && (t->proxy->options & PR_O_COOK_INS)) ||
-			    ((t->flags & TF_DIRECT) && (t->proxy->options & PR_O_COOK_IND))) {
+			    ((t->flags & SN_DIRECT) && (t->proxy->options & PR_O_COOK_IND))) {
 			    /* this header must be deleted */
 			    delete_header = 1;
 			}
@@ -2425,9 +2522,9 @@
 	    } /* end of cookie processing */
 
 	    /* let's look if we have to delete this header */
-	    if (delete_header) {
+	    if (delete_header && !(t->flags & SN_SVDENY))
 		buffer_replace2(rep, rep->h, rep->lr, "", 0);
-	    }
+
 	    rep->h = rep->lr;
 	} /* while (rep->lr < rep->r) */
 
@@ -3142,6 +3239,20 @@
     }
 }
 
+void chain_regex(struct hdr_exp **head, regex_t *preg, int action, char *replace) {
+    struct hdr_exp *exp;
+
+    while (*head != NULL)
+	head = &(*head)->next;
+
+    exp = calloc(1, sizeof(struct hdr_exp));
+
+    exp->preg = preg;
+    exp->replace = replace;
+    exp->action = action;
+    *head = exp;
+}
+
 /*
  * This function reads and parses the configuration file given in the argument.
  * returns 0 if OK, -1 if error.
@@ -3525,11 +3636,6 @@
 	}
 	else if (!strcmp(args[0], "cliexp") || !strcmp(args[0], "reqrep")) {  /* replace request header from a regex */
 	    regex_t *preg;
-	    if (curproxy->nb_reqexp >= MAX_REGEXP) {
-		Alert("parsing [%s:%d] : too many request expressions. Continuing.\n",
-		      file, linenum);
-		continue;
-	    }
 
 	    if (*(args[1]) == 0 || *(args[2]) == 0) {
 		Alert("parsing [%s:%d] : <reqrep> expects <search> and <replace> as arguments.\n",
@@ -3542,20 +3648,31 @@
 		Alert("parsing [%s:%d] : bad regular expression <%s>.\n", file, linenum, args[1]);
 		return -1;
 	    }
-	    curproxy->req_exp[curproxy->nb_reqexp].preg = preg;
-	    curproxy->req_exp[curproxy->nb_reqexp].replace = strdup(args[2]);
-	    curproxy->nb_reqexp++;
+
+	    chain_regex(&curproxy->req_exp, preg, ACT_REPLACE, strdup(args[2]));
 	}
 	else if (!strcmp(args[0], "reqdel")) {  /* delete request header from a regex */
 	    regex_t *preg;
-	    if (curproxy->nb_reqexp >= MAX_REGEXP) {
-		Alert("parsing [%s:%d] : too many request expressions. Continuing.\n",
+
+	    if (*(args[1]) == 0) {
+		Alert("parsing [%s:%d] : <reqdel> expects <regex> as an argument.\n",
 		      file, linenum);
-		continue;
+		return -1;
+	    }
+
+	    preg = calloc(1, sizeof(regex_t));
+	    if (regcomp(preg, args[1], REG_EXTENDED) != 0) {
+		Alert("parsing [%s:%d] : bad regular expression <%s>.\n", file, linenum, args[1]);
+		return -1;
 	    }
 
+	    chain_regex(&curproxy->req_exp, preg, ACT_REMOVE, NULL);
+	}
+	else if (!strcmp(args[0], "reqdeny")) {  /* deny a request if a header matches this regex */
+	    regex_t *preg;
+
 	    if (*(args[1]) == 0) {
-		Alert("parsing [%s:%d] : <reqdel> expects <search> as an argument.\n",
+		Alert("parsing [%s:%d] : <reqdeny> expects <regex> as an argument.\n",
 		      file, linenum);
 		return -1;
 	    }
@@ -3565,13 +3682,29 @@
 		Alert("parsing [%s:%d] : bad regular expression <%s>.\n", file, linenum, args[1]);
 		return -1;
 	    }
-	    curproxy->req_exp[curproxy->nb_reqexp].preg = preg;
-	    curproxy->req_exp[curproxy->nb_reqexp].replace = NULL; /* means it must be deleted */
-	    curproxy->nb_reqexp++;
+
+	    chain_regex(&curproxy->req_exp, preg, ACT_DENY, NULL);
+	}
+	else if (!strcmp(args[0], "reqallow")) {  /* allow a request if a header matches this regex */
+	    regex_t *preg;
+
+	    if (*(args[1]) == 0) {
+		Alert("parsing [%s:%d] : <reqallow> expects <regex> as an argument.\n",
+		      file, linenum);
+		return -1;
+	    }
+
+	    preg = calloc(1, sizeof(regex_t));
+	    if (regcomp(preg, args[1], REG_EXTENDED) != 0) {
+		Alert("parsing [%s:%d] : bad regular expression <%s>.\n", file, linenum, args[1]);
+		return -1;
+	    }
+
+	    chain_regex(&curproxy->req_exp, preg, ACT_ALLOW, NULL);
 	}
 	else if (!strcmp(args[0], "reqadd")) {  /* add request header */
-	    if (curproxy->nb_reqadd >= MAX_REGEXP) {
-		Alert("parsing [%s:%d] : too many client expressions. Continuing.\n",
+	    if (curproxy->nb_reqadd >= MAX_NEWHDR) {
+		Alert("parsing [%s:%d] : too many `reqadd'. Continuing.\n",
 		      file, linenum);
 		continue;
 	    }
@@ -3586,11 +3719,6 @@
 	}
 	else if (!strcmp(args[0], "srvexp") || !strcmp(args[0], "rsprep")) {  /* replace response header from a regex */
 	    regex_t *preg;
-	    if (curproxy->nb_rspexp >= MAX_REGEXP) {
-		Alert("parsing [%s:%d] : too many server expressions. Continuing.\n",
-		      file, linenum);
-		continue;
-	    }
 
 	    if (*(args[1]) == 0 || *(args[2]) == 0) {
 		Alert("parsing [%s:%d] : <rsprep> expects <search> and <replace> as arguments.\n",
@@ -3603,18 +3731,11 @@
 		Alert("parsing [%s:%d] : bad regular expression <%s>.\n", file, linenum, args[1]);
 		return -1;
 	    }
-	    //	    fprintf(stderr,"before=<%s> after=<%s>\n", args[1], args[2]);
-	    curproxy->rsp_exp[curproxy->nb_rspexp].preg = preg;
-	    curproxy->rsp_exp[curproxy->nb_rspexp].replace = strdup(args[2]);
-	    curproxy->nb_rspexp++;
+
+	    chain_regex(&curproxy->rsp_exp, preg, ACT_REPLACE, strdup(args[2]));
 	}
 	else if (!strcmp(args[0], "rspdel")) {  /* delete response header from a regex */
 	    regex_t *preg;
-	    if (curproxy->nb_rspexp >= MAX_REGEXP) {
-		Alert("parsing [%s:%d] : too many server expressions. Continuing.\n",
-		      file, linenum);
-		continue;
-	    }
 
 	    if (*(args[1]) == 0) {
 		Alert("parsing [%s:%d] : <rspdel> expects <search> as an argument.\n",
@@ -3627,14 +3748,12 @@
 		Alert("parsing [%s:%d] : bad regular expression <%s>.\n", file, linenum, args[1]);
 		return -1;
 	    }
-	    //	    fprintf(stderr,"before=<%s> after=<%s>\n", args[1], args[2]);
-	    curproxy->rsp_exp[curproxy->nb_rspexp].preg = preg;
-	    curproxy->rsp_exp[curproxy->nb_rspexp].replace = NULL; /* means it must be deleted */
-	    curproxy->nb_rspexp++;
+
+	    chain_regex(&curproxy->rsp_exp, preg, ACT_REMOVE, strdup(args[2]));
 	}
 	else if (!strcmp(args[0], "rspadd")) {  /* add response header */
-	    if (curproxy->nb_rspadd >= MAX_REGEXP) {
-		Alert("parsing [%s:%d] : too many server expressions. Continuing.\n",
+	    if (curproxy->nb_rspadd >= MAX_NEWHDR) {
+		Alert("parsing [%s:%d] : too many `rspadd'. Continuing.\n",
 		      file, linenum);
 		continue;
 	    }
@@ -3701,11 +3820,11 @@
 		Warning("parsing %s : servers will be ignored for listener %s.\n",
 			file, curproxy->id);
 	    }
-	    if (curproxy->nb_rspexp) {
+	    if (curproxy->rsp_exp != NULL) {
 		Warning("parsing %s : server regular expressions will be ignored for listener %s.\n",
 			file, curproxy->id);
 	    }
-	    if (curproxy->nb_reqexp) {
+	    if (curproxy->req_exp != NULL) {
 		Warning("parsing %s : client regular expressions will be ignored for listener %s.\n",
 			file, curproxy->id);
 	    }