* released 1.1.0
* added OpenBSD, Linux-2.2 and Linux-2.4 targets to the Makefile
* added a Formilux init script
* 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 ;
* allow multiple accept() for one select() wake up ;
* implemented internal load balancing with basic health-check ;
* cookie insertion and header add/replace/delete, with better strings
  support.
* reworked buffer handling to fix a few rewrite bugs, and
  improve overall performance.
* implement the "purge" option to delete server cookies in direct mode.
* fixed some error cases where the maxfd was not decreased.
* now supports transparent proxying, at least on linux 2.4.
* soft stop works again (fixed select timeout computation).
* it seems that TCP proxies sometimes cannot timeout.
* added a "quiet" mode.
* enforce file descriptor limitation on socket() and accept().
diff --git a/Makefile b/Makefile
index e0d65d1..ba59843 100644
--- a/Makefile
+++ b/Makefile
@@ -1,26 +1,39 @@
 CC = gcc
 LD = gcc
 
-# This is for Linux 2.4
-COPTS.linux = -O2
-LIBS.linux =
+# This is for Linux 2.4 with netfilter
+COPTS.linux24 = -O2 -DNETFILTER
+LIBS.linux24 =
 
-# This is for solaris 8
+# This is for Linux 2.2
+COPTS.linux22 = -O2 -DUSE_GETSOCKNAME
+LIBS.linux22 =
+
+# This is for Solaris 8
 COPTS.solaris = -O2 -fomit-frame-pointer -DSOLARIS -DHAVE_STRLCPY
 LIBS.solaris = -lnsl -lsocket
 
+# This is for OpenBSD 3.0
+COPTS.openbsd = -O2 -DHAVE_STRLCPY
+LIBS.openbsd =
+
 # Select target OS. TARGET must match a system for which COPTS and LIBS are
 # correctly defined above.
-TARGET = linux
+TARGET = linux24
+#TARGET = linux22
 #TARGET = solaris
+#TARGET = openbsd
 
-DEBUG =
-#DEBUG = -g
+#DEBUG =
+DEBUG = -g
 
 COPTS=$(COPTS.$(TARGET))
 LIBS=$(LIBS.$(TARGET))
 
-CFLAGS = -Wall $(COPTS) -DSTATTIME=0
+# - use -DSTATTIME=0 to disable statistics, else specify an interval in
+#   milliseconds.
+# - use -DTRANSPARENT to compile with transparent proxy support.
+CFLAGS = -Wall $(COPTS) $(DEBUG) -DSTATTIME=0 -DTRANSPARENT
 LDFLAGS = -g
 
 all: haproxy
diff --git a/doc/haproxy.txt b/doc/haproxy.txt
index 36bd2ea..4f5ec14 100644
--- a/doc/haproxy.txt
+++ b/doc/haproxy.txt
@@ -1,9 +1,9 @@
 
          		     H A - P r o x y
          		     ---------------
-         		      version 1.0.0
+         		      version 1.1.0
 			      willy tarreau
-			       2001/12/16
+			       2002/03/10
 
 ==============
 |Introduction|
@@ -12,8 +12,11 @@
 HA-Proxy est un relais TCP/HTTP offrant des facilités d'intégration en
 environnement hautement disponible. En effet, il est capable de :
   - assurer un aiguillage statique défini par des cookies ;
+  - assurer une répartition de charge avec création de cookies pour
+    assurer la persistence de session ;
   - fournir une visibilité externe de son état de santé ;
   - s'arrêter en douceur sans perte brutale de service.
+  - modifier/ajouter/supprimer des entêtes dans la requête et la réponse.
 
 Il requiert peu de ressources, et son architecture événementielle
 mono-processus lui permet facilement de gérer plusieurs milliers de
@@ -51,7 +54,7 @@
 
 L'analyseur du fichier de configuration ignore des lignes vides, les
 espaces, les tabulations, et tout ce qui est compris entre le symbole
-'#' et la fin de la ligne.
+'#' (s'il n'est pas précédé d'un '\'), et la fin de la ligne.
 
 
 Serveur
@@ -102,7 +105,7 @@
 Mode TCP
 --------
 Dans ce mode, le service relaye, dès leur établissement, les
-connexions TCP vers un unique serveur distant. Aucun traitement n'est
+connexions TCP vers un ou plusieurs serveurs. Aucun traitement n'est
 effectué sur le flux. Il s'agit simplement d'une association
 <adresse_source:port_source> <adresse_destination:port_destination>.
 Pour l'utiliser, préciser le mode TCP sous la déclaration du relais :
@@ -236,6 +239,30 @@
 	mode http
 	cookie SERVERID
 
+On peut modifier l'utilisation du cookie pour la rendre plus
+intelligente vis-à-vis des applications relayées. Il est possible,notamment
+de supprimer ou réécrire un cookie retourné par un serveur accédé en direct,
+et d'insérer un cookie dans une réponse HTTP orientée vers un serveur
+sélectionné en répartition de charge.
+
+Pour ne conserver le cookie qu'en accès indirect, donc à travers le
+dispatcheur, et le supprimer lors des accès directs :
+
+	cookie SERVERID indirect
+
+Pour réécrire le nom du serveur dans un cookie lors d'un accès direct :
+
+	cookie SERVERID rewrite
+
+Pour créer un cookie comportant le nom du serveur lors d'un accès en
+répartition de charge interne. Dans ce cas, il est indispensable que tous les
+serveurs aient un cookie renseigné.
+
+	cookie SERVERID insert
+
+Remarque: Il est possible de combiner 'insert' avec 'indirect' ou 'rewrite'
+pour s'adapter à des applications générant déjà le cookie, avec un contenu
+invalide. Il suffit pour cela de les spécifier sur la même ligne.
 
 Assignation d'un serveur à une valeur de cookie
 ===============================================
@@ -243,10 +270,12 @@
 En mode HTTP, il est possible d'associer des serveurs à des valeurs de
 cookie par le paramètre "server". La syntaxe est :
 
-    server <valeur> <adresse_ip>:<port>
+    server <identifiant> <adresse_ip>:<port> cookie <valeur>
 
-<valeur> est la valeur trouvée dans le cookie,
+<identifiant> est un nom quelconque de serveur utilisé pour
+l'identifier dans la configuration (erreurs...).
 <adresse_ip>:<port> le couple adresse-port sur lequel le serveur écoute.
+<valeur> est la valeur trouvée dans le cookie,
 
 Exemple : le cookie SERVERID peut contenir server01 ou server02
 -------
@@ -254,18 +283,64 @@
 	mode http
 	cookie SERVERID
 	dispatch 192.168.1.100:80
-	server server01 192.168.1.1:80
-	server server02 192.168.1.2:80
+	server web1 192.168.1.1:80 cookie server01
+	server web2 192.168.1.2:80 cookie server02
+
+Attention : la syntaxe a changé depuis la version 1.0.
+---------
+
+Répartiteur de charge interne
+=============================
+
+Le relais peut effectuer lui-même la répartition de charge entre les
+différents serveurs décrits pour un service donné, en mode TCP comme
+en mode HTTP. Pour cela, on précise le mot clé 'balance' dans la
+définition du service, éventuellement suivi du nom d'un algorithme de
+répartition. En version 1.1.0, seul 'roundrobin' est géré, et c'est
+aussi la valeur implicite par défaut. Il est évident qu'en cas
+d'utilisation du répartiteur interne, il ne faudra pas spécifier
+d'adresse de dispatch, et qu'il faudra au moins un serveur.
+
+Exemple : même que précédemment en répartition interne
+-------
+
+    listen http_proxy 0.0.0.0:80
+	mode http
+	cookie SERVERID
+	balance roundrobin
+	server web1 192.168.1.1:80 cookie server01
+	server web2 192.168.1.2:80 cookie server02
+
 
+Surveillance des serveurs
+=========================
 
-Reconnexion vers le répartiteur
-===============================
+A cette date, l'état des serveurs n'est testé que par établissement
+de connexion TCP toutes les 2 secondes, avec 3 essais pour déclarer
+un serveur en panne, 2 pour le déclarer utilisable. Un serveur hors
+d'usage ne sera pas utilisé dans le processus de répartition de charge
+interne. Pour activer la surveillance, ajouter le mot clé 'check' à la
+fin de la déclaration du serveur.
+
+Exemple : même que précédemment avec surveillance
+-------
+
+    listen http_proxy 0.0.0.0:80
+	mode http
+	cookie SERVERID
+	balance roundrobin
+	server web1 192.168.1.1:80 cookie server01 check
+	server web2 192.168.1.2:80 cookie server02 check
+
+
+Reconnexion vers un répartiteur en cas d'échec direct
+=====================================================
 
 En mode HTTP, si un serveur défini par un cookie ne répond plus, les
 clients seront définitivement aiguillés dessus à cause de leur cookie,
 et de ce fait, définitivement privés de service. La spécification du
-paramètre "redisp" autorise dans ce cas à renvoyer les connexions
-échouées vers l'adresse de répartition (dispatch) afin d'assigner un
+paramètre "redispatch" autorise dans ce cas à renvoyer les connexions
+échouées vers le répartiteur (externe ou interne) afin d'assigner un
 nouveau serveur à ces clients.
 
 Exemple :
@@ -274,9 +349,31 @@
 	mode http
 	cookie SERVERID
 	dispatch 192.168.1.100:80
+	server web1 192.168.1.1:80 cookie server01
+	server web2 192.168.1.2:80 cookie server02
+	redispatch # renvoyer vers dispatch si serveur HS.
+
+Fonctionnement en mode transparent
+==================================
+
+En mode HTTP, le mot clé "transparent" permet d'intercepter des
+sessions routées à travers la machine hébergeant le proxy. Dans
+ce mode, on ne précise pas l'adresse de répartition "dispatch",
+car celle-ci est tirée de l'adresse destination de la session
+détournée. Le système doit permettre de rediriger les paquets
+vers un processus local.
+
+Exemple :
+-------
+    listen http_proxy 0.0.0.0:65000
+	mode http
+	transparent
+	cookie SERVERID
 	server server01 192.168.1.1:80
 	server server02 192.168.1.2:80
-	redisp # renvoyer vers dispatch si serveur HS.
+
+    # iptables -t nat -A PREROUTING -i eth0 -p tcp -d 192.168.1.100 \
+      --dport 80 -j REDIRECT --to-ports 65000
 
 Journalisation des connexions
 =============================
@@ -307,49 +404,92 @@
     local0, local1, local2, local3, local4, local5, local6, local7
 
 
-Remplacement d'entêtes par expressions régulières
-=================================================
+Modification des entêtes HTTP
+=============================
 
 En mode HTTP uniquement, il est possible de remplacer certains entêtes
-client et/ou serveur à partir d'expressions régulières. Deux
-limitations cependant :
-  - il n'est pas encore possible de supprimer un entête ni d'en
-    ajouter un ; On peut en général s'en sortir avec des
-    modifications.
-  - les entêtes fournis au milieu de connexions persistentes
-    (keep-alive) ne sont pas vus.
+dans la requête et/ou la réponse à partir d'expressions régulières. Une
+limitation cependant : les entêtes fournis au milieu de connexions
+persistentes (keep-alive) ne sont pas vus. Les données ne sont pas
+affectées, ceci ne s'applique qu'aux entêtes.
 
 La syntaxe est :
-   cliexp <search> <replace>   pour les entêtes client
-   srvexp <search> <replace>   pour les entêtes serveur
+   reqadd <string>             pour ajouter un entête dans la requête
+   reqrep <search> <replace>   pour modifier la requête
+   reqrep <search>             pour supprimer un entête dans la requête
+
+   rspadd <string>             pour ajouter un entête dans la réponse
+   rsprep <search> <replace>   pour modifier la réponse
+   rsprep <search>             pour supprimer un entête dans la réponse
+
 
 <search> est une expression régulière compatible GNU regexp supportant
 le groupage par parenthèses (sans les '\'). Les espaces et autres
 séparateurs doivent êtres précédés d'un '\' pour ne pas être confondus
-avec la fin de la chaîne.
+avec la fin de la chaîne. De plus, certains caractères spéciaux peuvent
+être précédés d'un backslach ('\') :
+
+  \t   pour une tabulation
+  \r   pour un retour charriot
+  \n   pour un saut de ligne
+  \    pour différencier un espace d'un séparateur
+  \#   pour différencier un dièse d'un commentaire
+  \\   pour un backslash
+  \xXX pour un caractère spécifique XX (comme en C)
 
-<replace> contient la chaîne remplaçant la portion vérifiée par
-l'expression. Elle peut inclure des espaces et tabulations précédés
-par un '\', faire référence à un groupe délimité par des parenthèses
-dans l'expression régulière, par sa position numérale. Les positions
-vont de 1 à 9, et sont codées par un '\' suivi du chiffre désiré. Il
-est également possible d'insérer un caractère non imprimable (utile
-pour le saut de ligne) inscrivant '\x' suivi du code hexadécimal de ce
-caractère (comme en C).
 
-Remarque : la première ligne de la requête et celle de la réponse sont
-traitées comme des entêtes, ce qui permet de réécrire des URL et des
-codes d'erreur.
+<replace> contient la chaîne remplaçant la portion vérifiée par l'expression.
+Elle peut inclure les caractères spéciaux ci-dessus, faire référence à un
+groupe délimité par des parenthèses dans l'expression régulière, par sa
+position numérale. Les positions vont de 1 à 9, et sont codées par un '\'
+suivi du chiffre désiré. Il est également possible d'insérer un caractère non
+imprimable (utile pour le saut de ligne) inscrivant '\x' suivi du code
+hexadécimal de ce caractère (comme en C).
+
+<string> représente une chaîne qui sera ajoutée systématiquement après la
+dernière ligne d'entête.
+
+Remarques :
+---------
+  - la première ligne de la requête et celle de la réponse sont traitées comme
+    des entêtes, ce qui permet de réécrire des URL et des codes d'erreur.
+  - 'reqrep' est l'équivalent de 'cliexp' en version 1.0, et 'rsprep' celui de
+    'srvexp'. Ces noms sont toujours supportés mais déconseillés.
+  - pour des raisons de performances, le nombre total de caractères ajoutés sur
+    une requête ou une réponse est limité à 256. Cette valeur est modifiable dans
+    le code. Pour un usage temporaire, on peut gagner de la place en supprimant
+    quelques entêtes inutiles avant les ajouts.
 
 Exemples :
-----------
-	cliexp	^(GET.*)(.free.fr)(.*) \1.online.fr\3
-	cliexp	^(POST.*)(.free.fr)(.*) \1.online.fr\3
-	cliexp	^Proxy-Connection:.*	Proxy-Connection:\ close
-	srvexp	^Proxy-Connection:.*	Proxy-Connection:\ close
-	srvexp	^(Location:\ )([^:]*://[^/]*)(.*) \1\3
+--------
+	reqrep	^(GET.*)(.free.fr)(.*) \1.online.fr\3
+	reqrep	^(POST.*)(.free.fr)(.*) \1.online.fr\3
+	reqrep	^Proxy-Connection:.*	Proxy-Connection:\ close
+	rsprep	^Server:.* Server:\ Tux-2.0
+	rsprep	^(Location:\ )([^:]*://[^/]*)(.*) \1\3
+	rspdel  ^Connection:.*
+	rspadd	Connection:\ close
 
 
+Répartition avec persistence
+============================
+
+La combinaison de l'insertion de cookie avec la répartition de charge interne
+permet d'assurer une persistence dans les sessions HTTP d'une manière
+pratiquement transparente pour les applications. Le principe est simple :
+  - assigner un cookie à chaque serveur
+  - effectuer une répartition interne
+  - insérer un cookie dans les réponses issues d'une répartition
+
+Exemple :
+-------
+    listen application 0.0.0.0:80
+	mode http
+	cookie SERVERID insert indirect
+	balance roundrobin
+	server 192.168.1.1:80 cookie server01 check
+	server 192.168.1.2:80 cookie server02 check
+
 =====================
 |Paramétrage système|
 =====================
@@ -360,13 +500,15 @@
 echo 131072 > /proc/sys/fs/file-max
 echo 65536 > /proc/sys/net/ipv4/ip_conntrack_max
 echo 1024 60999 > /proc/sys/net/ipv4/ip_local_port_range
-echo 16384 > /proc/sys/net/ipv4/ip_queue_maxlen
+echo 32768 > /proc/sys/net/ipv4/ip_queue_maxlen
 echo 60 > /proc/sys/net/ipv4/tcp_fin_timeout
-echo 4096 > /proc/sys/net/ipv4/tcp_max_orphans
+echo 262144 > /proc/sys/net/ipv4/tcp_max_orphans
 echo 16384 > /proc/sys/net/ipv4/tcp_max_syn_backlog
 echo 262144 > /proc/sys/net/ipv4/tcp_max_tw_buckets
 echo 1 > /proc/sys/net/ipv4/tcp_tw_recycle
 echo 0 > /proc/sys/net/ipv4/tcp_timestamps
-ulimit -n 65536
+echo 0 > /proc/sys/net/ipv4/tcp_sack
+echo 0 > /proc/sys/net/ipv4/tcp_ecn
+ulimit -n 131072
 
 -- fin --
diff --git a/examples/cfg b/examples/cfg
index 053648f..6aed525 100644
--- a/examples/cfg
+++ b/examples/cfg
@@ -1,20 +1,52 @@
-listen proxy1 0.0.0.0:3128
+listen proxy1 0.0.0.0:8000
 	mode	http
-        cookie SERVERID
-	dispatch 192.168.12.1:80
- 	server srv1 192.168.12.2:8080
- 	server srv2 192.168.12.3:8080
+	#mode	tcp
+        cookie SERVERID insert indirect
+	balance roundrobin
+	#dispatch 127.0.0.1:3130
+	#dispatch 127.0.0.1:31300
+	#dispatch 127.0.0.1:80
+	#dispatch 127.0.0.1:22
+	server tuxlocal 127.0.0.1:80 cookie cookie1 check
+	server tuxceleron 10.101.0.1:80 cookie cookie2 check
+	#server telnet 127.0.0.1:23
+	#server ssh 127.0.0.1:22
+	server local 127.0.0.1:3130 cookie cookie3 check
+	#server local 127.0.0.1:3130
+	#server celeron 10.101.0.1:80 cookie srv1
+	#server celeron 10.101.0.1:31300
+	#server local 10.101.23.9:31300
 	contimeout	3000
 	clitimeout	150000
 	srvtimeout	150000
 	maxconn 60000
+	redispatch
+	retries	3
+	grace 3000
+	#rsprep	^Server.* Server:\ IIS
+	#rspdel	^Server.*
+	#rspadd Set-Cookie:\ mycookie=0;\ path=/
+	#rsprep ^(Date:\ )([^,]*)(,\ )(.*) LaDate\ est:\ \4\ (\2)
+	
+listen proxy1 0.0.0.0:3128
+	mode	http
+        cookie SERVERID indirect
+	dispatch 127.0.0.1:8080
+	server srv1 127.0.0.1:8080
+ 	#server srv2 192.168.12.3:8080
+	contimeout	3000
+	clitimeout	450000
+	srvtimeout	450000
+	maxconn 60000
-	redisp
+	redispatch
 	retries	3
 	grace 3000
+	
 
 listen proxy2 0.0.0.0:3129
 	mode	http
-	dispatch 127.0.0.1:80
+	transparent
+#	dispatch 127.0.0.1:80
 	contimeout	3000
 	clitimeout	150000
 	srvtimeout	150000
@@ -35,5 +67,13 @@
 	mode	health
 	clitimeout	1500
 	srvtimeout	1500
+	maxconn 6000
+	grace 0
+
+
+listen health 0.0.0.0:31300
+	mode	health
+	clitimeout	1500
+	srvtimeout	1500
-	maxconn 4
+	maxconn 6000
 	grace 0
diff --git a/haproxy.c b/haproxy.c
index dbbef43..f1e183b 100644
--- a/haproxy.c
+++ b/haproxy.c
@@ -1,6 +1,6 @@
 /*
- * HA-Proxy : High Availability-enabled HTTP/TCP proxy - Willy Tarreau
- * willy AT meta-x DOT org.
+ * HA-Proxy : High Availability-enabled HTTP/TCP proxy
+ * 2000-2002 - Willy Tarreau - willy AT meta-x DOT org.
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License
@@ -9,7 +9,29 @@
  *
  * ChangeLog :
  *
- * 2001/12/30 : release of version 1.0.2 : no fixed a bug in header processing
+ * 2002/03/10
+ *   - released 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 ;
+ *   - allow multiple accept() for one select() wake up ;
+ *   - implemented internal load balancing with basic health-check ;
+ *   - cookie insertion and header add/replace/delete, with better strings
+ *     support.
+ * 2002/03/08
+ *   - reworked buffer handling to fix a few rewrite bugs, and
+ *     improve overall performance.
+ *   - implement the "purge" option to delete server cookies in direct mode.
+ * 2002/03/07
+ *   - fixed some error cases where the maxfd was not decreased.
+ * 2002/02/26
+ *   - now supports transparent proxying, at least on linux 2.4.
+ * 2002/02/12
+ *   - soft stop works again (fixed select timeout computation).
+ *   - it seems that TCP proxies sometimes cannot timeout.
+ *   - added a "quiet" mode.
+ *   - enforce file descriptor limitation on socket() and accept().
+ * 2001/12/30 : release of version 1.0.2 : fixed a bug in header processing
  * 2001/12/19 : release of version 1.0.1 : no MSG_NOSIGNAL on solaris
  * 2001/12/16 : release of version 1.0.0.
  * 2001/12/16 : added syslog capability for each accepted connection.
@@ -25,7 +47,10 @@
  * 2000/11/28 : major rewrite
  * 2000/11/26 : first write
  *
- * TODO: handle properly intermediate incomplete server headers.
+ * TODO:
+ *   - handle properly intermediate incomplete server headers. Done ?
+ *   - log proxies start/stop
+ *   - handle hot-reconfiguration
  *
  */
 
@@ -49,9 +74,12 @@
 #include <time.h>
 #include <regex.h>
 #include <syslog.h>
+#if defined(TRANSPARENT) && defined(NETFILTER)
+#include <linux/netfilter_ipv4.h>
+#endif
 
-#define HAPROXY_VERSION "1.0.2"
-#define HAPROXY_DATE	"2001/12/30"
+#define HAPROXY_VERSION "1.1.0"
+#define HAPROXY_DATE	"2002/03/10"
 
 /* this is for libc5 for example */
 #ifndef TCP_NODELAY
@@ -71,16 +99,26 @@
 // reserved buffer space for header rewriting
 #define	MAXREWRITE	256
 
+// max # args on a configuration line
+#define MAX_LINE_ARGS	10
+
 // max # of regexps per proxy
 #define	MAX_REGEXP	10
 
 // max # of matches per regexp
 #define	MAX_MATCH	10
 
+/* FIXME: serverid_len and cookiename_len are no longer checked in configuration file */
 #define COOKIENAME_LEN	16
 #define SERVERID_LEN	16
 #define CONN_RETRIES	3
 
+/* FIXME: this should be user-configurable */
+#define	CHK_CONNTIME	2000
+#define	CHK_INTERVAL	2000
+#define FALLTIME	3
+#define RISETIME	2
+
 /* how many bits are needed to code the size of an int (eg: 32bits -> 5) */
 #define	INTBITS		5
 
@@ -89,6 +127,13 @@
 #define STATTIME	2000
 #endif
 
+/* this reduces the number of calls to select() by choosing appropriate
+ * sheduler precision in milliseconds. It should be near the minimum
+ * time that is needed by select() to collect all events. All timeouts
+ * are rounded up by adding this value prior to pass it to select().
+ */
+#define SCHEDULER_RESOLUTION	9
+
 #define MINTIME(old, new)	(((new)<0)?(old):(((old)<0||(new)<(old))?(new):(old)))
 #define SETNOW(a)		(*a=now)
 
@@ -157,39 +202,56 @@
 #define pool_free(type, ptr) (free(ptr));
 #endif	/* MEM_OPTIM */
 
-#define sizeof_session	sizeof(struct task)
+#define sizeof_task	sizeof(struct task)
+#define sizeof_session	sizeof(struct session)
 #define sizeof_buffer	sizeof(struct buffer)
 #define sizeof_fdtab	sizeof(struct fdtab)
 #define sizeof_str256	256
 
 
-/*
- * different possible states for the sockets
- */
+/* different possible states for the sockets */
 #define FD_STCLOSE	0
 #define FD_STLISTEN	1
 #define FD_STCONN	2
 #define FD_STREADY	3
 #define FD_STERROR	4
 
+/* values for task->state */
 #define TASK_IDLE	0
 #define TASK_RUNNING	1
 
+/* values for proxy->state */
 #define PR_STNEW	0
 #define PR_STIDLE	1
 #define PR_STRUN	2
 #define PR_STDISABLED	3
 
+/* values for proxy->mode */
 #define PR_MODE_TCP	0
 #define PR_MODE_HTTP	1
 #define PR_MODE_HEALTH	2
 
+/* bits for proxy->options */
+#define PR_O_REDISP	1	/* allow reconnection to dispatch in case of errors */
+#define PR_O_TRANSP	2	/* transparent mode : use original DEST as dispatch */
+#define PR_O_COOK_RW	4	/* rewrite all direct cookies with the right serverid */
+#define PR_O_COOK_IND	8	/* keep only indirect cookies */
+#define PR_O_COOK_INS	16	/* insert cookies when not accessing a server directly */
+#define PR_O_COOK_ANY	(PR_O_COOK_RW | PR_O_COOK_IND | PR_O_COOK_INS)
+#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 */
+
+/* different possible states for the client side */
 #define CL_STHEADERS	0
 #define CL_STDATA	1
 #define CL_STSHUTR	2
 #define CL_STSHUTW	3
 #define CL_STCLOSE	4
 
+/* different possible states for the server side */
 #define SV_STIDLE	0
 #define SV_STCONN	1
 #define SV_STHEADERS	2
@@ -204,11 +266,15 @@
 #define	RES_NULL	2	/* result is 0 (read == 0), or connect without need for writing */
 #define RES_ERROR	3	/* result -1 or error on the socket (eg: connect()) */
 
-/* modes of operation */
+/* modes of operation (global variable "mode") */
 #define	MODE_DEBUG	1
 #define	MODE_STATS	2
 #define	MODE_LOG	4
 #define	MODE_DAEMON	8
+#define	MODE_QUIET	16
+
+/* server flags */
+#define SRV_RUNNING	1
 
 /*********************************************************************/
 
@@ -229,15 +295,30 @@
 
 struct server {
     struct server *next;
-    char *id;				/* the id found in the cookie */
+    int state;				/* server state (SRV_*) */
+    int  cklen;				/* the len of the cookie, to speed up checks */
+    char *cookie;			/* the id set in the cookie */
+    char *id;				/* just for identification */
     struct sockaddr_in addr;		/* the address to connect to */
+    int health;				/* 0->rise-1 = bad; rise->rise+fall-1 = good */
+    int result;				/* 0 = connect OK, -1 = connect KO */
+    int curfd;				/* file desc used for current test, or -1 if not in test */
 };
 
+/* The base for all tasks */
 struct task {
     struct task *next, *prev;		/* chaining ... */
     struct task *rqnext;		/* chaining in run queue ... */
+    struct task *wq;			/* the wait queue this task is in */
     int state;				/* task state : IDLE or RUNNING */
     struct timeval expire;		/* next expiration time for this task, use only for fast sorting */
+    int (*process)(struct task *t);	/* the function which processes the task */
+    void *context;			/* the task's context */
+};
+
+/* WARNING: if new fields are added, they must be initialized in event_accept() */
+struct session {
+    struct task *task;			/* the task associated with this session */
     /* application specific below */
     struct timeval crexpire;		/* expiration date for a client read  */
     struct timeval cwexpire;		/* expiration date for a client write */
@@ -251,12 +332,12 @@
     int cli_state;			/* state of the client side */
     int srv_state;			/* state of the server side */
     int conn_retries;			/* number of connect retries left */
-    int conn_redisp;			/* allow reconnection to dispatch in case of errors */
+    int flags;				/* some flags describing the session */
     struct buffer *req;			/* request buffer */
     struct buffer *rep;			/* response buffer */
     struct sockaddr_in cli_addr;	/* the client address */
     struct sockaddr_in srv_addr;	/* the address to connect to */
-    char cookie_val[SERVERID_LEN+1];	/* the cookie value, if present */
+    struct server *srv;			/* the server being used */
 };
 
 struct proxy {
@@ -264,7 +345,8 @@
     int state;				/* proxy state */
     struct sockaddr_in listen_addr;	/* the address we listen to */
     struct sockaddr_in dispatch_addr;	/* the default address to connect to */
-    struct server *srv;			/* known servers */
+    struct server *srv, *cursrv;	/* known servers, current server */
+    int nbservers;			/* # of servers */
     char *cookie_name;			/* name of the cookie to look for */
     int clitimeout;			/* client I/O timeout (in milliseconds) */
     int srvtimeout;			/* server I/O timeout (in milliseconds) */
@@ -272,18 +354,17 @@
     char *id;				/* proxy id */
     int nbconn;				/* # of active sessions */
     int maxconn;			/* max # of active sessions */
-    int conn_retries;			/* number of connect retries left */
-    int conn_redisp;			/* allow to reconnect to dispatch in case of errors */
-    int mode;				/* mode = PR_MODE_TCP or PR_MODE_HTTP */
-    struct task task;			/* active sessions (bi-dir chaining) */
-    struct task *rq;			/* sessions in the run queue (unidir chaining) */
+    int conn_retries;			/* maximum number of connect retries */
+    int options;			/* PR_O_REDISP, PR_O_TRANSP */
+    int mode;				/* mode = PR_MODE_TCP, PR_MODE_HTTP or PR_MODE_HEALTH */
     struct proxy *next;
     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_cliexp, nb_srvexp;
-    struct hdr_exp cli_exp[MAX_REGEXP];	/* regular expressions for client headers */
-    struct hdr_exp srv_exp[MAX_REGEXP];	/* regular expressions for server headers */
+    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 grace;				/* grace time after stop request */
 };
 
@@ -313,10 +394,16 @@
 void **pool_session = NULL,
     **pool_buffer   = NULL,
     **pool_fdtab    = NULL,
-    **pool_str256   = NULL;
+    **pool_str256   = NULL,
+    **pool_task	    = NULL;
 
 struct proxy *proxy  = NULL;	/* list of all existing proxies */
 struct fdtab *fdtab = NULL;	/* array of all the file descriptors */
+struct task *rq = NULL;		/* global run queue */
+struct task wait_queue = {	/* global wait queue */
+    prev:LIST_HEAD(wait_queue),
+    next:LIST_HEAD(wait_queue)
+};
 
 static int mode = 0;		/* MODE_DEBUG, ... */
 static int totalconn = 0;	/* total # of terminated sessions */
@@ -376,6 +463,7 @@
 int event_cli_write(int fd);
 int event_srv_read(int fd);
 int event_srv_write(int fd);
+int process_session(struct task *t);
 
 /*********************************************************************/
 /*  general purpose functions  ***************************************/
@@ -383,7 +471,7 @@
 
 void display_version() {
     printf("HA-Proxy version " HAPROXY_VERSION " " HAPROXY_DATE"\n");
-    printf("Copyright 2000-2001 Willy Tarreau <willy AT meta-x DOT org>\n\n");
+    printf("Copyright 2000-2002 Willy Tarreau <willy AT meta-x DOT org>\n\n");
 }
 
 /*
@@ -403,7 +491,8 @@
 	    "        -s enables statistics output\n"
 	    "        -l enables long statistics format\n"
 #endif
-	    "        -D goes daemon\n"
+	    "        -D goes daemon ; implies -q\n"
+	    "        -q quiet mode : don't display messages\n"
 	    "        -n sets the maximum total # of connections (%d)\n"
 	    "        -N sets the default, per-proxy maximum # of connections (%d)\n\n",
 	    name, cfg_maxconn, cfg_maxpconn);
@@ -419,15 +508,17 @@
     struct timeval tv;
     struct tm *tm;
 
-    va_start(argp, fmt);
+    if (!(mode & MODE_QUIET)) {
+	va_start(argp, fmt);
 
-    gettimeofday(&tv, NULL);
-    tm=localtime(&tv.tv_sec);
-    fprintf(stderr, "[ALERT] %03d/%02d%02d%02d (%d) : ",
-	    tm->tm_yday, tm->tm_hour, tm->tm_min, tm->tm_sec, getpid());
-    vfprintf(stderr, fmt, argp);
-    fflush(stderr);
-    va_end(argp);
+	gettimeofday(&tv, NULL);
+	tm=localtime(&tv.tv_sec);
+	fprintf(stderr, "[ALERT] %03d/%02d%02d%02d (%d) : ",
+		tm->tm_yday, tm->tm_hour, tm->tm_min, tm->tm_sec, getpid());
+	vfprintf(stderr, fmt, argp);
+	fflush(stderr);
+	va_end(argp);
+    }
 }
 
 
@@ -439,15 +530,31 @@
     struct timeval tv;
     struct tm *tm;
 
-    va_start(argp, fmt);
+    if (!(mode & MODE_QUIET)) {
+	va_start(argp, fmt);
 
-    gettimeofday(&tv, NULL);
-    tm=localtime(&tv.tv_sec);
-    fprintf(stderr, "[WARNING] %03d/%02d%02d%02d (%d) : ",
-	    tm->tm_yday, tm->tm_hour, tm->tm_min, tm->tm_sec, getpid());
-    vfprintf(stderr, fmt, argp);
-    fflush(stderr);
-    va_end(argp);
+	gettimeofday(&tv, NULL);
+	tm=localtime(&tv.tv_sec);
+	fprintf(stderr, "[WARNING] %03d/%02d%02d%02d (%d) : ",
+		tm->tm_yday, tm->tm_hour, tm->tm_min, tm->tm_sec, getpid());
+	vfprintf(stderr, fmt, argp);
+	fflush(stderr);
+	va_end(argp);
+    }
+}
+
+/*
+ * Displays the message on <out> only if quiet mode is not set.
+ */
+void qfprintf(FILE *out, char *fmt, ...) {
+    va_list argp;
+
+    if (!(mode & MODE_QUIET)) {
+	va_start(argp, fmt);
+	vfprintf(out, fmt, argp);
+	fflush(out);
+	va_end(argp);
+    }
 }
 
 
@@ -743,11 +850,14 @@
 
 
 
-/* deletes an FD from the fdsets, and recomputes the maxfd limit */
+/* Deletes an FD from the fdsets, and recomputes the maxfd limit.
+ * The file descriptor is also closed.
+ */
 static inline void fd_delete(int fd) {
-    fdtab[fd].state = FD_STCLOSE;
     FD_CLR(fd, StaticReadEvent);
     FD_CLR(fd, StaticWriteEvent);
+    close(fd);
+    fdtab[fd].state = FD_STCLOSE;
 
     while ((maxfd-1 >= 0) && (fdtab[maxfd-1].state == FD_STCLOSE))
 	    maxfd--;
@@ -763,58 +873,52 @@
 /*   task management   ***************************************/
 /*************************************************************/
 
-/* puts the task <s> in <p>'s run queue, and returns <s> */
-static inline struct task *task_wakeup(struct proxy *p, struct task *s) {
-    //    fprintf(stderr,"task_wakeup: proxy %p, task %p\n", p, s);
-
-    if (s->state == TASK_RUNNING)
-	return s;
+/* puts the task <t> in run queue <q>, and returns <t> */
+static inline struct task *task_wakeup(struct task **q, struct task *t) {
+    if (t->state == TASK_RUNNING)
+	return t;
     else {
-	s->rqnext = p->rq;
-	s->state = TASK_RUNNING;
-	return p->rq = s;
+	t->rqnext = *q;
+	t->state = TASK_RUNNING;
+	return *q = t;
     }
 }
 
-/* removes the task <s> from <p>'s run queue.
- * <s> MUST be <p>'s first task in the queue.
+/* removes the task <t> from the queue <q>
+ * <s> MUST be <q>'s first task.
  * set the run queue to point to the next one, and return it
  */
-static inline struct task *task_sleep(struct proxy *p, struct task *s) {
-    if (s->state == TASK_RUNNING) {
-	p->rq = s->rqnext;
-	s->state = TASK_IDLE; /* tell that s has left the run queue */
+static inline struct task *task_sleep(struct task **q, struct task *t) {
+    if (t->state == TASK_RUNNING) {
+	*q = t->rqnext;
+	t->state = TASK_IDLE; /* tell that s has left the run queue */
     }
-    return p->rq; /* return next running task */
+    return *q; /* return next running task */
 }
 
 /*
- * removes the task <s> from its wait queue. It must have already been removed
+ * removes the task <t> from its wait queue. It must have already been removed
  * from the run queue. A pointer to the task itself is returned.
  */
-static inline struct task *task_delete(struct task *s) {
-    s->prev->next = s->next;
-    s->next->prev = s->prev;
-    return s;
+static inline struct task *task_delete(struct task *t) {
+    t->prev->next = t->next;
+    t->next->prev = t->prev;
+    return t;
 }
 
 /*
- * frees  the context associated to a task. It must have been removed first.
+ * frees a task. Its context must have been freed since it will be lost.
  */
 static inline void task_free(struct task *t) {
-    if (t->req)
-	pool_free(buffer, t->req);
-    if (t->rep)
-	pool_free(buffer, t->rep);
-    pool_free(session, t);
+    pool_free(task, t);
 }
 
-/* inserts <task> into the list <list>, where it may already be. In this case, it
+/* inserts <task> into its assigned wait queue, where it may already be. In this case, it
  * may be only moved or left where it was, depending on its timing requirements.
  * <task> is returned.
  */
-
-struct task *task_queue(struct task *list, struct task *task) {
+struct task *task_queue(struct task *task) {
+    struct task *list = task->wq;
     struct task *start_from;
 
     /* first, test if the task was already in a list */
@@ -884,49 +988,102 @@
 /* some prototypes */
 static int maintain_proxies(void);
 
+/* this either returns the sockname or the original destination address. Code
+ * inspired from Patrick Schaaf's example of nf_getsockname() implementation.
+ */
+static int get_original_dst(int fd, struct sockaddr_in *sa, int *salen) {
+#if defined(TRANSPARENT) && defined(SO_ORIGINAL_DST)
+    return getsockopt(fd, SOL_IP, SO_ORIGINAL_DST, (void *)sa, salen);
+#else
+#if defined(TRANSPARENT) && defined(USE_GETSOCKNAME)
+    return getsockname(fd, (struct sockaddr *)sa, salen);
+#else
+    return -1;
+#endif
+#endif
+}
+
+/*
+ * frees  the context associated to a session. It must have been removed first.
+ */
+static inline void session_free(struct session *s) {
+    if (s->req)
+	pool_free(buffer, s->req);
+    if (s->rep)
+	pool_free(buffer, s->rep);
+    pool_free(session, s);
+}
+
 
 /*
- * This function initiates a connection to the server whose name is in <s->proxy->src->id>,
- * or the dispatch server if <id> not found. It returns 0 if
+ * This function initiates a connection to the current server (s->srv) if (s->direct)
+ * is set, or to the dispatch server if (s->direct) is 0. It returns 0 if
  * it's OK, -1 if it's impossible.
  */
-int connect_server(struct task *s, int usecookie) {
-    struct server *srv = s->proxy->srv;
-    char *sn = s->cookie_val;
+int connect_server(struct session *s) {
     int one = 1;
     int fd;
 
     //    fprintf(stderr,"connect_server : s=%p\n",s);
 
-    if (usecookie) {
-	while (*sn && srv && strcmp(sn, srv->id)) {
-	    srv = srv->next;
-	}
-	if (!srv || !*sn) { /* server not found, let's use the dispatcher */
-	    s->srv_addr = s->proxy->dispatch_addr;
-	}
-	else {
-	    s->srv_addr = srv->addr;
+    if (s->flags & TF_DIRECT) { /* srv cannot be null */
+	s->srv_addr = s->srv->addr;
+    }
+    else if (s->proxy->options & PR_O_BALANCE) {
+	if (s->proxy->options & PR_O_BALANCE_RR) {
+	    int retry = s->proxy->nbservers;
+	    do {
+		if (s->proxy->cursrv == NULL)
+		    s->proxy->cursrv = s->proxy->srv;
+		if (s->proxy->cursrv->state & SRV_RUNNING)
+		    break;
+		s->proxy->cursrv = s->proxy->cursrv->next;
+	    } while (retry--);
+
+	    if (retry == 0) /* no server left */
+		return -1;
+
+	    s->srv = s->proxy->cursrv;
+	    s->srv_addr = s->srv->addr;
+	    s->proxy->cursrv = s->proxy->cursrv->next;
 	}
+	else /* unknown balancing algorithm */
+	    return -1;
     }
-    else
+    else if (*(int *)&s->proxy->dispatch_addr) {
+	/* connect to the defined dispatch addr */
 	s->srv_addr = s->proxy->dispatch_addr;
+    }
+    else if (s->proxy->options & PR_O_TRANSP) {
+	/* in transparent mode, use the original dest addr if no dispatch specified */
+	int salen = sizeof(struct sockaddr_in);
+	if (get_original_dst(s->cli_fd, &s->srv_addr, &salen) == -1) {
+	    qfprintf(stderr, "Cannot get original server address.\n");
+	    return -1;
+	}
+    }
 
     if ((fd = s->srv_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) {
-	fprintf(stderr,"Cannot get a server socket.\n");
+	qfprintf(stderr, "Cannot get a server socket.\n");
 	return -1;
     }
 	
+    if (fd >= cfg_maxsock) {
+	Alert("socket(): not enough free sockets. Raise -n argument. Giving up.\n");
+	close(fd);
+	return -1;
+    }
+
     if ((fcntl(fd, F_SETFL, O_NONBLOCK)==-1) ||
 	(setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char *) &one, sizeof(one)) == -1)) {
-	fprintf(stderr,"Cannot set client socket to non blocking mode.\n");
+	qfprintf(stderr,"Cannot set client socket to non blocking mode.\n");
 	close(fd);
 	return -1;
     }
 
     if ((connect(fd, (struct sockaddr *)&s->srv_addr, sizeof(s->srv_addr)) == -1) && (errno != EINPROGRESS)) {
 	if (errno == EAGAIN) { /* no free ports left, try again later */
-	    fprintf(stderr,"Cannot connect, no free ports.\n");
+	    qfprintf(stderr,"Cannot connect, no free ports.\n");
 	    close(fd);
 	    return -1;
 	}
@@ -936,7 +1093,7 @@
 	}
     }
 
-    fdtab[fd].owner = s;
+    fdtab[fd].owner = s->task;
     fdtab[fd].read  = &event_srv_read;
     fdtab[fd].write = &event_srv_write;
     fdtab[fd].state = FD_STCONN; /* connection in progress */
@@ -957,74 +1114,86 @@
  * It returns 0.
  */
 int event_cli_read(int fd) {
-    struct task *s = fdtab[fd].owner;
+    struct task *t = fdtab[fd].owner;
+    struct session *s = t->context;
     struct buffer *b = s->req;
     int ret, max;
 
     //    fprintf(stderr,"event_cli_read : fd=%d, s=%p\n", fd, s);
 
-    if (b->l == 0) { /* let's realign the buffer to optimize I/O */
-	b->r = b->w = b->h = b->lr  = b->data;
-	max = BUFSIZE - MAXREWRITE;
-    }
-    else if (b->r > b->w) {
-	max = b->data + BUFSIZE - MAXREWRITE - b->r;
-    }
-    else {
-	max = b->w - b->r;
-	if (max > BUFSIZE - MAXREWRITE)
-	    max = BUFSIZE - MAXREWRITE;
-    }
-
-    if (max == 0) {
-	FD_CLR(fd, StaticReadEvent);
-	//fprintf(stderr, "cli_read(%d) : max=%d, d=%p, r=%p, w=%p, l=%d\n",
-	//fd, max, b->data, b->r, b->w, b->l);
-	return 0;
-    }
-
     if (fdtab[fd].state != FD_STERROR) {
+	while (1) {
+	    if (b->l == 0) { /* let's realign the buffer to optimize I/O */
+		b->r = b->w = b->h = b->lr  = b->data;
+		max = BUFSIZE - MAXREWRITE;
+	    }
+	    else if (b->r > b->w) {
+		max = b->data + BUFSIZE - MAXREWRITE - b->r;
+	    }
+	    else {
+		max = b->w - b->r;
+		if (max > BUFSIZE - MAXREWRITE)
+		    max = BUFSIZE - MAXREWRITE;
+	    }
+	    
+	    if (max == 0) {  /* not anymore room to store data */
+		FD_CLR(fd, StaticReadEvent);
+		break;;
+	    }
+	    
 #ifndef MSG_NOSIGNAL
-	int skerr, lskerr;
-	lskerr=sizeof(skerr);
-	getsockopt(fd, SOL_SOCKET, SO_ERROR, &skerr, &lskerr);
-	if (skerr)
-		ret = -1;
-	else
-		ret = recv(fd, b->r, max, 0);
+	    {
+		int skerr, lskerr;
+		
+		lskerr = sizeof(skerr);
+		getsockopt(fd, SOL_SOCKET, SO_ERROR, &skerr, &lskerr);
+		if (skerr)
+		    ret = -1;
+		else
+		    ret = recv(fd, b->r, max, 0);
+	    }
 #else
-	ret = recv(fd, b->r, max, MSG_NOSIGNAL);
+	    ret = recv(fd, b->r, max, MSG_NOSIGNAL);
 #endif
-
-	if (ret > 0) {
-	    b->r += ret;
-	    b->l += ret;
-	    s->res_cr = RES_DATA;
-	    
-	    if (b->r == b->data + BUFSIZE) {
-		b->r = b->data; /* wrap around the buffer */
+	    if (ret > 0) {
+		b->r += ret;
+		b->l += ret;
+		s->res_cr = RES_DATA;
+		
+		if (b->r == b->data + BUFSIZE) {
+		    b->r = b->data; /* wrap around the buffer */
+		}
+		/* we hope to read more data or to get a close on next round */
+		continue;
 	    }
-	}
-	else if (ret == 0)
-	    s->res_cr = RES_NULL;
-	else if (errno == EAGAIN) /* ignore EAGAIN */
-	    return 0;
-	else {
-	    s->res_cr = RES_ERROR;
-	    fdtab[fd].state = FD_STERROR;
-	}
+	    else if (ret == 0) {
+		s->res_cr = RES_NULL;
+		break;
+	    }
+	    else if (errno == EAGAIN) {/* ignore EAGAIN */
+		break;
+	    }
+	    else {
+		s->res_cr = RES_ERROR;
+		fdtab[fd].state = FD_STERROR;
+		break;
+	    }
+	} /* while(1) */
     }
     else {
 	s->res_cr = RES_ERROR;
 	fdtab[fd].state = FD_STERROR;
     }
 
-    if (s->proxy->clitimeout)
-	tv_delayfrom(&s->crexpire, &now, s->proxy->clitimeout);
-    else
-	tv_eternity(&s->crexpire);
+    if (s->res_cr != RES_SILENT) {
+	if (s->proxy->clitimeout)
+	    tv_delayfrom(&s->crexpire, &now, s->proxy->clitimeout);
+	else
+	    tv_eternity(&s->crexpire);
+	
+	task_wakeup(&rq, t);
+    }
 
-    task_wakeup(s->proxy, s);
     return 0;
 }
 
@@ -1034,74 +1203,86 @@
  * It returns 0.
  */
 int event_srv_read(int fd) {
-    struct task *s = fdtab[fd].owner;
+    struct task *t = fdtab[fd].owner;
+    struct session *s = t->context;
     struct buffer *b = s->rep;
     int ret, max;
 
     //    fprintf(stderr,"event_srv_read : fd=%d, s=%p\n", fd, s);
 
-    if (b->l == 0) { /* let's realign the buffer to optimize I/O */
-	b->r = b->w = b->h = b->lr  = b->data;
-	max = BUFSIZE - MAXREWRITE;
-    }
-    else if (b->r > b->w) {
-	max = b->data + BUFSIZE - MAXREWRITE - b->r;
-    }
-    else {
-	max = b->w - b->r;
-    	if (max > BUFSIZE - MAXREWRITE)
-	    max = BUFSIZE - MAXREWRITE;
-    }
-
-    if (max == 0) {
-	FD_CLR(fd, StaticReadEvent);
-	//fprintf(stderr, "srv_read(%d) : max=%d, d=%p, r=%p, w=%p, l=%d\n",
-	//fd, max, b->data, b->r, b->w, b->l);
-	return 0;
-    }
-
     if (fdtab[fd].state != FD_STERROR) {
+	while (1) {
+	    if (b->l == 0) { /* let's realign the buffer to optimize I/O */
+		b->r = b->w = b->h = b->lr  = b->data;
+		max = BUFSIZE - MAXREWRITE;
+	    }
+	    else if (b->r > b->w) {
+		max = b->data + BUFSIZE - MAXREWRITE - b->r;
+	    }
+	    else {
+		max = b->w - b->r;
+		if (max > BUFSIZE - MAXREWRITE)
+		    max = BUFSIZE - MAXREWRITE;
+	    }
+	    
+	    if (max == 0) {  /* not anymore room to store data */
+		FD_CLR(fd, StaticReadEvent);
+		break;
+	    }
+
 #ifndef MSG_NOSIGNAL
-	int skerr, lskerr;
-	lskerr=sizeof(skerr);
-	getsockopt(fd, SOL_SOCKET, SO_ERROR, &skerr, &lskerr);
-	if (skerr)
-		ret = -1;
-	else
-		ret = recv(fd, b->r, max, 0);
+	    {
+		int skerr, lskerr;
+
+		lskerr = sizeof(skerr);
+		getsockopt(fd, SOL_SOCKET, SO_ERROR, &skerr, &lskerr);
+		if (skerr)
+		    ret = -1;
+		else
+		    ret = recv(fd, b->r, max, 0);
+	    }
 #else
-	ret = recv(fd, b->r, max, MSG_NOSIGNAL);
+	    ret = recv(fd, b->r, max, MSG_NOSIGNAL);
 #endif
-	if (ret > 0) {
-	    b->r += ret;
-	    b->l += ret;
-	    s->res_sr = RES_DATA;
+	    if (ret > 0) {
+		b->r += ret;
+		b->l += ret;
+		s->res_sr = RES_DATA;
 	    
-	    if (b->r == b->data + BUFSIZE) {
-		b->r = b->data; /* wrap around the buffer */
+		if (b->r == b->data + BUFSIZE) {
+		    b->r = b->data; /* wrap around the buffer */
+		}
+		/* we hope to read more data or to get a close on next round */
+		continue;
 	    }
-	}
-	else if (ret == 0)
-	    s->res_sr = RES_NULL;
-	else if (errno != EAGAIN) /* ignore EAGAIN */
-	    return 0;
-	else {
-	    s->res_sr = RES_ERROR;
-	    fdtab[fd].state = FD_STERROR;
-	}
+	    else if (ret == 0) {
+		s->res_sr = RES_NULL;
+		break;
+	    }
+	    else if (errno == EAGAIN) {/* ignore EAGAIN */
+		break;
+	    }
+	    else {
+		s->res_sr = RES_ERROR;
+		fdtab[fd].state = FD_STERROR;
+		break;
+	    }
+	} /* while(1) */
     }
     else {
 	s->res_sr = RES_ERROR;
 	fdtab[fd].state = FD_STERROR;
     }
 
-
-    if (s->proxy->srvtimeout)
-	tv_delayfrom(&s->srexpire, &now, s->proxy->srvtimeout);
-    else
-	tv_eternity(&s->srexpire);
+    if (s->res_sr != RES_SILENT) {
+	if (s->proxy->srvtimeout)
+	    tv_delayfrom(&s->srexpire, &now, s->proxy->srvtimeout);
+	else
+	    tv_eternity(&s->srexpire);
+	
+	task_wakeup(&rq, t);
+    }
 
-    task_wakeup(s->proxy, s);
     return 0;
 }
 
@@ -1110,7 +1291,8 @@
  * It returns 0.
  */
 int event_cli_write(int fd) {
-    struct task *s = fdtab[fd].owner;
+    struct task *t = fdtab[fd].owner;
+    struct session *s = t->context;
     struct buffer *b = s->rep;
     int ret, max;
 
@@ -1128,10 +1310,11 @@
 	max = b->data + BUFSIZE - b->w;
     
     if (max == 0) {
-	FD_CLR(fd, StaticWriteEvent);
+	// FD_CLR(fd, StaticWriteEvent); // useless
 	//fprintf(stderr, "cli_write(%d) : max=%d, d=%p, r=%p, w=%p, l=%d\n",
 	//fd, max, b->data, b->r, b->w, b->l);
 	s->res_cw = RES_NULL;
+	task_wakeup(&rq, t);
 	return 0;
     }
 
@@ -1141,7 +1324,7 @@
 #endif
 	if (max == 0) { /* nothing to write, just make as if we were never called */
 		s->res_cw = RES_NULL;
-		task_wakeup(s->proxy, s);
+		task_wakeup(&rq, t);
 		return 0;
 	}
 
@@ -1188,7 +1371,7 @@
     else
 	tv_eternity(&s->cwexpire);
 
-    task_wakeup(s->proxy, s);
+    task_wakeup(&rq, t);
     return 0;
 }
 
@@ -1198,7 +1381,8 @@
  * It returns 0.
  */
 int event_srv_write(int fd) {
-    struct task *s = fdtab[fd].owner;
+    struct task *t = fdtab[fd].owner;
+    struct session *s = t->context;
     struct buffer *b = s->req;
     int ret, max;
 
@@ -1216,10 +1400,12 @@
 	max = b->data + BUFSIZE - b->w;
     
     if (max == 0) {
-	FD_CLR(fd, StaticWriteEvent);
+	/* may be we have received a connection acknowledgement in TCP mode without data */
+	// FD_CLR(fd, StaticWriteEvent); // useless ?
 	//fprintf(stderr, "srv_write(%d) : max=%d, d=%p, r=%p, w=%p, l=%d\n",
 	//fd, max, b->data, b->r, b->w, b->l);
 	s->res_sw = RES_NULL;
+	task_wakeup(&rq, t);
 	return 0;
     }
 
@@ -1229,8 +1415,9 @@
 #endif
 	fdtab[fd].state = FD_STREADY;
 	if (max == 0) { /* nothing to write, just make as if we were never called, except to finish a connect() */
+	    //FD_CLR(fd, StaticWriteEvent); // useless ?
 	    s->res_sw = RES_NULL;
-	    task_wakeup(s->proxy, s);
+	    task_wakeup(&rq, t);
 	    return 0;
 	}
 
@@ -1276,142 +1463,195 @@
     else
 	tv_eternity(&s->swexpire);
 
-    task_wakeup(s->proxy, s);
+    task_wakeup(&rq, t);
     return 0;
 }
 
 
 /*
  * this function is called on a read event from a listen socket, corresponding
- * to an accept. It returns 0.
+ * to an accept. It tries to accept as many connections as possible.
+ * It returns 0.
  */
 int event_accept(int fd) {
     struct proxy *p = (struct proxy *)fdtab[fd].owner;
-    struct task *s;
-    int laddr;
+    struct session *s;
+    struct task *t;
     int cfd;
     int one = 1;
 
-    if ((s = pool_alloc(session)) == NULL) { /* disable this proxy for a while */
-	Alert("out of memory in event_accept().\n");
-	FD_CLR(fd, StaticReadEvent);
-	p->state = PR_STIDLE;
-	return 0;
-    }
 
-    laddr = sizeof(s->cli_addr);
-    if ((cfd = accept(fd, (struct sockaddr *)&s->cli_addr, &laddr)) == -1) {
-	pool_free(session, s);
-	return 0;
-    }
+    while (p->nbconn < p->maxconn) {
+	struct sockaddr_in addr;
+	int laddr = sizeof(addr);
+	if ((cfd = accept(fd, (struct sockaddr *)&addr, &laddr)) == -1)
+	    return 0;	    /* nothing more to accept */
 
-    if ((fcntl(cfd, F_SETFL, O_NONBLOCK) == -1) ||
-	(setsockopt(cfd, IPPROTO_TCP, TCP_NODELAY,
-		    (char *) &one, sizeof(one)) == -1)) {
-	Alert("accept(): cannot set the socket in non blocking mode. Giving up\n");
-	close(cfd);
-	pool_free(session, s);
-	return 0;
-    }
+	if ((s = pool_alloc(session)) == NULL) { /* disable this proxy for a while */
+	    Alert("out of memory in event_accept().\n");
+	    FD_CLR(fd, StaticReadEvent);
+	    p->state = PR_STIDLE;
+	    close(cfd);
+	    return 0;
+	}
 
-    if ((p->mode == PR_MODE_TCP || p->mode == PR_MODE_HTTP)
-	&& (p->logfac1 >= 0 || p->logfac2 >= 0)) {
-	struct sockaddr_in peername, sockname;
-	unsigned char *pn, *sn;
-	int namelen;
-	char message[256];
+	if ((t = pool_alloc(task)) == NULL) { /* disable this proxy for a while */
+	    Alert("out of memory in event_accept().\n");
+	    FD_CLR(fd, StaticReadEvent);
+	    p->state = PR_STIDLE;
+	    close(cfd);
+	    pool_free(session, s);
+	    return 0;
+	}
 
-	namelen = sizeof(peername);
-	getpeername(cfd, (struct sockaddr *)&peername, &namelen);
-	pn = (unsigned char *)&peername.sin_addr;
+	s->cli_addr = addr;
+	if (cfd >= cfg_maxsock) {
+	    Alert("accept(): not enough free sockets. Raise -n argument. Giving up.\n");
+	    close(cfd);
+	    pool_free(task, t);
+	    pool_free(session, s);
+	    return 0;
+	}
 
-	namelen = sizeof(sockname);
-	getsockname(cfd, (struct sockaddr *)&sockname, &namelen);
-	sn = (unsigned char *)&sockname.sin_addr;
+	if ((fcntl(cfd, F_SETFL, O_NONBLOCK) == -1) ||
+	    (setsockopt(cfd, IPPROTO_TCP, TCP_NODELAY,
+			(char *) &one, sizeof(one)) == -1)) {
+	    Alert("accept(): cannot set the socket in non blocking mode. Giving up\n");
+	    close(cfd);
+	    pool_free(task, t);
+	    pool_free(session, s);
+	    return 0;
+	}
 
-	sprintf(message, "Connect from %d.%d.%d.%d:%d to %d.%d.%d.%d:%d (%s/%s)\n",
-		pn[0], pn[1], pn[2], pn[3], ntohs(peername.sin_port),
-		sn[0], sn[1], sn[2], sn[3], ntohs(sockname.sin_port),
-		p->id, (p->mode == PR_MODE_HTTP) ? "HTTP" : "TCP");
+	if ((p->mode == PR_MODE_TCP || p->mode == PR_MODE_HTTP)
+	    && (p->logfac1 >= 0 || p->logfac2 >= 0)) {
+	    struct sockaddr_in peername, sockname;
+	    unsigned char *pn, *sn;
+	    int namelen;
+	    char message[256];
 
-	if (p->logfac1 >= 0)
-	    send_syslog(&p->logsrv1, p->logfac1, LOG_INFO, message);
-	if (p->logfac2 >= 0)
-	    send_syslog(&p->logsrv2, p->logfac2, LOG_INFO, message);
-    }
+	    //namelen = sizeof(peername);
+	    //getpeername(cfd, (struct sockaddr *)&peername, &namelen);
+	    //pn = (unsigned char *)&peername.sin_addr;
+	    pn = (unsigned char *)&s->cli_addr;
 
-    s->proxy = p;
-    s->state = TASK_IDLE;
-    s->cli_state = (p->mode == PR_MODE_HTTP) ?  CL_STHEADERS : CL_STDATA; /* no HTTP headers for non-HTTP proxies */
-    s->srv_state = SV_STIDLE;
-    s->req = s->rep = NULL; /* will be allocated later */
-    s->cookie_val[0] = 0;
-    s->res_cr = s->res_cw = s->res_sr = s->res_sw = RES_SILENT;
-    s->rqnext = NULL; /* task not in run queue */
-    s->next = s->prev = NULL;
-    s->cli_fd = cfd;
-    s->conn_retries = p->conn_retries;
-    s->conn_redisp  = p->conn_redisp;
+	    namelen = sizeof(sockname);
+	    if (get_original_dst(cfd, (struct sockaddr_in *)&sockname, &namelen) == -1)
+		getsockname(cfd, (struct sockaddr *)&sockname, &namelen);
+	    sn = (unsigned char *)&sockname.sin_addr;
 
-    if ((s->req = pool_alloc(buffer)) == NULL) { /* no memory */
-	close(cfd); /* nothing can be done for this fd without memory */
-	pool_free(session, s);
-	return 0;
-    }
-    s->req->l = 0;
-    s->req->h = s->req->r = s->req->lr = s->req->w = s->req->data;		/* r and w will be reset further */
+	    sprintf(message, "Connect from %d.%d.%d.%d:%d to %d.%d.%d.%d:%d (%s/%s)\n",
+		    pn[0], pn[1], pn[2], pn[3], ntohs(peername.sin_port),
+		    sn[0], sn[1], sn[2], sn[3], ntohs(sockname.sin_port),
+		    p->id, (p->mode == PR_MODE_HTTP) ? "HTTP" : "TCP");
 
-    if ((s->rep = pool_alloc(buffer)) == NULL) { /* no memory */
-	pool_free(buffer, s->req);
-	close(cfd); /* nothing can be done for this fd without memory */
-	pool_free(session, s);
-	return 0;
-    }
-    s->rep->l = 0;
-    s->rep->h = s->rep->r = s->rep->lr = s->rep->w = s->rep->data;
+	    if (p->logfac1 >= 0)
+		send_syslog(&p->logsrv1, p->logfac1, LOG_INFO, message);
+	    if (p->logfac2 >= 0)
+		send_syslog(&p->logsrv2, p->logfac2, LOG_INFO, message);
+	}
 
-    fdtab[cfd].read  = &event_cli_read;
-    fdtab[cfd].write = &event_cli_write;
-    fdtab[cfd].owner = s;
-    fdtab[cfd].state = FD_STREADY;
 
-    if (p->mode == PR_MODE_HEALTH) {  /* health check mode, no client reading */
-	FD_CLR(cfd, StaticReadEvent);
-	tv_eternity(&s->crexpire);
-	shutdown(s->cli_fd, SHUT_RD);
-	s->cli_state = CL_STSHUTR;
+	t->next = t->prev = t->rqnext = NULL; /* task not in run queue yet */
+	t->wq = LIST_HEAD(wait_queue); /* but already has a wait queue assigned */
+	t->state = TASK_IDLE;
+	t->process = process_session;
+	t->context = s;
 
-	strcpy(s->rep->data, "OK\n"); /* forge an "OK" response */
-	s->rep->l = 3;
-	s->rep->r += 3;
-    }
-    else {
-	FD_SET(cfd, StaticReadEvent);
-    }
+	s->task = t;
+	s->proxy = p;
+	s->cli_state = (p->mode == PR_MODE_HTTP) ?  CL_STHEADERS : CL_STDATA; /* no HTTP headers for non-HTTP proxies */
+	s->srv_state = SV_STIDLE;
+	s->req = s->rep = NULL; /* will be allocated later */
+	s->flags = 0;
+	s->res_cr = s->res_cw = s->res_sr = s->res_sw = RES_SILENT;
+	s->cli_fd = cfd;
+	s->srv_fd = -1;
+	s->conn_retries = p->conn_retries;
+
+	if ((s->req = pool_alloc(buffer)) == NULL) { /* no memory */
+	    close(cfd); /* nothing can be done for this fd without memory */
+	    pool_free(task, t);
+	    pool_free(session, s);
+	    return 0;
+	}
+	s->req->l = 0;
+	s->req->h = s->req->r = s->req->lr = s->req->w = s->req->data;		/* r and w will be reset further */
 
-    fd_insert(cfd);
+	if ((s->rep = pool_alloc(buffer)) == NULL) { /* no memory */
+	    pool_free(buffer, s->req);
+	    close(cfd); /* nothing can be done for this fd without memory */
+	    pool_free(task, t);
+	    pool_free(session, s);
+	    return 0;
+	}
+	s->rep->l = 0;
+	s->rep->h = s->rep->r = s->rep->lr = s->rep->w = s->rep->data;
 
-    tv_eternity(&s->cnexpire);
-    tv_eternity(&s->srexpire);
-    tv_eternity(&s->swexpire);
-    tv_eternity(&s->cwexpire);
+	fdtab[cfd].read  = &event_cli_read;
+	fdtab[cfd].write = &event_cli_write;
+	fdtab[cfd].owner = t;
+	fdtab[cfd].state = FD_STREADY;
 
-    if (s->proxy->clitimeout)
-	tv_delayfrom(&s->crexpire, &now, s->proxy->clitimeout);
-    else
-	tv_eternity(&s->crexpire);
+	if (p->mode == PR_MODE_HEALTH) {  /* health check mode, no client reading */
+	    FD_CLR(cfd, StaticReadEvent);
+	    tv_eternity(&s->crexpire);
+	    shutdown(s->cli_fd, SHUT_RD);
+	    s->cli_state = CL_STSHUTR;
 
-    s->expire = s->crexpire;
+	    strcpy(s->rep->data, "OK\n"); /* forge an "OK" response */
+	    s->rep->l = 3;
+	    s->rep->r += 3;
+	}
+	else {
+	    FD_SET(cfd, StaticReadEvent);
+	}
+
+	fd_insert(cfd);
+
+	tv_eternity(&s->cnexpire);
+	tv_eternity(&s->srexpire);
+	tv_eternity(&s->swexpire);
+	tv_eternity(&s->cwexpire);
+
+	if (s->proxy->clitimeout)
+	    tv_delayfrom(&s->crexpire, &now, s->proxy->clitimeout);
+	else
+	    tv_eternity(&s->crexpire);
+
+	t->expire = s->crexpire;
+
+	task_queue(t);
+	task_wakeup(&rq, t);
+
+	p->nbconn++;
+	actconn++;
+	totalconn++;
+
+	// fprintf(stderr, "accepting from %p => %d conn, %d total\n", p, actconn, totalconn);
+    } /* end of while (p->nbconn < p->maxconn) */
+    return 0;
+}
 
-    task_queue(LIST_HEAD(p->task), s);
-    task_wakeup(p, s);
 
-    p->nbconn++;
-    actconn++;
-    totalconn++;
+/*
+ * This function is used only for server health-checks. It handles
+ * the connection acknowledgement and returns 1 if the socket is OK,
+ * or -1 if an error occured.
+ */
+int event_srv_hck(int fd) {
+    struct task *t = fdtab[fd].owner;
+    struct server *s = t->context;
 
-    // fprintf(stderr, "accepting from %p => %d conn, %d total\n", p, actconn, totalconn);
+    int skerr, lskerr;
+    lskerr=sizeof(skerr);
+    getsockopt(fd, SOL_SOCKET, SO_ERROR, &skerr, &lskerr);
+    if (skerr)
+	s->result = -1;
+    else
+	s->result = 1;
 
+    task_wakeup(&rq, t);
     return 0;
 }
 
@@ -1424,7 +1664,7 @@
  * If there's no space left, the move is not done.
  *
  */
-int buffer_replace(struct buffer *b, char *pos, char *str, char *end) {
+int buffer_replace(struct buffer *b, char *pos, char *end, char *str) {
     int delta;
     int len;
 
@@ -1440,17 +1680,18 @@
     /* now, copy str over pos */
     memcpy(pos, str,len);
 
-    if (b->r >= end) b->r += delta;
-    if (b->w >= end) b->w += delta;
-    if (b->h >= end) b->h += delta;
-    if (b->lr >= end) b->lr += delta;
+    /* we only move data after the displaced zone */
+    if (b->r  > pos) b->r  += delta;
+    if (b->w  > pos) b->w  += delta;
+    if (b->h  > pos) b->h  += delta;
+    if (b->lr > pos) b->lr += delta;
     b->l += delta;
 
     return delta;
 }
 
 /* same except that the string len is given */
-int buffer_replace2(struct buffer *b, char *pos, char *str, int len, char *end) {
+int buffer_replace2(struct buffer *b, char *pos, char *end, char *str, int len) {
     int delta;
 
     delta = len - (end - pos);
@@ -1464,10 +1705,11 @@
     /* now, copy str over pos */
     memcpy(pos, str,len);
 
-    if (b->r >= end) b->r += delta;
-    if (b->w >= end) b->w += delta;
-    if (b->h >= end) b->h += delta;
-    if (b->lr >= end) b->lr += delta;
+    /* we only move data after the displaced zone */
+    if (b->r  > pos) b->r  += delta;
+    if (b->w  > pos) b->w  += delta;
+    if (b->h  > pos) b->h  += delta;
+    if (b->lr > pos) b->lr += delta;
     b->l += delta;
 
     return delta;
@@ -1518,7 +1760,7 @@
  * cookie. It returns 1 if a state has changed (and a resync may be needed),
  * 0 else.
  */
-int process_cli(struct task *t) {
+int process_cli(struct session *t) {
     int s = t->srv_state;
     int c = t->cli_state;
     struct buffer *req = t->req;
@@ -1529,177 +1771,225 @@
     //FD_ISSET(t->srv_fd, StaticReadEvent), FD_ISSET(t->srv_fd, StaticWriteEvent)
     //);
     if (c == CL_STHEADERS) {
-	char *ptr;
+	/* now parse the partial (or complete) headers */
+	while (req->lr < req->r) { /* this loop only sees one header at each iteration */
+	    char *ptr;
+	    int delete_header;
 	
-	/* read timeout, read error, or last read : give up */
-	if (t->res_cr == RES_ERROR || t->res_cr == RES_NULL ||
-	    tv_cmp2_ms(&t->crexpire, &now) <= 0) {
-	    FD_CLR(t->cli_fd, StaticReadEvent);
-	    FD_CLR(t->cli_fd, StaticWriteEvent);
-	    fd_delete(t->cli_fd);
-	    close(t->cli_fd);
-	    tv_eternity(&t->crexpire);
-	    t->cli_state = CL_STCLOSE;
-	    return 1;
-	}
-	else if (t->res_cr == RES_SILENT) {
-	    return 0;
-	}
-	/* now we know that there are headers to process */
-
-	if (req->l >= BUFSIZE - MAXREWRITE) {
-	    /* buffer full : stop reading till we free some space */
-	    FD_CLR(t->cli_fd, StaticReadEvent);
-	    tv_eternity(&t->crexpire);
-	}
+	    ptr = req->lr;
 
-	ptr = req->lr;
-	req->lr = req->r; /* tell that bytes up to <lr> have been read and processes */
-	while (ptr < req->r) {
 	    /* look for the end of the current header */
 	    while (ptr < req->r && *ptr != '\n' && *ptr != '\r')
 		ptr++;
 	    
-	    if (ptr < req->r) {
-		/* now we have one complete client header between req->h and ptr */
-		if (ptr == req->h) { /* empty line, end of headers */
-		    t->cli_state = CL_STDATA;
-		    //req->lr = ptr; /* tell that bytes up to <lr> have been read and processes */
-		    return 1;
-		}
-		else {
-		    /* we have one standard header */
-		    if (mode & MODE_DEBUG) {
-			int len, max;
-			len = sprintf(trash, "clihdr[%04x:%04x]: ", (unsigned  short)t->cli_fd, (unsigned short)t->srv_fd);
-			max = ptr - req->h;
-			UBOUND(max, sizeof(trash) - len - 1);
-			len += strlcpy(trash + len, req->h, max + 1);
-			trash[len++] = '\n';
-			write(1, trash, len);
-		    }
-
-		    if ((req->r >= req->h + 8) && (t->proxy->cookie_name != NULL)
-			&& (strncmp(req->h, "Cookie: ", 8) == 0)) {
-			char *p1, *p2, *p3, *p4;
-
-			p1 = req->h + 8; /* first char after 'Cookie: ' */
+	    if (ptr == req->h) { /* empty line, end of headers */
+		char newhdr[MAXREWRITE + 1];
+		int line, len;
+		/* we can only get here after an end of headers */
+		/* we'll have something else to do here : add new headers ... */
 
-			while (p1 < ptr) {
-			    while (p1 < ptr && (isspace(*p1) || *p1 == ';'))
-				p1++;
-
-			    if (p1 == ptr)
-				break;
-			    else if (*p1 == ';') { /* next cookie */
-				++p1;
-				continue;
-			    }
+		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);
+		}
 
-			    /* p1 is at the beginning of the cookie name */
-			    p2 = p1;
+		t->cli_state = CL_STDATA;
 
-			    while (p2 < ptr && *p2 != '=' && *p2 != ';')
-				p2++;
+		/* FIXME: we'll set the client in a wait state while we try to
+		 * connect to the server. Is this really needed ? wouldn't it be
+		 * better to release the maximum of system buffers instead ? */
+		FD_CLR(t->cli_fd, StaticReadEvent);
+		tv_eternity(&t->crexpire);
+		break;
+	    }
 
-			    if (p2 == ptr)
-				break;
-			    else if (*p2 == ';') { /* next cookie */
-				p1=++p2;
-				continue;
-			    }
+	    /* to get a complete header line, we need the ending \r\n, \n\r, \r or \n too */
+	    if (ptr > req->r - 2) {
+		/* this is a partial header, let's wait for more to come */
+		req->lr = ptr;
+		break;
+	    }
 
-			    p3 = p2 + 1; /* skips the '=' sign */
-			    if (p3 == ptr)
-				break;
+	    /* now we know that *ptr is either \r or \n,
+	     * and that there are at least 1 char after it.
+	     */
+	    if ((ptr[0] == ptr[1]) || (ptr[1] != '\r' && ptr[1] != '\n'))
+		req->lr = ptr + 1; /* \r\r, \n\n, \r[^\n], \n[^\r] */
+	    else
+		req->lr = ptr + 2; /* \r\n or \n\r */
 
-			    p4=p3;
-			    while (p4 < ptr && !isspace(*p4) && *p4 != ';')
-				p4++;
+	    /*
+	     * now we know that we have a full header ; we can do whatever
+	     * we want with these pointers :
+	     *   req->h  = beginning of header
+	     *   ptr     = end of header (first \r or \n)
+	     *   req->lr = beginning of next line (next rep->h)
+	     *   req->r  = end of data (not used at this stage)
+	     */
 
-			    /* here, we have the cookie name between p1 and p2,
-			     * and its value between p3 and p4.
-			     * we can process it.
-			     */
+	    delete_header = 0;
 
-			    if ((p2-p1 == strlen(t->proxy->cookie_name)) &&
-				(strncmp(p1, t->proxy->cookie_name, p2-p1) == 0)) {
-				/* Cool... it's the right one */
-				int l;
-				l = (p4 - p3) < SERVERID_LEN ?
-				    (p4 - p3) : SERVERID_LEN;
-				strlcpy(t->cookie_val, p3, l + 1);
-				break;
-			    }
-			    else {
-//				fprintf(stderr,"Ignoring unknown cookie : ");
-//				write(2, p1, p2-p1);
-//				fprintf(stderr," = ");
-//				write(2, p3, p4-p3);
-//				fprintf(stderr,"\n");
-			    }
-			    /* we'll have to look for another cookie ... */
-			    p1 = p4;
-			}
-			/* FIXME */
-//			fprintf(stderr,"Cookie is now: <%s>\n", s->cookie_val);
-		    }
-		    else if (t->proxy->nb_cliexp) { /* try headers regexps */
-			struct proxy *p = t->proxy;
-			int exp;
-			char term;
+	    if ((mode & MODE_DEBUG) && !(mode & MODE_QUIET)) {
+		int len, max;
+		len = sprintf(trash, "clihdr[%04x:%04x]: ", (unsigned  short)t->cli_fd, (unsigned short)t->srv_fd);
+		max = ptr - req->h;
+		UBOUND(max, sizeof(trash) - len - 1);
+		len += strlcpy(trash + len, req->h, max + 1);
+		trash[len++] = '\n';
+		write(1, trash, len);
+	    }
 
-			term = *ptr;
-			*ptr = '\0';
-			for (exp=0; exp < p->nb_cliexp; exp++) {
-			    if (regexec(p->cli_exp[exp].preg, req->h, MAX_MATCH, pmatch, 0) == 0) {
-				int len = exp_replace(trash, req->h, p->cli_exp[exp].replace, pmatch);
-				ptr += buffer_replace2(req, req->h, trash, len, ptr);
-				break;
-			    }
-			}
-			*ptr = term; /* restore the string terminator */
-		    }
-		    
-		    /* look for the beginning of the next header */
-		    if (ptr < req->r) {
-			if (*ptr == '\n') {
-			    if ((++ptr < req->r) && (*ptr == '\r'))
-				ptr++;
+	    /* try headers regexps */
+	    if (t->proxy->nb_reqexp) {
+		struct proxy *p = t->proxy;
+		int 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 if (*ptr == '\r') {
-			    if ((++ptr < req->r) && (*ptr == '\n'))
-				ptr++;
+			else {
+			    delete_header = 1;
 			}
-			req->h = ptr;
+			break;
 		    }
 		}
+		*ptr = term; /* restore the string terminator */
 	    }
-	    else if (ptr >= req->data + BUFSIZE - MAXREWRITE) { /* no more headers */
-		t->cli_state = CL_STDATA;
-		FD_CLR(t->cli_fd, StaticReadEvent);
-		tv_eternity(&t->crexpire);
-		//req->lr = ptr; /* tell that bytes up to <lr> have been read and processes */
-		return 1;
+	    
+	    /* now look for cookies */
+	    if (!delete_header && (req->r >= req->h + 8) && (t->proxy->cookie_name != NULL)
+		&& (strncmp(req->h, "Cookie: ", 8) == 0)) {
+		char *p1, *p2, *p3, *p4;
+		
+		p1 = req->h + 8; /* first char after 'Cookie: ' */
+		
+		while (p1 < ptr) {
+		    while (p1 < ptr && (isspace(*p1) || *p1 == ';'))
+			p1++;
+		    
+		    if (p1 == ptr)
+			break;
+		    else if (*p1 == ';') { /* next cookie */
+			++p1;
+			continue;
+		    }
+		    
+		    /* p1 is at the beginning of the cookie name */
+		    p2 = p1;
+		    
+		    while (p2 < ptr && *p2 != '=' && *p2 != ';')
+			p2++;
+		    
+		    if (p2 == ptr)
+			break;
+		    else if (*p2 == ';') { /* next cookie */
+			p1=++p2;
+			continue;
+		    }
+
+		    p3 = p2 + 1; /* skips the '=' sign */
+		    if (p3 == ptr)
+			break;
+		    
+		    p4=p3;
+		    while (p4 < ptr && !isspace(*p4) && *p4 != ';')
+			p4++;
+		    
+		    /* here, we have the cookie name between p1 and p2,
+		     * and its value between p3 and p4.
+		     * we can process it.
+		     */
+		    
+		    if ((p2 - p1 == strlen(t->proxy->cookie_name)) &&
+			(strncmp(p1, t->proxy->cookie_name, p2 - p1) == 0)) {
+			/* Cool... it's the right one */
+			struct server *srv = t->proxy->srv;
+
+			while (srv &&
+			       ((srv->cklen != p4 - p3) || memcmp(p3, srv->cookie, p4 - p3))) {
+			    srv = srv->next;
+			}
+
+			if (srv) { /* we found the server */
+			    t->flags |= TF_DIRECT;
+			    t->srv = srv;
+			}
+
+			break;
+		    }
+		    else {
+			// fprintf(stderr,"Ignoring unknown cookie : ");
+			// write(2, p1, p2-p1);
+			// fprintf(stderr," = ");
+			// write(2, p3, p4-p3);
+			// fprintf(stderr,"\n");
+		    }
+		    /* we'll have to look for another cookie ... */
+		    p1 = p4;
+		} /* while (p1 < ptr) */
+	    } /* end of cookie processing */
+
+	    /* let's look if we have to delete this header */
+	    if (delete_header) {
+		buffer_replace2(req, req->h, req->lr, "", 0);
 	    }
+	    req->h = req->lr;
+	} /* while (req->lr < req->r) */
+
+	/* end of header processing (even if incomplete) */
+
+	if ((req->l < BUFSIZE - MAXREWRITE) && ! FD_ISSET(t->cli_fd, StaticReadEvent)) {
+	    FD_SET(t->cli_fd, StaticReadEvent);
+	    if (t->proxy->clitimeout)
+		tv_delayfrom(&t->crexpire, &now, t->proxy->clitimeout);
+	    else
+		tv_eternity(&t->crexpire);
+	}
+
+	/* read timeout, read error, or last read : give up */
+	if (t->res_cr == RES_ERROR || t->res_cr == RES_NULL ||
+	    tv_cmp2_ms(&t->crexpire, &now) <= 0) {
+	    //FD_CLR(t->cli_fd, StaticReadEvent);
+	    //FD_CLR(t->cli_fd, StaticWriteEvent);
+	    tv_eternity(&t->crexpire);
+	    fd_delete(t->cli_fd);
+	    //close(t->cli_fd);
+	    t->cli_state = CL_STCLOSE;
+	    return 1;
 	}
-	//req->lr = ptr; /* tell that bytes up to <lr> have been read and processes */
+//	  else if (t->res_cr == RES_SILENT) {
+//	      return 0;
+//	  }
+
+	if (req->l >= BUFSIZE - MAXREWRITE) {
+	    /* buffer full : stop reading till we free some space */
+	    FD_CLR(t->cli_fd, StaticReadEvent);
+	    tv_eternity(&t->crexpire);
+	}
+
+	return t->cli_state != CL_STHEADERS;
     }
     else if (c == CL_STDATA) {
 	/* read or write error */
 	if (t->res_cw == RES_ERROR || t->res_cr == RES_ERROR) {
-	    FD_CLR(t->cli_fd, StaticReadEvent);
-	    FD_CLR(t->cli_fd, StaticWriteEvent);
 	    tv_eternity(&t->crexpire);
 	    tv_eternity(&t->cwexpire);
-	    close(t->cli_fd);
+	    fd_delete(t->cli_fd);
+	    //FD_CLR(t->cli_fd, StaticReadEvent);
+	    //FD_CLR(t->cli_fd, StaticWriteEvent);
+	    //close(t->cli_fd);
 	    t->cli_state = CL_STCLOSE;
 	    return 1;
 	}
 	/* read timeout, last read, or end of server write */
 	else if (t->res_cr == RES_NULL || s == SV_STSHUTW || s == SV_STCLOSE
 		 || tv_cmp2_ms(&t->crexpire, &now) <= 0) {
-
 	    FD_CLR(t->cli_fd, StaticReadEvent);
 	    //	    if (req->l == 0) /* nothing to write on the server side */
 	    //		FD_CLR(t->srv_fd, StaticWriteEvent);
@@ -1736,7 +2026,7 @@
 	}
 
 	if ((rep->l == 0) ||
-	    ((s == SV_STHEADERS) && (rep->w == rep->h))) {
+	    ((s == SV_STHEADERS) /* FIXME: this may be optimized && (rep->w == rep->h)*/)) {
 	    if (FD_ISSET(t->cli_fd, StaticWriteEvent)) {
 		FD_CLR(t->cli_fd, StaticWriteEvent); /* stop writing */
 		tv_eternity(&t->cwexpire);
@@ -1757,16 +2047,15 @@
 	if ((t->res_cw == RES_ERROR) ||
 	    ((s == SV_STSHUTR || s == SV_STCLOSE) && (rep->l == 0))
 	    || (tv_cmp2_ms(&t->crexpire, &now) <= 0)) {
-	    
-	    FD_CLR(t->cli_fd, StaticWriteEvent);
+	    //FD_CLR(t->cli_fd, StaticWriteEvent);
 	    tv_eternity(&t->cwexpire);
 	    fd_delete(t->cli_fd);
-	    close(t->cli_fd);
+	    //close(t->cli_fd);
 	    t->cli_state = CL_STCLOSE;
 	    return 1;
 	}
 	else if ((rep->l == 0) ||
-	    ((s == SV_STHEADERS) && (rep->w == rep->h))) {
+		 ((s == SV_STHEADERS) /* FIXME: this may be optimized && (rep->w == rep->h)*/)) {
 	    if (FD_ISSET(t->cli_fd, StaticWriteEvent)) {
 		FD_CLR(t->cli_fd, StaticWriteEvent); /* stop writing */
 		tv_eternity(&t->cwexpire);
@@ -1786,10 +2075,10 @@
     else if (c == CL_STSHUTW) {
 	if (t->res_cr == RES_ERROR || t->res_cr == RES_NULL || s == SV_STSHUTW ||
 	    s == SV_STCLOSE || tv_cmp2_ms(&t->cwexpire, &now) <= 0) {
-	    FD_CLR(t->cli_fd, StaticReadEvent);
+	    //FD_CLR(t->cli_fd, StaticReadEvent);
 	    tv_eternity(&t->crexpire);
 	    fd_delete(t->cli_fd);
-	    close(t->cli_fd);
+	    //close(t->cli_fd);
 	    t->cli_state = CL_STCLOSE;
 	    return 1;
 	}
@@ -1811,9 +2100,9 @@
 	return 0;
     }
     else { /* CL_STCLOSE: nothing to do */
-	if (mode & MODE_DEBUG) {
+	if ((mode & MODE_DEBUG) && !(mode & MODE_QUIET)) {
 	    int len;
-	    len = sprintf(trash, "clicls[%04x:%04x]\n", t->cli_fd, t->srv_fd);
+	    len = sprintf(trash, "clicls[%04x:%04x]\n", (unsigned short)t->cli_fd, (unsigned short)t->srv_fd);
 	    write(1, trash, len);
 	}
 	return 0;
@@ -1826,16 +2115,17 @@
  * manages the server FSM and its socket. It returns 1 if a state has changed
  * (and a resync may be needed), 0 else.
  */
-int process_srv(struct task *t) {
+int process_srv(struct session *t) {
     int s = t->srv_state;
     int c = t->cli_state;
     struct buffer *req = t->req;
     struct buffer *rep = t->rep;
 
-	    //fprintf(stderr,"process_srv: c=%d, s=%d, cr=%d, cw=%d, sr=%d, sw=%d\n", c, s,
-	    //FD_ISSET(t->cli_fd, StaticReadEvent), FD_ISSET(t->cli_fd, StaticWriteEvent),
-	    // FD_ISSET(t->srv_fd, StaticReadEvent), FD_ISSET(t->srv_fd, StaticWriteEvent)
-	    //);
+    //fprintf(stderr,"process_srv: c=%d, s=%d\n", c, s);
+    //fprintf(stderr,"process_srv: c=%d, s=%d, cr=%d, cw=%d, sr=%d, sw=%d\n", c, s,
+    //FD_ISSET(t->cli_fd, StaticReadEvent), FD_ISSET(t->cli_fd, StaticWriteEvent),
+    //FD_ISSET(t->srv_fd, StaticReadEvent), FD_ISSET(t->srv_fd, StaticWriteEvent)
+    //);
     if (s == SV_STIDLE) {
 	if (c == CL_STHEADERS)
 	    return 0;	/* stay in idle, waiting for data to reach the client side */
@@ -1847,13 +2137,18 @@
 	    return 1;
 	}
 	else { /* go to SV_STCONN */
-	    if (connect_server(t, 1) == 0) { /* initiate a connection to the server */
+	    if (connect_server(t) == 0) { /* initiate a connection to the server */
 		//fprintf(stderr,"0: c=%d, s=%d\n", c, s);
 		t->srv_state = SV_STCONN;
 	    }
 	    else { /* try again */
 		while (t->conn_retries-- > 0) {
-		    if (connect_server(t, !t->conn_redisp || (t->conn_retries > 0)) == 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->srv = NULL; /* it's left to the dispatcher to choose a server */
+		    }
+
+		    if (connect_server(t) == 0) {
 			t->srv_state = SV_STCONN;
 			break;
 		    }
@@ -1875,13 +2170,17 @@
 	else if (t->res_sw == RES_SILENT || t->res_sw == RES_ERROR) {
 	    //fprintf(stderr,"2: c=%d, s=%d\n", c, s);
 	    /* timeout,  connect error or first write error */
-	    FD_CLR(t->srv_fd, StaticWriteEvent);
+	    //FD_CLR(t->srv_fd, StaticWriteEvent);
 	    fd_delete(t->srv_fd);
-	    close(t->srv_fd);
+	    //close(t->srv_fd);
 	    t->conn_retries--;
-	    if (t->conn_retries >= 0 &&
-		connect_server(t, !t->conn_redisp || (t->conn_retries > 0)) == 0) {
-		return 0; /* no state changed */
+	    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->srv = NULL; /* it's left to the dispatcher to choose a server */
+		    }
+		    if (connect_server(t) == 0)
+			return 0; /* no state changed */
 	    }
 	    /* if conn_retries < 0 or other error, let's abort */
 	    tv_eternity(&t->cnexpire);
@@ -1906,20 +2205,204 @@
 	    }
 	    else
 		t->srv_state = SV_STHEADERS;
+	    tv_eternity(&t->cnexpire);
 	    return 1;
 	}
     }
     else if (s == SV_STHEADERS) { /* receiving server headers */
-	char *ptr;
-	int header_processed = 0;
+
+	/* now parse the partial (or complete) headers */
+	while (rep->lr < rep->r) { /* this loop only sees one header at each iteration */
+	    char *ptr;
+	    int delete_header;
+
+	    ptr = rep->lr;
+
+	    /* look for the end of the current header */
+	    while (ptr < rep->r && *ptr != '\n' && *ptr != '\r')
+		ptr++;
+	    
+	    if (ptr == rep->h) {
+		char newhdr[MAXREWRITE + 1];
+		int line, len;
+
+		/* 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)) {
+		    /* the server is known, it's not the one the client requested, we have to
+		     * insert a set-cookie here.
+		     */
+		    len = sprintf(newhdr, "Set-Cookie: %s=%s; path=/\r\n",
+				  t->proxy->cookie_name, t->srv->cookie);
+		    buffer_replace2(rep, rep->h, rep->h, newhdr, len);
+		}
+
+		/* headers to be added */
+		for (line = 0; line < t->proxy->nb_rspadd; line++) {
+		    len = sprintf(newhdr, "%s\r\n", t->proxy->rsp_add[line]);
+		    buffer_replace2(rep, rep->h, rep->h, newhdr, len);
+		}
+
+		t->srv_state = SV_STDATA;
+		break;
+	    }
+
+	    /* to get a complete header line, we need the ending \r\n, \n\r, \r or \n too */
+	    if (ptr > rep->r - 2) {
+		/* this is a partial header, let's wait for more to come */
+		rep->lr = ptr;
+		break;
+	    }
+
+	    //	    fprintf(stderr,"h=%p, ptr=%p, lr=%p, r=%p, *h=", rep->h, ptr, rep->lr, rep->r);
+	    //	    write(2, rep->h, ptr - rep->h);   fprintf(stderr,"\n");
+
+	    /* now we know that *ptr is either \r or \n,
+	     * and that there are at least 1 char after it.
+	     */
+	    if ((ptr[0] == ptr[1]) || (ptr[1] != '\r' && ptr[1] != '\n'))
+		rep->lr = ptr + 1; /* \r\r, \n\n, \r[^\n], \n[^\r] */
+	    else
+		rep->lr = ptr + 2; /* \r\n or \n\r */
+
+	    /*
+	     * now we know that we have a full header ; we can do whatever
+	     * we want with these pointers :
+	     *   rep->h  = beginning of header
+	     *   ptr     = end of header (first \r or \n)
+	     *   rep->lr = beginning of next line (next rep->h)
+	     *   rep->r  = end of data (not used at this stage)
+	     */
+
+	    delete_header = 0;
+
+	    if ((mode & MODE_DEBUG) && !(mode & MODE_QUIET)) {
+		int len, max;
+		len = sprintf(trash, "srvhdr[%04x:%04x]: ", (unsigned  short)t->cli_fd, (unsigned short)t->srv_fd);
+		max = ptr - rep->h;
+		UBOUND(max, sizeof(trash) - len - 1);
+		len += strlcpy(trash + len, rep->h, max + 1);
+		trash[len++] = '\n';
+		write(1, trash, len);
+	    }
+
+	    /* try headers regexps */
+	    if (t->proxy->nb_rspexp) {
+		struct proxy *p = t->proxy;
+		int 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;
+			}
+			break;
+		    }
+		}
+		*ptr = term; /* restore the string terminator */
+	    }
+	    
+	    /* check for server cookies */
+	    if (!delete_header && (t->proxy->options & PR_O_COOK_ANY) && (rep->r >= rep->h + 12) &&
+		(t->proxy->cookie_name != NULL)	&& (strncmp(rep->h, "Set-Cookie: ", 12) == 0)) {
+		char *p1, *p2, *p3, *p4;
+		
+		p1 = rep->h + 12; /* first char after 'Set-Cookie: ' */
+		
+		while (p1 < ptr) { /* in fact, we'll break after the first cookie */
+		    while (p1 < ptr && (isspace(*p1)))
+			p1++;
+		    
+		    if (p1 == ptr || *p1 == ';') /* end of cookie */
+			break;
+		    
+		    /* p1 is at the beginning of the cookie name */
+		    p2 = p1;
+		    
+		    while (p2 < ptr && *p2 != '=' && *p2 != ';')
+			p2++;
+		    
+		    if (p2 == ptr || *p2 == ';') /* next cookie */
+			break;
+		    
+		    p3 = p2 + 1; /* skips the '=' sign */
+		    if (p3 == ptr)
+			break;
+		    
+		    p4 = p3;
+		    while (p4 < ptr && !isspace(*p4) && *p4 != ';')
+			p4++;
+		    
+		    /* here, we have the cookie name between p1 and p2,
+		     * and its value between p3 and p4.
+		     * we can process it.
+		     */
+		    
+		    if ((p2 - p1 == strlen(t->proxy->cookie_name)) &&
+			(strncmp(p1, t->proxy->cookie_name, p2 - p1) == 0)) {
+			/* Cool... it's the right one */
+			
+			/* If the cookie is in insert mode on a known server, we'll delete
+			 * this occurrence because we'll insert another one later.
+			 * 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))) {
+			    /* this header must be deleted */
+			    delete_header = 1;
+			}
+			else if ((t->srv) && (t->proxy->options & PR_O_COOK_RW)) {
+			    /* replace bytes p3->p4 with the cookie name associated
+			     * with this server since we know it.
+			     */
+			    buffer_replace2(rep, p3, p4, t->srv->cookie, t->srv->cklen);
+			}
+			break;
+		    }
+		    else {
+			//	fprintf(stderr,"Ignoring unknown cookie : ");
+			//	write(2, p1, p2-p1);
+			//	fprintf(stderr," = ");
+			//	write(2, p3, p4-p3);
+			//	fprintf(stderr,"\n");
+		    }
+		    break; /* we don't want to loop again since there cannot be another cookie on the same line */
+		} /* we're now at the end of the cookie value */
+	    } /* end of cookie processing */
+
+	    /* let's look if we have to delete this header */
+	    if (delete_header) {
+		buffer_replace2(rep, rep->h, rep->lr, "", 0);
+	    }
+	    rep->h = rep->lr;
+	} /* while (rep->lr < rep->r) */
+
+	/* end of header processing (even if incomplete) */
+
+	if ((rep->l < BUFSIZE - MAXREWRITE) && ! FD_ISSET(t->srv_fd, StaticReadEvent)) {
+	    FD_SET(t->srv_fd, StaticReadEvent);
+	    if (t->proxy->srvtimeout)
+		tv_delayfrom(&t->srexpire, &now, t->proxy->srvtimeout);
+	    else
+		tv_eternity(&t->srexpire);
+	}
 
 	/* read or write error */
 	if (t->res_sw == RES_ERROR || t->res_sr == RES_ERROR) {
-	    FD_CLR(t->srv_fd, StaticReadEvent);
-	    FD_CLR(t->srv_fd, StaticWriteEvent);
 	    tv_eternity(&t->srexpire);
 	    tv_eternity(&t->swexpire);
-	    close(t->srv_fd);
+	    //FD_CLR(t->srv_fd, StaticReadEvent);
+	    //FD_CLR(t->srv_fd, StaticWriteEvent);
+	    //close(t->srv_fd);
+	    fd_delete(t->srv_fd);
 	    t->srv_state = SV_STCLOSE;
 	    return 1;
 	}
@@ -1966,95 +2449,23 @@
 	    }
 	}
 
-	/* now parse the partial (or complete) headers */
-
-	//fprintf(stderr,"rep->data=%p, rep->lr=%p, rep->r=%p, rep->l=%d\n", rep->data, rep->lr, rep->r, rep->l);
-	ptr = rep->lr;
-	rep->lr = rep->r;
-
-	//write(1,"rep=",4); write(1, ptr, 4); write(1,"\n",1);
-	//write(1,"hdr=",4); write(1, rep->h, 4); write(1,"\n",1);
-	while (ptr < rep->r) {
-	    /* look for the end of the current header */
-	    while (ptr < rep->r && *ptr != '\n' && *ptr != '\r')
-		ptr++;
-	    
-	    if (ptr < rep->r) {
-		//write(1,"ptr=",4); write(1, ptr, 4); write(1,"\n",1);
-		/* now we have one complete header between rep->h and ptr */
-		header_processed = 1;
-		if (ptr == rep->h) { /* empty line, end of headers */
-		    t->srv_state = SV_STDATA;
-		    //rep->lr = ptr; /* tell that bytes up to <lr> have been read and processes */
-		    return 1;
-		}
-		else {
-		    /* we have one standard header */
-		    if (mode & MODE_DEBUG) {
-			int len, max;
-			len = sprintf(trash, "srvhdr[%04x:%04x]: ", (unsigned  short)t->cli_fd, (unsigned short)t->srv_fd);
-			max = ptr - rep->h;
-			UBOUND(max, sizeof(trash) - len - 1);
-			len += strlcpy(trash + len, rep->h, max + 1);
-			trash[len++] = '\n';
-			write(1, trash, len);
-		    }
-
-		    if (t->proxy->nb_srvexp) { /* try headers regexps */
-			struct proxy *p = t->proxy;
-			int exp;
-			char term;
-
-			term = *ptr;
-			*ptr = '\0';
-			for (exp=0; exp < p->nb_srvexp; exp++) {
-			    if (regexec(p->srv_exp[exp].preg, rep->h, MAX_MATCH, pmatch, 0) == 0) {
-				int len = exp_replace(trash, rep->h, p->srv_exp[exp].replace, pmatch);
-				ptr += buffer_replace2(rep, rep->h, trash, len, ptr);
-				break;
-			    }
-			}
-			*ptr = term; /* restore the string terminator */
-		    }
-
-		    /* look for the beginning of the next header */
-		    if (ptr < rep->r) {
-			if (*ptr == '\n') {
-			    if ((++ptr < rep->r) && (*ptr == '\r'))
-				ptr++;
-			}
-			else if (*ptr == '\r') {
-			    if ((++ptr < rep->r) && (*ptr == '\n'))
-				ptr++;
-			}
-			rep->h = ptr;
-		    }
-		}
-		//// rep->lr = ptr;
-		//rep->lr = rep->h;
-	    }
-	}
-
-	if ((rep->l < BUFSIZE - MAXREWRITE) && ! FD_ISSET(t->srv_fd, StaticReadEvent)) {
-	    FD_SET(t->srv_fd, StaticReadEvent);
-	    if (t->proxy->srvtimeout)
-		tv_delayfrom(&t->srexpire, &now, t->proxy->srvtimeout);
-	    else
-		tv_eternity(&t->srexpire);
-	}
-
-	/* be nice with the client side which would like to send a complete header */
-	return header_processed;
-	//return 0;
+	/* be nice with the client side which would like to send a complete header
+	 * FIXME: COMPLETELY BUGGY !!! not all headers may be processed because the client
+	 * would read all remaining data at once ! The client should not write past rep->lr
+	 * when the server is in header state.
+	 */
+	//return header_processed;
+	return t->srv_state != SV_STHEADERS;
     }
     else if (s == SV_STDATA) {
 	/* read or write error */
 	if (t->res_sw == RES_ERROR || t->res_sr == RES_ERROR) {
-	    FD_CLR(t->srv_fd, StaticReadEvent);
-	    FD_CLR(t->srv_fd, StaticWriteEvent);
 	    tv_eternity(&t->srexpire);
 	    tv_eternity(&t->swexpire);
-	    close(t->srv_fd);
+	    //FD_CLR(t->srv_fd, StaticReadEvent);
+	    //FD_CLR(t->srv_fd, StaticWriteEvent);
+	    //close(t->srv_fd);
+	    fd_delete(t->srv_fd);
 	    t->srv_state = SV_STCLOSE;
 	    return 1;
 	}
@@ -2116,11 +2527,10 @@
 	if ((t->res_sw == RES_ERROR) ||
 	    ((c == CL_STSHUTR || c == CL_STCLOSE) && (req->l == 0)) ||
 	    (tv_cmp2_ms(&t->swexpire, &now) <= 0)) {
-
-	    FD_CLR(t->srv_fd, StaticWriteEvent);
+	    //FD_CLR(t->srv_fd, StaticWriteEvent);
 	    tv_eternity(&t->swexpire);
 	    fd_delete(t->srv_fd);
-	    close(t->srv_fd);
+	    //close(t->srv_fd);
 	    t->srv_state = SV_STCLOSE;
 	    return 1;
 	}
@@ -2145,11 +2555,10 @@
 	if (t->res_sr == RES_ERROR || t->res_sr == RES_NULL ||
 	    c == CL_STSHUTW || c == CL_STCLOSE ||
 	    tv_cmp2_ms(&t->srexpire, &now) <= 0) {
-
-	    FD_CLR(t->srv_fd, StaticReadEvent);
+	    //FD_CLR(t->srv_fd, StaticReadEvent);
 	    tv_eternity(&t->srexpire);
 	    fd_delete(t->srv_fd);
-	    close(t->srv_fd);
+	    //close(t->srv_fd);
 	    t->srv_state = SV_STCLOSE;
 	    return 1;
 	}
@@ -2171,9 +2580,9 @@
 	return 0;
     }
     else { /* SV_STCLOSE : nothing to do */
-	if (mode & MODE_DEBUG) {
+	if ((mode & MODE_DEBUG) && !(mode & MODE_QUIET)) {
 	    int len;
-	    len = sprintf(trash, "srvcls[%04x:%04x]\n", t->cli_fd, t->srv_fd);
+	    len = sprintf(trash, "srvcls[%04x:%04x]\n", (unsigned short)t->cli_fd, (unsigned short)t->srv_fd);
 	    write(1, trash, len);
 	}
 	return 0;
@@ -2182,42 +2591,164 @@
 }
 
 
-/*
- * puts a task back to the wait queue in a clean state, or
- * cleans up its resources if it must be deleted.
+/* Processes the client and server jobs of a session task, then
+ * puts it back to the wait queue in a clean state, or
+ * cleans up its resources if it must be deleted. Returns
+ * the time the task accepts to wait, or -1 for infinity
  */
-void process_task(struct task *t) {
+int process_session(struct task *t) {
+    struct session *s = t->context;
+    int fsm_resync = 0;
 
-    if (t->cli_state != CL_STCLOSE || t->srv_state != SV_STCLOSE) {
+    do {
+	fsm_resync = 0;
+	//fprintf(stderr,"before_cli:cli=%d, srv=%d\n", t->cli_state, t->srv_state);
+	fsm_resync |= process_cli(s);
+	//fprintf(stderr,"cli/srv:cli=%d, srv=%d\n", t->cli_state, t->srv_state);
+	fsm_resync |= process_srv(s);
+	//fprintf(stderr,"after_srv:cli=%d, srv=%d\n", t->cli_state, t->srv_state);
+    } while (fsm_resync);
+
+    if (s->cli_state != CL_STCLOSE || s->srv_state != SV_STCLOSE) {
 	struct timeval min1, min2;
-	t->res_cw = t->res_cr = t->res_sw = t->res_sr = RES_SILENT;
+	s->res_cw = s->res_cr = s->res_sw = s->res_sr = RES_SILENT;
 
-	tv_min(&min1, &t->crexpire, &t->cwexpire);
-	tv_min(&min2, &t->srexpire, &t->swexpire);
-	tv_min(&min1, &min1, &t->cnexpire);
+	tv_min(&min1, &s->crexpire, &s->cwexpire);
+	tv_min(&min2, &s->srexpire, &s->swexpire);
+	tv_min(&min1, &min1, &s->cnexpire);
 	tv_min(&t->expire, &min1, &min2);
 
 	/* restore t to its place in the task list */
-	task_queue(LIST_HEAD(t->proxy->task), t);
+	task_queue(t);
 
-	return; /* nothing more to do */
+	return tv_remain(&now, &t->expire); /* nothing more to do */
     }
 
-    t->proxy->nbconn--;
+    s->proxy->nbconn--;
     actconn--;
     
-    if (mode & MODE_DEBUG) {
+    if ((mode & MODE_DEBUG) && !(mode & MODE_QUIET)) {
 	int len;
-	len = sprintf(trash, "closed[%04x:%04x]\n", t->cli_fd, t->srv_fd);
+	len = sprintf(trash, "closed[%04x:%04x]\n", (unsigned short)s->cli_fd, (unsigned short)s->srv_fd);
 	write(1, trash, len);
     }
 
     /* the task MUST not be in the run queue anymore */
     task_delete(t);
+    session_free(s);
     task_free(t);
+    return -1; /* rest in peace for eternity */
+}
+
+
+
+/*
+ * manages a server health-check. Returns
+ * the time the task accepts to wait, or -1 for infinity.
+ */
+int process_chk(struct task *t) {
+    struct server *s = t->context;
+    int fd = s->curfd;
+    int one = 1;
+
+    //fprintf(stderr, "process_chk: 1\n");
+
+    if (fd < 0) {   /* no check currently running */
+	//fprintf(stderr, "process_chk: 2\n");
+	if (tv_cmp2_ms(&t->expire, &now) > 0) { /* not good time yet */
+	    task_queue(t);	/* restore t to its place in the task list */
+	    return tv_remain(&now, &t->expire);
+	}
+	
+	/* we'll initiate a new check */
+	s->result = 0; /* no result yet */
+	if ((fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) != -1) {
+	    if ((fd < cfg_maxsock) &&
+		(fcntl(fd, F_SETFL, O_NONBLOCK) != -1) &&
+		(setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char *) &one, sizeof(one)) != -1)) {
+		//fprintf(stderr, "process_chk: 3\n");
+
+		if ((connect(fd, (struct sockaddr *)&s->addr, sizeof(s->addr)) != -1) || (errno == EINPROGRESS)) {
+		    /* OK, connection in progress or established */
+
+		    //fprintf(stderr, "process_chk: 4\n");
+
+		    s->curfd = fd; /* that's how we know a test is in progress ;-) */
+		    fdtab[fd].owner = t;
+		    fdtab[fd].read  = NULL;
+		    fdtab[fd].write = &event_srv_hck;
+		    fdtab[fd].state = FD_STCONN; /* connection in progress */
+		    FD_SET(fd, StaticWriteEvent);  /* for connect status */
+		    fd_insert(fd);
+		    tv_delayfrom(&t->expire, &now, CHK_CONNTIME);
+		    task_queue(t);	/* restore t to its place in the task list */
+		    return tv_remain(&now, &t->expire);
+		}
+		else if (errno != EALREADY && errno != EISCONN && errno != EAGAIN) {
+		    s->result = -1;    /* a real error */
+		}
+	    }
+	    //fprintf(stderr, "process_chk: 5\n");
+	    close(fd);
+	}
+
+	if (!s->result) { /* nothing done */
+	    //fprintf(stderr, "process_chk: 6\n");
+	    tv_delayfrom(&t->expire, &now, CHK_INTERVAL);
+	    task_queue(t);	/* restore t to its place in the task list */
+	    return tv_remain(&now, &t->expire);
+	}
+
+	/* here, we have seen a failure */
+	if (s->health > FALLTIME)
+	    s->health--; /* still good */
+	else {
+	    s->health = 0; /* failure */
+	    s->state &= ~SRV_RUNNING;
+	}
+
+	//fprintf(stderr, "process_chk: 7\n");
+	tv_delayfrom(&t->expire, &now, CHK_CONNTIME);
+    }
+    else {
+	//fprintf(stderr, "process_chk: 8\n");
+	/* there was a test running */
+	if (s->result > 0) { /* good server detected */
+	    //fprintf(stderr, "process_chk: 9\n");
+	    s->health++; /* was bad, stays for a while */
+	    if (s->health >= FALLTIME) {
+		s->health = FALLTIME + RISETIME -1; /* OK now */
+		s->state |= SRV_RUNNING;
+	    }
+	    s->curfd = -1;
+	    FD_CLR(fd, StaticWriteEvent);
+	    fd_delete(fd);
+	    tv_delayfrom(&t->expire, &now, CHK_INTERVAL);
+	}
+	else if (s->result < 0 || tv_cmp2_ms(&t->expire, &now) <= 0) {
+	    //fprintf(stderr, "process_chk: 10\n");
+	    /* failure or timeout detected */
+	    if (s->health > FALLTIME)
+		s->health--; /* still good */
+	    else {
+		s->health = 0; /* failure */
+		s->state &= ~SRV_RUNNING;
+	    }
+	    s->curfd = -1;
+	    FD_CLR(fd, StaticWriteEvent);
+	    fd_delete(fd);
+	    tv_delayfrom(&t->expire, &now, CHK_INTERVAL);
+	}
+	/* if result is 0 and there's no timeout, we have to wait again */
+    }
+    //fprintf(stderr, "process_chk: 11\n");
+    s->result = 0;
+    task_queue(t);	/* restore t to its place in the task list */
+    return tv_remain(&now, &t->expire);
 }
 
 
+
 #if STATTIME > 0
 int stats(void);
 #endif
@@ -2228,21 +2759,58 @@
 
 void select_loop() {
   int next_time;
-#if STATTIME > 0
   int time2;
-#endif
   int status;
   int fd,i;
   struct timeval delta;
   int readnotnull, writenotnull;
-  struct proxy *p;
+  struct task *t, *tnext;
 
-  /* stop when there's no connection left and we don't allow them anymore */
-  while (actconn || listeners > 0) {
-      next_time = -1;
-      tv_now(&now);
+  tv_now(&now);
+
+  while (1) {
+      next_time = -1; /* set the timer to wait eternally first */
 
-      maintain_proxies();
+      /* look for expired tasks and add them to the run queue.
+       */
+      tnext = ((struct task *)LIST_HEAD(wait_queue))->next;
+      while ((t = tnext) != LIST_HEAD(wait_queue)) { /* we haven't looped ? */
+	  tnext = t->next;
+
+	  /* wakeup expired entries. It doesn't matter if they are
+	   * already running because of a previous event
+	   */
+	  if (tv_cmp2_ms(&t->expire, &now) <= 0) {
+	      task_wakeup(&rq, t);
+	  }
+	  else {
+	      break;
+	  }
+      }
+
+      /* process each task in the run queue now. Each task may be deleted
+       * since we only use tnext.
+       */
+      tnext = rq;
+      while ((t = tnext) != NULL) {
+	  int temp_time;
+	  
+	  tnext = t->rqnext;
+	  task_sleep(&rq, t);
+	  
+	  temp_time = t->process(t);
+	  next_time = MINTIME(temp_time, next_time);
+      }
+
+
+      /* maintain all proxies in a consistent state. This should quickly become a task */
+      time2 = maintain_proxies();
+      next_time = MINTIME(time2, next_time);
+
+      /* stop when there's no connection left and we don't allow them anymore */
+      if (!actconn && listeners == 0)
+	  break;
+
 	  
 #if STATTIME > 0
       time2 = stats();
@@ -2250,17 +2818,22 @@
       next_time = MINTIME(time2, next_time);
 #endif
 
-      if (next_time >= 0) {
+      if (next_time > 0) {  /* FIXME */
 	  /* Convert to timeval */
-	  delta.tv_sec=next_time/1000; 
-	  delta.tv_usec=(next_time%1000)*1000;
+	  /* to avoid eventual select loops due to timer precision */
+	  next_time += SCHEDULER_RESOLUTION;
+	  delta.tv_sec  = next_time / 1000; 
+	  delta.tv_usec = (next_time % 1000) * 1000;
+      }
+      else if (next_time == 0) { /* allow select to return immediately when needed */
+	  delta.tv_sec = delta.tv_usec = 0;
       }
 
 
       /* let's restore fdset state */
 
       readnotnull = 0; writenotnull = 0;
-      for (i = 0; i < (cfg_maxsock + 3 + FD_SETSIZE - 1)/(8*sizeof(int)); i++) {
+      for (i = 0; i < (cfg_maxsock + FD_SETSIZE - 1)/(8*sizeof(int)); i++) {
 	  readnotnull |= (*(((int*)ReadEvent)+i) = *(((int*)StaticReadEvent)+i)) != 0;
 	  writenotnull |= (*(((int*)WriteEvent)+i) = *(((int*)StaticWriteEvent)+i)) != 0;
       }
@@ -2280,7 +2853,12 @@
 		    NULL,
 		    (next_time >= 0) ? &delta : NULL);
       
+      /* this is an experiment on the separation of the select work */
+      // status  = (readnotnull  ? select(maxfd, ReadEvent, NULL, NULL, (next_time >= 0) ? &delta : NULL) : 0);
+      // status |= (writenotnull ? select(maxfd, NULL, WriteEvent, NULL, (next_time >= 0) ? &delta : NULL) : 0);
+      
       tv_now(&now);
+
       if (status > 0) { /* must proceed with events */
 
 	  int fds;
@@ -2290,63 +2868,25 @@
 	      if ((((int *)(ReadEvent))[fds] | ((int *)(WriteEvent))[fds]) != 0)
 		  for (count = 1<<INTBITS, fd = fds << INTBITS; count && fd < maxfd; count--, fd++) {
 		      
+		      /* if we specify read first, the accepts and zero reads will be
+		       * seen first. Moreover, system buffers will be flushed faster.
+		       */
 		      if (fdtab[fd].state == FD_STCLOSE)
 			  continue;
-
-		      if (FD_ISSET(fd, WriteEvent))
-			  fdtab[fd].write(fd);
 		      
 		      if (FD_ISSET(fd, ReadEvent))
 			  fdtab[fd].read(fd);
+
+		      if (fdtab[fd].state == FD_STCLOSE)
+			  continue;
+
+		      if (FD_ISSET(fd, WriteEvent))
+			  fdtab[fd].write(fd);
 		  }
       }
       else {
 	  //	  fprintf(stderr,"select returned %d, maxfd=%d\n", status, maxfd);
       }
-
-      for (p = proxy; p; p = p->next) {
-	  struct task *t, *tnext;
-	  tnext = ((struct task *)LIST_HEAD(p->task))->next;
-	  while ((t = tnext) != LIST_HEAD(p->task)) { /* we haven't looped ? */
-	      tnext = t->next;
-
-	      /* wakeup expired entries. It doesn't matter if they are
-	       * already running because of a previous event
-	       */
-	      if (tv_cmp2_ms(&t->expire, &now) <= 0) {
-		  //		  fprintf(stderr,"WQ: expiring task %p : rq=%p\n", t, p->rq);
-		  task_wakeup(p, t);
-	      }
-	      else {
-		  //		  fprintf(stderr,"WQ: ignoring task %p : rq=%p\n", t, p->rq);
-		  break;
-	      }
-	  }
-
-	  /* process each task in the run queue now. Each task may be deleted
-	   * since we only use tnext.
-	   */
-	  tnext = p->rq;
-	  while ((t = tnext) != NULL) {
-	      int fsm_resync = 0;
-
-	      tnext = t->rqnext;
-	      task_sleep(p, t);
-
-	      do {
-		  fsm_resync = 0;
-		  //fprintf(stderr,"before_cli:cli=%d, srv=%d\n", t->cli_state, t->srv_state);
-		  fsm_resync |= process_cli(t);
-		  //fprintf(stderr,"cli/srv:cli=%d, srv=%d\n", t->cli_state, t->srv_state);
-		  fsm_resync |= process_srv(t);
-		  //fprintf(stderr,"after_srv:cli=%d, srv=%d\n", t->cli_state, t->srv_state);
-	      } while (fsm_resync);
-
-	      // task_queue(LIST_HEAD(p->task), t);  /* restore t to its place in the task list */
-	      // it has been moved to process_task which was more logical.
-	      process_task(t);
-	  }
-      }
   }
 }
 
@@ -2370,10 +2910,10 @@
 	
 	if (mode & MODE_STATS) {	
 		if ((lines++ % 16 == 0) && !(mode & MODE_LOG))
-		    fprintf(stderr,
+		    qfprintf(stderr,
 			    "\n active   total  tsknew tskgood tskleft tskrght tsknsch tsklsch tskrsch\n");
 		if (lines>1) {
-			fprintf(stderr,"%07d %07d %07d %07d %07d %07d %07d %07d %07d\n",
+			qfprintf(stderr,"%07d %07d %07d %07d %07d %07d %07d %07d %07d\n",
 				actconn, totalconn,
 				stats_tsk_new, stats_tsk_good,
 				stats_tsk_left, stats_tsk_right,
@@ -2394,27 +2934,15 @@
 /*
  * this function enables proxies when there are enough free sessions,
  * or stops them when the table is full. It is designed to be called from the
- * select_loop().
+ * select_loop(). It returns the time left before next expiration event
+ * during stop time, -1 otherwise.
  */
 static int maintain_proxies(void) {
     struct proxy *p;
+    int tleft; /* time left */
 
     p = proxy;
-
-    if (stopping) {
-	while (p) {
-	    if (p->state != PR_STDISABLED) {
-		if (stopping && (tv_remain(&now, &p->stop_time) == 0)) {
-		    FD_CLR(p->listen_fd, StaticReadEvent);
-		    close(p->listen_fd);
-		    p->state = PR_STDISABLED;
-		    listeners--;
-		}
-	    }
-	    p = p->next;
-	}
-	return -1;
-    }
+    tleft = -1; /* infinite time */
 
     /* if there are enough free sessions, we'll activate proxies */
     if (actconn < cfg_maxconn) {
@@ -2444,7 +2972,27 @@
 	}
     }
 
-    return -1;
+    if (stopping) {
+	p = proxy;
+	while (p) {
+	    if (p->state != PR_STDISABLED) {
+		int t;
+		t = tv_remain(&now, &p->stop_time);
+		if (t == 0) {
+		    //FD_CLR(p->listen_fd, StaticReadEvent);
+		    //close(p->listen_fd);
+		    fd_delete(p->listen_fd);
+		    p->state = PR_STDISABLED;
+		    listeners--;
+		}
+		else {
+		    tleft = MINTIME(t, tleft);
+		}
+	    }
+	    p = p->next;
+	}
+    }
+    return tleft;
 }
 
 /*
@@ -2456,6 +3004,7 @@
 
     stopping = 1;
     p = proxy;
+    tv_now(&now); /* else, the old time before select will be used */
     while (p) {
 	if (p->state != PR_STDISABLED)
 	    tv_delayfrom(&p->stop_time, &now, p->grace);
@@ -2473,26 +3022,25 @@
 
 
 void dump(int sig) {
-    struct proxy *p;
+    struct task *t, *tnext;
+    struct session *s;
 
-    for (p = proxy; p; p = p->next) {
-	struct task *t, *tnext;
-	tnext = ((struct task *)LIST_HEAD(p->task))->next;
-	while ((t = tnext) != LIST_HEAD(p->task)) { /* we haven't looped ? */
-	    tnext = t->next;
-	    fprintf(stderr,"[dump] wq: task %p, still %ld ms, "
-		    "cli=%d, srv=%d, cr=%d, cw=%d, sr=%d, sw=%d, "
-		    "req=%d, rep=%d, clifd=%d\n",
-		    t, tv_remain(&now, &t->expire),
-		    t->cli_state,
-		    t->srv_state,
-		    FD_ISSET(t->cli_fd, StaticReadEvent),
-		    FD_ISSET(t->cli_fd, StaticWriteEvent),
-		    FD_ISSET(t->srv_fd, StaticReadEvent),
-		    FD_ISSET(t->srv_fd, StaticWriteEvent),
-		    t->req->l, t->rep?t->rep->l:0, t->cli_fd
-		    );
-	}
+    tnext = ((struct task *)LIST_HEAD(wait_queue))->next;
+    while ((t = tnext) != LIST_HEAD(wait_queue)) { /* we haven't looped ? */
+	tnext = t->next;
+	s = t->context;
+	qfprintf(stderr,"[dump] wq: task %p, still %ld ms, "
+		 "cli=%d, srv=%d, cr=%d, cw=%d, sr=%d, sw=%d, "
+		 "req=%d, rep=%d, clifd=%d\n",
+		 s, tv_remain(&now, &t->expire),
+		 s->cli_state,
+		 s->srv_state,
+		 FD_ISSET(s->cli_fd, StaticReadEvent),
+		 FD_ISSET(s->cli_fd, StaticWriteEvent),
+		 FD_ISSET(s->srv_fd, StaticReadEvent),
+		 FD_ISSET(s->srv_fd, StaticWriteEvent),
+		 s->req->l, s->rep?s->rep->l:0, s->cli_fd
+		 );
     }
 }
 
@@ -2505,8 +3053,8 @@
     char *line;
     FILE *f;
     int linenum = 0;
-    char *cmd;
-    char *args[10];
+    char *end;
+    char *args[MAX_LINE_ARGS];
     int arg;
     int cfgerr = 0;
 
@@ -2518,44 +3066,77 @@
 
     while (fgets(line = thisline, sizeof(thisline), f) != NULL) {
 	linenum++;
-	/* skips leading spaces */
-	while (isspace(*line))
-	    line++;
 
-	/* cleans up line contents */
-	cmd = line;
-	while (*cmd) {
-	    if (*cmd == '#' || *cmd == ';' || *cmd == '\n' || *cmd == '\r')
-		*cmd = 0; /* end of string, end of loop */
-	    else
-		cmd++;
-	}
+	end = line + strlen(line);
 
-	if (*line == 0)
-	    continue;
+	/* skip leading spaces */
+	while (isspace(*line))
+	    line++;
 	
-	/* fills args[0..9] with the line contents */
-	for (arg=0; arg<9; arg++) {
-	    int escaped = 0;
+	arg = 0;
+	args[arg] = line;
 
-	    args[arg] = line;
-	    while (*line && (escaped || !isspace(*line))) {
-	        if (!escaped) {
-		    if (*line == '\\')
-			escaped = 1; 
+	while (*line && arg < MAX_LINE_ARGS) {
+	    /* first, we'll replace \\, \<space>, \#, \r, \n, \t, \xXX with their
+	     * C equivalent value. Other combinations left unchanged (eg: \1).
+	     */
+	    if (*line == '\\') {
+		int skip = 0;
+		if (line[1] == ' ' || line[1] == '\\' || line[1] == '#') {
+		    *line = line[1];
+		    skip = 1;
 		}
-		else
-		    escaped = 0;
+		else if (line[1] == 'r') {
+		    *line = '\r';
+		    skip = 1;
+		} 
+		else if (line[1] == 'n') {
+		    *line = '\n';
+		    skip = 1;
+		}
+		else if (line[1] == 't') {
+		    *line = '\t';
+		    skip = 1;
+		}
+		else if (line[1] == 'x' && (line + 3 < end )) {
+		    unsigned char hex1, hex2;
+		    hex1 = toupper(line[2]) - '0'; hex2 = toupper(line[3]) - '0';
+		    if (hex1 > 9) hex1 -= 'A' - '9' - 1;
+		    if (hex2 > 9) hex2 -= 'A' - '9' - 1;
+		    *line = (hex1<<4) + hex2;
+		    skip = 3;
+		} 
+		if (skip) {
+		    memmove(line + 1, line + 1 + skip, end - (line + skip + 1));
+		    end -= skip;
+		}
 		line++;
 	    }
-
-	    if (*line) {
-		*(line++) = 0;
-		while (isspace(*line))
+	    else {
+		if (*line == '#' || *line == '\n' || *line == '\r')
+		    *line = 0; /* end of string, end of loop */
+		else
 		    line++;
+		
+		/* a non-escaped space is an argument separator */
+		if (isspace(*line)) {
+		    *line++ = 0;
+		    while (isspace(*line))
+			line++;
+		    args[++arg] = line;
+		}
 	    }
 	}
 
+	/* empty line */
+	if (!**args)
+	    continue;
+
+	/* zero out remaining args */
+	while (++arg < MAX_LINE_ARGS) {
+	    args[arg] = line;
+	}
+
 	if (!strcmp(args[0], "listen")) {  /* new proxy */
 	    if (strchr(args[2], ':') == NULL) {
 		Alert("parsing [%s:%d] : <listen> expects <id> and <addr:port> as arguments.\n",
@@ -2573,12 +3154,10 @@
 	    curproxy->id = strdup(args[1]);
 	    curproxy->listen_addr = *str2sa(args[2]);
 	    curproxy->state = PR_STNEW;
-	    curproxy->task.prev = curproxy->task.next = LIST_HEAD(curproxy->task);
-	    curproxy->rq = NULL;
 	    /* set default values */
 	    curproxy->maxconn = cfg_maxpconn;
 	    curproxy->conn_retries = CONN_RETRIES;
-	    curproxy->conn_redisp = 0;
+	    curproxy->options = 0;
 	    curproxy->clitimeout = curproxy->contimeout = curproxy->srvtimeout = 0;
 	    curproxy->mode = PR_MODE_TCP;
 	    curproxy->logfac1 = curproxy->logfac2 = -1; /* log disabled */
@@ -2603,6 +3182,7 @@
 	    curproxy->state = PR_STDISABLED;
 	}
 	else if (!strcmp(args[0], "cookie")) {  /* cookie name */
+	    int cur_arg;
 	    if (curproxy->cookie_name != NULL) {
 		Alert("parsing [%s:%d] : cookie name already specified. Continuing.\n",
 		      file, linenum);
@@ -2615,6 +3195,30 @@
 		return -1;
 	    }
 	    curproxy->cookie_name = strdup(args[1]);
+
+	    cur_arg = 2;
+	    while (*(args[cur_arg])) {
+		if (!strcmp(args[cur_arg], "rewrite")) {
+		    curproxy->options |= PR_O_COOK_RW;
+		}
+		else if (!strcmp(args[cur_arg], "indirect")) {
+		    curproxy->options |= PR_O_COOK_IND;
+		}
+		else if (!strcmp(args[cur_arg], "insert")) {
+		    curproxy->options |= PR_O_COOK_INS;
+		}
+		else {
+		    Alert("parsing [%s:%d] : <cookie> supports 'rewrite', 'insert' and 'indirect' options.\n",
+			  file, linenum);
+		    return -1;
+		}
+		cur_arg++;
+	    }
+	    if ((curproxy->options & (PR_O_COOK_RW|PR_O_COOK_IND)) == (PR_O_COOK_RW|PR_O_COOK_IND)) {
+		Alert("parsing [%s:%d] : <cookie> 'rewrite' and 'indirect' mode are incompatibles.\n",
+		      file, linenum);
+		return -1;
+	    }
 	}
 	else if (!strcmp(args[0], "contimeout")) {  /* connect timeout */
 	    if (curproxy->contimeout != 0) {
@@ -2663,9 +3267,16 @@
 	    }
 	    curproxy->conn_retries = atol(args[1]);
 	}
-	else if (!strcmp(args[0], "redisp")) {  /* enable reconnections to dispatch */
-	    curproxy->conn_redisp = 1;
+	else if (!strcmp(args[0], "redispatch") || !strcmp(args[0], "redisp")) {
+	    /* enable reconnections to dispatch */
+	    curproxy->options |= PR_O_REDISP;
 	}
+#ifdef TRANSPARENT
+	else if (!strcmp(args[0], "transparent")) {
+	    /* enable transparent proxy connections */
+	    curproxy->options |= PR_O_TRANSP;
+	}
+#endif
 	else if (!strcmp(args[0], "maxconn")) {  /* maxconn */
 	    if (*(args[1]) == 0) {
 		Alert("parsing [%s:%d] : <maxconn> expects an integer argument.\n",
@@ -2690,21 +3301,73 @@
 	    }
 	    curproxy->dispatch_addr = *str2sa(args[1]);
 	}
+	else if (!strcmp(args[0], "balance")) {  /* set balancing with optionnal algorithm */
+	    if (*(args[1])) {
+		if (!strcmp(args[1], "roundrobin")) {
+		    curproxy->options |= PR_O_BALANCE_RR;
+		}
+		else {
+		    Alert("parsing [%s:%d] : <balance> supports 'roundrobin' options.\n",
+			  file, linenum);
+		    return -1;
+		}
+	    }
+	    else /* if no option is set, use round-robin by default */
+		curproxy->options |= PR_O_BALANCE_RR;
+	}
 	else if (!strcmp(args[0], "server")) {  /* server address */
+	    int cur_arg;
+
 	    if (strchr(args[2], ':') == NULL) {
 		Alert("parsing [%s:%d] : <server> expects <name> and <addr:port> as arguments.\n",
 		      file, linenum);
 		return -1;
 	    }
-	    if ((newsrv = (struct server *)calloc(1, sizeof(struct server)))
-		== NULL) {
-		Alert("parsing [%s:%d] : out of memory\n", file, linenum);
+	    if ((newsrv = (struct server *)calloc(1, sizeof(struct server))) == NULL) {
+		Alert("parsing [%s:%d] : out of memory.\n", file, linenum);
 		exit(1);
 	    }
 	    newsrv->next = curproxy->srv;
 	    curproxy->srv = newsrv;
 	    newsrv->id = strdup(args[1]);
 	    newsrv->addr = *str2sa(args[2]);
+	    newsrv->state = SRV_RUNNING; /* early server setup */
+	    newsrv->health = FALLTIME; /* up, but will fall down at first failure */
+	    newsrv->curfd = -1; /* no health-check in progress */
+	    cur_arg = 3;
+	    while (*args[cur_arg]) {
+		if (!strcmp(args[cur_arg], "cookie")) {
+		    newsrv->cookie = strdup(args[cur_arg + 1]);
+		    newsrv->cklen = strlen(args[cur_arg + 1]);
+		    cur_arg += 2;
+		}
+		else if (!strcmp(args[cur_arg], "check")) {
+		    struct task *t;
+
+		    if ((t = pool_alloc(task)) == NULL) { /* disable this proxy for a while */
+			Alert("parsing [%s:%d] : out of memory.\n", file, linenum);
+			return -1;
+		    }
+
+		    t->next = t->prev = t->rqnext = NULL; /* task not in run queue yet */
+		    t->wq = LIST_HEAD(wait_queue); /* but already has a wait queue assigned */
+		    t->state = TASK_IDLE;
+		    t->process = process_chk;
+		    t->context = newsrv;
+
+		    tv_delayfrom(&t->expire, &now, CHK_INTERVAL); /* check this every ms */
+		    task_queue(t);
+		    task_wakeup(&rq, t);
+
+		    cur_arg += 1;
+		}
+		else {
+		    Alert("parsing [%s:%d] : server %s only supports options 'cookie' and 'check'.\n",
+			  file, linenum, newsrv->id);
+		    return -1;
+		}
+	    }
+	    curproxy->nbservers++;
 	}
 	else if (!strcmp(args[0], "log")) {  /* syslog server address */
 	    struct sockaddr_in *sa;
@@ -2743,16 +3406,16 @@
 	    }
 
 	}
-	else if (!strcmp(args[0], "cliexp")) {  /* client regex */
+	else if (!strcmp(args[0], "cliexp") || !strcmp(args[0], "reqrep")) {  /* replace request header from a regex */
 	    regex_t *preg;
-	    if (curproxy->nb_cliexp >= MAX_REGEXP) {
-		Alert("parsing [%s:%d] : too many client expressions. Continuing.\n",
+	    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] : <cliexp> expects <search> and <replace> as arguments.\n",
+		Alert("parsing [%s:%d] : <reqrep> expects <search> and <replace> as arguments.\n",
 		      file, linenum);
 		return -1;
 	    }
@@ -2762,20 +3425,58 @@
 		Alert("parsing [%s:%d] : bad regular expression <%s>.\n", file, linenum, args[1]);
 		return -1;
 	    }
-	    curproxy->cli_exp[curproxy->nb_cliexp].preg = preg;
-	    curproxy->cli_exp[curproxy->nb_cliexp].replace = strdup(args[2]);
-	    curproxy->nb_cliexp++;
+	    curproxy->req_exp[curproxy->nb_reqexp].preg = preg;
+	    curproxy->req_exp[curproxy->nb_reqexp].replace = strdup(args[2]);
+	    curproxy->nb_reqexp++;
 	}
-	else if (!strcmp(args[0], "srvexp")) {  /* server regex */
+	else if (!strcmp(args[0], "reqdel")) {  /* delete request header from a regex */
 	    regex_t *preg;
-	    if (curproxy->nb_srvexp >= MAX_REGEXP) {
+	    if (curproxy->nb_reqexp >= MAX_REGEXP) {
+		Alert("parsing [%s:%d] : too many request expressions. Continuing.\n",
+		      file, linenum);
+		continue;
+	    }
+
+	    if (*(args[1]) == 0) {
+		Alert("parsing [%s:%d] : <reqdel> expects <search> 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;
+	    }
+	    curproxy->req_exp[curproxy->nb_reqexp].preg = preg;
+	    curproxy->req_exp[curproxy->nb_reqexp].replace = NULL; /* means it must be deleted */
+	    curproxy->nb_reqexp++;
+	}
+	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",
+		      file, linenum);
+		continue;
+	    }
+
+	    if (*(args[1]) == 0) {
+		Alert("parsing [%s:%d] : <reqadd> expects <header> as an argument.\n",
+		      file, linenum);
+		return -1;
+	    }
+
+	    curproxy->req_add[curproxy->nb_reqadd++] = strdup(args[1]);
+	}
+	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] : <srvexp> expects <search> and <replace> as arguments.\n",
+		Alert("parsing [%s:%d] : <rsprep> expects <search> and <replace> as arguments.\n",
 		      file, linenum);
 		return -1;
 	    }
@@ -2786,9 +3487,48 @@
 		return -1;
 	    }
 	    //	    fprintf(stderr,"before=<%s> after=<%s>\n", args[1], args[2]);
-	    curproxy->srv_exp[curproxy->nb_srvexp].preg = preg;
-	    curproxy->srv_exp[curproxy->nb_srvexp].replace = strdup(args[2]);
-	    curproxy->nb_srvexp++;
+	    curproxy->rsp_exp[curproxy->nb_rspexp].preg = preg;
+	    curproxy->rsp_exp[curproxy->nb_rspexp].replace = strdup(args[2]);
+	    curproxy->nb_rspexp++;
+	}
+	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",
+		      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;
+	    }
+	    //	    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++;
+	}
+	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",
+		      file, linenum);
+		continue;
+	    }
+
+	    if (*(args[1]) == 0) {
+		Alert("parsing [%s:%d] : <rspadd> expects <header> as an argument.\n",
+		      file, linenum);
+		return -1;
+	    }
+
+	    curproxy->rsp_add[curproxy->nb_rspadd++] = strdup(args[1]);
 	}
 	else {
 	    Alert("parsing [%s:%d] : unknown keyword <%s>\n", file, linenum, args[0]);
@@ -2808,7 +3548,30 @@
     }
 
     while (curproxy != NULL) {
-	if (curproxy->mode == PR_MODE_TCP || curproxy->mode == PR_MODE_HEALTH) { /* TCP PROXY or HEALTH CHECK */
+	if ((curproxy->mode != PR_MODE_HEALTH) &&
+	    !(curproxy->options & (PR_O_TRANSP | PR_O_BALANCE)) &&
+	    (*(int *)&curproxy->dispatch_addr == 0)) {
+	    Alert("parsing %s : listener %s has no dispatch address and is not in transparent or balance mode.\n",
+		    file, curproxy->id);
+	    cfgerr++;
+	}
+	else if ((curproxy->mode != PR_MODE_HEALTH) && (curproxy->options & PR_O_BALANCE)) {
+	    if (curproxy->options & PR_O_TRANSP) {
+		Alert("parsing %s : listener %s cannot use both transparent and balance mode.\n",
+		      file, curproxy->id);
+		cfgerr++;
+	    }
+	    else if (curproxy->srv == NULL) {
+		Alert("parsing %s : listener %s needs at least 1 server in balance mode.\n",
+		      file, curproxy->id);
+		cfgerr++;
+	    }
+	    else if (*(int *)&curproxy->dispatch_addr != 0) {
+		Warning("parsing %s : dispatch address of listener %s will be ignored in balance mode.\n",
+			file, curproxy->id);
+	    }
+	}
+	else if (curproxy->mode == PR_MODE_TCP || curproxy->mode == PR_MODE_HEALTH) { /* TCP PROXY or HEALTH CHECK */
 	    if (curproxy->cookie_name != NULL) {
 		Warning("parsing %s : cookie will be ignored for listener %s.\n",
 			file, curproxy->id);
@@ -2817,11 +3580,11 @@
 		Warning("parsing %s : servers will be ignored for listener %s.\n",
 			file, curproxy->id);
 	    }
-	    if (curproxy->nb_srvexp) {
+	    if (curproxy->nb_rspexp) {
 		Warning("parsing %s : server regular expressions will be ignored for listener %s.\n",
 			file, curproxy->id);
 	    }
-	    if (curproxy->nb_cliexp) {
+	    if (curproxy->nb_reqexp) {
 		Warning("parsing %s : client regular expressions will be ignored for listener %s.\n",
 			file, curproxy->id);
 	    }
@@ -2860,7 +3623,7 @@
     char *tmp;
 
     if (1<<INTBITS != sizeof(int)*8) {
-	fprintf(stderr,
+	qfprintf(stderr,
 		"Error: wrong architecture. Recompile so that sizeof(int)=%d\n",
 		sizeof(int)*8);
 	exit(1);
@@ -2886,7 +3649,9 @@
 	    else if (*flag == 'd')
 		mode |= MODE_DEBUG;
 	    else if (*flag == 'D')
-		mode |= MODE_DAEMON;
+		mode |= MODE_DAEMON | MODE_QUIET;
+	    else if (*flag == 'q')
+		mode |= MODE_QUIET;
 #if STATTIME > 0
 	    else if (*flag == 's')
 		mode |= MODE_STATS;
@@ -2925,20 +3690,20 @@
 
     ReadEvent = (fd_set *)calloc(1,
 		sizeof(fd_set) *
-		(cfg_maxsock + 3 + FD_SETSIZE - 1) / FD_SETSIZE);
+		(cfg_maxsock + FD_SETSIZE - 1) / FD_SETSIZE);
     WriteEvent = (fd_set *)calloc(1,
 		sizeof(fd_set) *
-		(cfg_maxsock + 3 + FD_SETSIZE - 1) / FD_SETSIZE);
+		(cfg_maxsock + FD_SETSIZE - 1) / FD_SETSIZE);
     StaticReadEvent = (fd_set *)calloc(1,
 		sizeof(fd_set) *
-		(cfg_maxsock + 3 + FD_SETSIZE - 1) / FD_SETSIZE);
+		(cfg_maxsock + FD_SETSIZE - 1) / FD_SETSIZE);
     StaticWriteEvent = (fd_set *)calloc(1,
 		sizeof(fd_set) *
-		(cfg_maxsock + 3 + FD_SETSIZE - 1) / FD_SETSIZE);
+		(cfg_maxsock + FD_SETSIZE - 1) / FD_SETSIZE);
 
     fdtab = (struct fdtab *)calloc(1,
-		sizeof(struct fdtab) * (cfg_maxsock + 3));
-    for (i = 0; i < cfg_maxsock + 3; i++) {
+		sizeof(struct fdtab) * (cfg_maxsock));
+    for (i = 0; i < cfg_maxsock; i++) {
 	fdtab[i].state = FD_STCLOSE;
     }
 }
@@ -2963,6 +3728,13 @@
 	    return -1;
 	}
 	
+	if (fd >= cfg_maxsock) {
+	    Alert("socket(): not enough free sockets for proxy %s. Raise -n argument. Aborting.\n",
+		  curproxy->id);
+	    close(fd);
+	    return -1;
+	}
+
 	if ((fcntl(fd, F_SETFL, O_NONBLOCK) == -1) ||
 	    (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY,
 			(char *) &one, sizeof(one)) == -1)) {
@@ -2996,7 +3768,7 @@
 	/* the function for the accept() event */
 	fdtab[fd].read  = &event_accept;
 	fdtab[fd].write = NULL; /* never called */
-	fdtab[fd].owner = (struct task *)curproxy; /* reference the proxy */
+	fdtab[fd].owner = (struct task *)curproxy; /* reference the proxy instead of a task */
 	curproxy->state = PR_STRUN;
 	fdtab[fd].state = FD_STLISTEN;
 	FD_SET(fd, StaticReadEvent);
@@ -3022,10 +3794,13 @@
 	    Alert("[%s.main()] Cannot fork\n", argv[0]);
 	    exit(1); /* there has been an error */
 	}
+	setpgid(1, 0);
+    }
 
+    if (mode & MODE_QUIET) {
 	/* detach from the tty */
+	fclose(stdin); fclose(stdout); fclose(stderr);
 	close(0); close(1); close(2);
-	setpgid(1, 0);
     }
 
     signal(SIGQUIT, dump);
diff --git a/init.d/haproxy b/init.d/haproxy
new file mode 100644
index 0000000..80fe4a6
--- /dev/null
+++ b/init.d/haproxy
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+bin=/usr/sbin/haproxy
+cmdline='$bin -D -f /etc/haproxy/haproxy.cfg'
+
+. $ROOT/sbin/init.d/default 
+
+# arret en douceur
+function dostop {
+   pids=`pidof -o $$ -- $PNAME`
+   if [ ! -z "$pids" ]; then
+      echo "Asking $PNAME to terminate asap..."
+      kill -USR1 $pids
+   fi
+}
+
+main $*
+