* released 1.1.17
* add the notion of "backup" servers, which are used only when all other
  servers are down.
* make Set-Cookie return "" instead of "(null)" when the server has no
  cookie assigned (useful for backup servers).
* "log" now supports an optionnal level name (info, notice, err ...) above
  which nothing is sent.
* replaced some strncmp() with memcmp() for better efficiency.
* added "capture cookie" option which logs client and/or server cookies
* cleaned up/down messages and dump servers states upon SIGHUP
* added a redirection feature for errors : "errorloc <errnum> <url>"
* now we won't insist on connecting to a dead server, even with a cookie,
  unless option "persist" is specified.
* added HTTP/408 response for client request time-out and HTTP/50[234] for
  server reply time-out or errors.
* updates to the examples files
* added a 'do_status' command to the Formilux init script
diff --git a/CHANGELOG b/CHANGELOG
index e7d93a8..19ea09e 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,5 +1,20 @@
- * ChangeLog :
+* ChangeLog :
  *
+ * 2002/10/18 : 1.1.17
+ *   - add the notion of "backup" servers, which are used only when all other
+ *     servers are down.
+ *   - make Set-Cookie return "" instead of "(null)" when the server has no
+ *     cookie assigned (useful for backup servers).
+ *   - "log" now supports an optionnal level name (info, notice, err ...) above
+ *     which nothing is sent.
+ *   - replaced some strncmp() with memcmp() for better efficiency.
+ *   - added "capture cookie" option which logs client and/or server cookies
+ *   - cleaned up/down messages and dump servers states upon SIGHUP
+ *   - added a redirection feature for errors : "errorloc <errnum> <url>"
+ *   - now we won't insist on connecting to a dead server, even with a cookie,
+ *     unless option "persist" is specified.
+ *   - added HTTP/408 response for client request time-out and HTTP/50[234] for
+ *     server reply time-out or errors.
  * 2002/09/01 : 1.1.16
  *   - implement HTTP health checks when option "httpchk" is specified.
  * 2002/08/07 : 1.1.15
diff --git a/doc/haproxy.txt b/doc/haproxy.txt
index 06ed2f9..18090ba 100644
--- a/doc/haproxy.txt
+++ b/doc/haproxy.txt
@@ -1,9 +1,9 @@
 
          		     H A - P r o x y
          		     ---------------
-         		      version 1.1.16
+         		      version 1.1.17
 			      willy tarreau
-			       2002/09/01
+			       2002/10/25
 
 ================
 | Introduction |
@@ -15,9 +15,11 @@
   - effectuer 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.
-  - interdire des requêtes qui vérifient certaines conditions.
+  - s'arrêter en douceur sans perte brutale de service ;
+  - modifier/ajouter/supprimer des entêtes dans la requête et la réponse ;
+  - interdire des requêtes qui vérifient certaines conditions ;
+  - utiliser des serveurs de secours lorsque les serveurs principaux sont hors
+    d'usage.
 
 Il requiert peu de ressources, et son architecture événementielle mono-processus
 lui permet facilement de gérer plusieurs milliers de connexions simultanées sur
@@ -83,7 +85,7 @@
 proxies. Ils sont tous spécifiés dans la section 'global'. Les paramètres
 supportés sont :
 
-  - log <adresse> <catégorie>
+  - log <adresse> <catégorie> [niveau_max]
   - maxconn <nombre>
   - uid <identifiant>
   - gid <identifiant>
@@ -99,12 +101,18 @@
 apparition de serveurs, connexions, erreurs. Tous les messages sont envoyés en
 syslog vers un ou deux serveurs. La syntaxe est la suivante :
 
-    log <adresse_ip> <facility>
+    log <adresse_ip> <catégorie> [niveau_max]
 
 Les connexions sont envoyées en niveau "info". Les démarrages de service et de
 serveurs seront envoyés en "notice", les signaux d'arrêts en "warning" et les
 arrêts définitifs de services et de serveurs en "alert". Ceci est valable aussi
-bien pour les proxies que pour les serveurs testés par les proxies.
+bien pour les proxies que pour les serveurs testés par les proxies. Le paramètre
+optionnel <niveau_max> définit le niveau maximal de traces émises parmi les 8
+valeurs suivantes  :
+    emerg, alert, crit, err, warning, notice, info, debug
+
+Par compatibilité avec les versions 1.1.16 et antérieures, La valeur par défaut
+est "debug" si l'option n'est pas précisée.
 
 Les catégories possibles sont :
     kern, user, mail, daemon, auth, syslog, lpr, news,
@@ -115,7 +123,7 @@
 ---------
     global
 	log 192.168.2.200 local3
-	log 192.168.2.201 local4
+	log 127.0.0.1     local4 notice
 
 1.2) limitation du nombre de connexions
 ---------------------------------------
@@ -139,7 +147,7 @@
 ------------------------------
 Afin de réduire les risques d'attaques dans le cas où une faille non identifiée
 serait exploitée, il est possible de diminuer les privilèges du processus, et
-de le cloisonner.
+de l'isoler dans un répertoire sans risque.
 
 Dans la section 'global', le paramètre 'uid' permet de spécifier un identifiant
 numérique d'utilisateur. La valeur 0, correspondant normalement au super-
@@ -153,11 +161,14 @@
 
 Le paramètre 'chroot' autorise à changer la racine du processus une fois le
 programme lancé, de sorte que ni le processus, ni l'un de ses descendants ne
-puisse remonter de nouveau à la racine. Ce type de cloisonnement (chroot) est
+puissent remonter de nouveau à la racine. Ce type de cloisonnement (chroot) est
 parfois contournable sur certains OS (Linux 2.2, Solaris), mais visiblement
 fiable sur d'autres (Linux 2.4). Aussi, il est important d'utiliser un
 répertoire spécifique au service pour cet usage, et de ne pas mutualiser un même
-répertoire pour plusieurs services de nature différente.
+répertoire pour plusieurs services de nature différente. Pour rendre l'isolement
+plus robuste, il est conseillé d'utiliser un répertoire vide, sans aucun droit,
+et de changer l'uid du processus de sorte qu'il ne puisse rien faire dans ledit
+répertoire.
 
 Remarque: dans le cas où une telle faille serait mise en évidence, il est fort
 probable que les premières tentatives de son exploitation provoquent un arrêt du
@@ -473,8 +484,9 @@
 	cookie SERVERID rewrite
 
 Pour créer un cookie comportant la valeur attribuée à un 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é :
+en répartition de charge interne. Dans ce cas, il est souhaitable que tous les
+serveurs aient un cookie renseigné. Un serveur non assigné d'un cookie
+retournera un cookie vide (cookie de suppression) :
 
 	cookie SERVERID insert
 
@@ -510,8 +522,8 @@
 
 2.10) Assignation d'un serveur à une valeur de cookie
 ----------------------------------------------------
-En mode HTTP, il est possible d'associer des serveurs à des valeurs de
-cookie par le paramètre 'server'. La syntaxe est :
+En mode HTTP, il est possible d'associer des valeurs de cookie à des serveurs
+par le paramètre 'server'. La syntaxe est :
 
     server <identifiant> <adresse_ip>:<port> cookie <valeur>
 
@@ -558,7 +570,7 @@
 ------------------------------
 Il est possible de tester l'état des serveurs par établissement de connexion TCP
 ou par envoi d'une requête HTTP. Un serveur hors d'usage ne sera pas utilisé
-dans leprocessus de répartition de charge interne. Pour activer la surveillance,
+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. Il est
 possible de spécifier l'intervalle (en millisecondes) séparant deux tests du
 serveur par le paramètre "inter", le nombre d'échecs acceptés par le paramètre
@@ -578,6 +590,25 @@
 Le temps maximal imparti pour une réponse est égal à l'intervalle entre deux
 tests (paramètre "inter"). Pour activer ce mode, spécifier l'option "httpchk".
 
+Depuis la version 1.1.17, il est possible de définir des serveurs de secours,
+utilisés uniquement lorsqu'aucun des autres serveurs ne fonctionne. Pour cela,
+ajouter le mot clé "backup" sur la ligne de définition du serveur. Un serveur
+de secours n'est appelé que lorsque tous les serveurs normaux, ainsi que tous
+les serveurs de secours qui le précèdent sont hors d'usage. Il n'y a donc pas
+de répartition de charge entre des serveurs de secours. Ce type de serveurs
+peut servir à retourner des pages d'indisponibilité de service. Dans ce cas,
+il est préférable de ne pas affecter de cookie, afin que les clients qui le
+rencontrent n'y soient pas affectés définitivement. Le fait de ne pas mettre
+de cookie envoie un cookie vide, ce qui a pour effet de supprimer un éventuel
+cookie affecté précédemment.
+
+Enfin, depuis la version 1.1.17, il est possible de visualiser rapidement l'état
+courant de tous les serveurs. Pour cela, il suffit d'envoyer un signal SIGHUP au
+processus proxy. L'état de tous les serveurs de tous les proxies est envoyé dans
+les logs en niveau "notice", ainsi que sur la sortie d'erreurs si elle est
+active. C'est une bonne raison pour avoir au moins un serveur de logs local en
+niveau notice.
+
 Exemples :
 ----------
 # même que précédemment avec surveillance TCP
@@ -606,6 +637,16 @@
 	balance roundrobin
 	server web1 192.168.1.1:80 cookie server01 check
 	server web2 192.168.1.2:80 cookie server02 check
+
+# idem avec serveur applicatif de secours, et serveur de pages d'erreurs
+    listen web_appl 0.0.0.0:80
+	mode http
+	cookie SERVERID insert nocache indirect
+	balance roundrobin
+	server web1 192.168.1.1:80 cookie server01 check
+	server web2 192.168.1.2:80 cookie server02 check
+	server web-backup 192.168.1.3:80 cookie server03 check backup
+	server web-excuse 192.168.1.4:80 check backup
 
 
 3.2) Reconnexion vers un répartiteur en cas d'échec direct
@@ -624,6 +665,22 @@
 	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 refus de connexion.
+
+Par défaut (et dans les versions 1.1.16 et antérieures), le paramètre redispatch
+ne s'applique qu'aux échecs de connexion au serveur. Depuis la version 1.1.17,
+il s'applique aussi aux connexions destinées à des serveurs identifiés comme
+hors d'usage par la surveillance. Si l'on souhaite malgré tout qu'un client
+disposant d'un cookie correspondant à un serveur défectueux tente de s'y
+connecter, il faut préciser l'option "persist" :
+
+    listen http_proxy 0.0.0.0:80
+	mode http
+	option persist
+	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.
 
 
@@ -660,12 +717,14 @@
 Les connexions TCP et HTTP peuvent donner lieu à une journalisation sommaire ou
 détaillée indiquant, pour chaque connexion, la date, l'heure, l'adresse IP
 source, le serveur destination, la durée de la connexion, les temps de réponse,
-la requête HTTP, le code de retour, la quantité de données transmise.
-Tous les messages sont envoyés en syslog vers un ou deux serveurs. La syntaxe
+la requête HTTP, le code de retour, la quantité de données transmises, et même
+dans certains cas, la valeur d'un cookie permettant de suivre les sessions.
+Tous les messages sont envoyés en syslog vers un ou deux serveurs. Se référer à
+la section 1.1 pour plus d'information sur les catégories de logs.  La syntaxe
 est la suivante :
 
-    log <adresse_ip> <facility>
-    log <adresse_ip> <facility>
+    log <adresse_ip_1> <catégorie_1> [niveau_max_1]
+    log <adresse_ip_2> <catégorie_2> [niveau_max_2]
 ou
     log global
 
@@ -681,15 +740,6 @@
 	log 192.168.2.200 local3
 	log 192.168.2.201 local4
 
-Les connexions sont envoyées en niveau "info". Les démarrages de service seront
-envoyés en "notice", les signaux d'arrêts en "warning" et les arrêts définitifs
-en "alert". Ceci est valable aussi bien pour les proxies que pour les serveurs
-testés au sein des proxies. Les catégories possibles sont :
-
-    kern, user, mail, daemon, auth, syslog, lpr, news,
-    uucp, cron, auth2, ftp, ntp, audit, alert, cron2,
-    local0, local1, local2, local3, local4, local5, local6, local7
-
 Par défaut, les informations contenues dans les logs se situent au niveau TCP
 uniquement. Il faut préciser l'option 'httplog' pour obtenir les détails du
 protocole HTTP. Dans les cas où un mécanisme de surveillance effectuant des
@@ -697,6 +747,32 @@
 l'option 'dontlognull', pour ne plus obtenir une ligne de log pour les sessions
 n'ayant pas donné lieu à un échange de données (requête ou réponse).
 
+Le mot clé "capture" permet d'ajouter dans des logs HTTP des informations
+capturées dans les échanges. La version 1.1.17 supporte uniquement une capture
+de cookies client et serveur, ce qui permet dans bien des cas, de reconstituer
+la session d'un utilisateur. La syntaxe est la suivante :
+
+    capture cookie <préfixe_cookie> len <longueur_capture>
+
+Le premier cookie dont le nom commencera par <préfixe_cookie> sera capturé, et
+transmis sous la forme "NOM=valeur", sans toutefois, excéder <longueur_capture>
+caractères (64 au maximum). Lorsque le nom du cookie est fixe et connu, on peut
+le suffixer du signe "=" pour s'assurer qu'aucun autre cookie ne prendra sa
+place dans les logs.
+
+Exemples :
+----------
+    # capture du premier cookie dont le nom commence par "ASPSESSION"
+    capture cookie ASPSESSION len 32
+
+    # capture du premier cookie dont le nom est exactement "vgnvisitor"
+    capture cookie vgnvisitor= len 32
+
+Dans les logs, le champ précédant la requête HTTP est le cookie positionné par
+le serveur, précédé du cookie positionné par le client. Chacun de ces champs est
+remplacé par le signe "-" lorsqu'aucun cookie n'est fourni par le client ou le
+serveur.
+
 Enfin, l'option 'forwardfor' ajoute l'adresse IP du client dans un champ
 'X-Forwarded-For' de la requête, ce qui permet à un serveur web final de
 connaître l'adresse IP du client initial.
@@ -709,6 +785,7 @@
 	option httplog
 	option dontlognull
 	option forwardfor
+	capture cookie userid= len 20
 
 
 4.3) Modification des entêtes HTTP
@@ -801,7 +878,7 @@
   - cacher ce cookie à l'application lors des requêtes ultérieures.
 
 Exemple :
--------
+---------
     listen application 0.0.0.0:80
 	mode http
 	cookie SERVERID insert nocache indirect
@@ -809,6 +886,42 @@
 	server 192.168.1.1:80 cookie server01 check
 	server 192.168.1.2:80 cookie server02 check
 
+4.5) Personalisation des erreurs
+--------------------------------
+
+Certaines situations conduisent à retourner une erreur HTTP au client :
+  - requête invalide ou trop longue => code HTTP 400
+  - requête mettant trop de temps à venir => code HTTP 408
+  - requête interdite (bloquée par un reqideny) => code HTTP 403
+  - erreur interne du proxy => code HTTP 500
+  - le serveur a retourné une réponse incomplète ou invalide => code HTTP 502
+  - aucun serveur disponible pour cette requête => code HTTP 503
+  - le serveur n'a pas répondu dans le temps imparti => code HTTP 504
+
+Un message d'erreur succint tiré de la RFC accompagne ces codes de retour.
+Cependant, en fonction du type de clientèle, on peut préférer retourner des
+pages personnalisées. Ceci est possible par le biais de la commande "errorloc" :
+
+    errorloc <code_HTTP> <location>
+
+Au lieu de générer une erreur HTTP <code_HTTP> parmi les codes cités ci-dessus,
+le proxy génèrera un code de redirection temporaire (HTTP 302) vers l'adresse
+d'une page précisée dans <location>. Cette adresse peut être relative au site,
+ou absolue. Comme cette réponse est traîtée par le navigateur du client
+lui-même, il est indispensable que l'adresse fournie lui soit accessible.
+
+Exemple :
+---------
+    listen application 0.0.0.0:80
+        errorloc 400 /badrequest.html
+        errorloc 403 /forbidden.html
+        errorloc 408 /toolong.html
+	errorloc 500 http://haproxy.domain.net/bugreport.html
+        errorloc 502 http://192.168.114.58/error50x.html
+        errorloc 503 http://192.168.114.58/error50x.html
+        errorloc 504 http://192.168.114.58/error50x.html
+
+
 =======================
 | Paramétrage système |
 =======================
diff --git a/examples/haproxy.cfg b/examples/haproxy.cfg
index 97ef70b..3b6f825 100644
--- a/examples/haproxy.cfg
+++ b/examples/haproxy.cfg
@@ -1,5 +1,7 @@
 global
 	log 127.0.0.1	local0
+	log 127.0.0.1	local1 notice
+	#log loghost	local0 info
 	maxconn 4096
 	chroot /tmp
 	uid 11
@@ -36,6 +38,7 @@
 	cookie	SERVERID insert indirect nocache
 	server	inst1 192.168.114.56:80 cookie server01 check inter 2000 fall 3
 	server	inst2 192.168.114.56:81 cookie server02 check inter 2000 fall 3
+	capture cookie vgnvisitor= len 32
 	retries	3
 	redispatch
 	maxconn	2000
@@ -62,3 +65,47 @@
 	clitimeout	50000
 	srvtimeout	50000
 
+listen	appli4-backup 0.0.0.0:10004
+	log	global
+	mode	http
+	option	httplog
+	option	dontlognull
+	option	httpchk
+	option	persist
+	balance	roundrobin
+	server	inst1 192.168.114.56:80 check inter 2000 fall 3
+	server	inst2 192.168.114.56:81 check inter 2000 fall 3 backup
+	retries	3
+	redispatch
+	maxconn	2000
+	contimeout	5000
+	clitimeout	50000
+	srvtimeout	50000
+
+listen	appli5-backup 0.0.0.0:10005
+	log	global
+	mode	http
+	option	httplog
+	option	dontlognull
+	option	httpchk
+	balance	roundrobin
+	cookie	SERVERID insert indirect nocache
+	server	inst1 192.168.114.56:80 cookie server01 check inter 2000 fall 3
+	server	inst2 192.168.114.56:81 cookie server02 check inter 2000 fall 3
+	server	inst3 192.168.114.57:80 backup check inter 2000 fall 3
+	capture cookie ASPSESSION len 32
+	retries	3
+	redispatch
+	maxconn	2000
+	contimeout	5000
+	clitimeout	50000
+	srvtimeout	50000
+
+	reqidel ^Connection:		# disable keep-alive
+	reqadd  Connection:\ close
+	rspidel ^Connection:
+	rspadd  Connection:\ close
+	rspidel ^Set-cookie:\ IP=	# do not let this cookie tell our internal IP address
+	
+	errorloc	502	http://192.168.114.58/error502.html
+
diff --git a/haproxy.c b/haproxy.c
index 0dfa46e..e343460 100644
--- a/haproxy.c
+++ b/haproxy.c
@@ -11,13 +11,27 @@
  *   - solaris only : sometimes, an HTTP proxy with only a dispatch address causes
  *     the proxy to terminate (no core) if the client breaks the connection during
  *     the response. Seen on 1.1.8pre4, but never reproduced. May not be related to
- *     the snprintf() bug since requests we simple (GET / HTTP/1.0).
- *   - cookie in insert+indirect mode sometimes segfaults !
+ *     the snprintf() bug since requests were simple (GET / HTTP/1.0), but may be
+ *     related to missing setsid() (fixed in 1.1.15)
  *   - a proxy with an invalid config will prevent the startup even if disabled.
- *   - it may be nice to return HTTP 502 when a server returns no header nor data.
  *
  * ChangeLog :
  *
+ * 2002/10/18 : 1.1.17
+ *   - add the notion of "backup" servers, which are used only when all other
+ *     servers are down.
+ *   - make Set-Cookie return "" instead of "(null)" when the server has no
+ *     cookie assigned (useful for backup servers).
+ *   - "log" now supports an optionnal level name (info, notice, err ...) above
+ *     which nothing is sent.
+ *   - replaced some strncmp() with memcmp() for better efficiency.
+ *   - added "capture cookie" option which logs client and/or server cookies
+ *   - cleaned up/down messages and dump servers states upon SIGHUP
+ *   - added a redirection feature for errors : "errorloc <errnum> <url>"
+ *   - now we won't insist on connecting to a dead server, even with a cookie,
+ *     unless option "persist" is specified.
+ *   - added HTTP/408 response for client request time-out and HTTP/50[234] for
+ *     server reply time-out or errors.
  * 2002/09/01 : 1.1.16
  *   - implement HTTP health checks when option "httpchk" is specified.
  * 2002/08/07 : 1.1.15
@@ -177,8 +191,8 @@
 #include <linux/netfilter_ipv4.h>
 #endif
 
-#define HAPROXY_VERSION "1.1.16"
-#define HAPROXY_DATE	"2002/09/01"
+#define HAPROXY_VERSION "1.1.17"
+#define HAPROXY_DATE	"2002/10/18"
 
 /* this is for libc5 for example */
 #ifndef TCP_NODELAY
@@ -198,6 +212,7 @@
 // reserved buffer space for header rewriting
 #define	MAXREWRITE	4096
 #define REQURI_LEN	1024
+#define CAPTURE_LEN	64
 
 // max # args on a configuration line
 #define MAX_LINE_ARGS	40
@@ -305,6 +320,7 @@
 #define sizeof_buffer	sizeof(struct buffer)
 #define sizeof_fdtab	sizeof(struct fdtab)
 #define sizeof_requri	REQURI_LEN
+#define sizeof_capture	CAPTURE_LEN
 
 /* different possible states for the sockets */
 #define FD_STCLOSE	0
@@ -344,6 +360,7 @@
 #define PR_O_COOK_NOC	1024	/* add a 'Cache-control' header with the cookie */
 #define PR_O_COOK_POST	2048	/* don't insert cookies for requests other than a POST */
 #define PR_O_HTTP_CHK	4096	/* use HTTP 'OPTIONS' method to check server health */
+#define PR_O_PERSIST	8192	/* server persistence stays effective even when server is down */
 
 
 /* various session flags */
@@ -385,6 +402,7 @@
 
 /* server flags */
 #define SRV_RUNNING	1
+#define SRV_BACKUP	2
 
 /* what to do when a header matches a regex */
 #define ACT_ALLOW	0	/* allow the request */
@@ -485,6 +503,8 @@
 	long  t_data;			/* delay before the first data byte from the server ... */
 	unsigned long  t_close;		/* total session duration */
 	char *uri;			/* first line if log needed, NULL otherwise */
+	char *cli_cookie;		/* cookie presented by the client, in capture mode */
+	char *srv_cookie;		/* cookie presented by the server, in capture mode */
 	int status;			/* HTTP status from the server, negative if from proxy */
 	long long bytes;		/* number of bytes transferred from the server */
     } logs;
@@ -498,6 +518,10 @@
     struct server *srv, *cursrv;	/* known servers, current server */
     int nbservers;			/* # of servers */
     char *cookie_name;			/* name of the cookie to look for */
+    int  cookie_len;			/* strlen(cookie_len), computed only once */
+    char *capture_name;			/* beginning of the name of the cookie to capture */
+    int  capture_namelen;		/* length of the cookie name to match */
+    int  capture_len;			/* length of the string to be captured */
     int clitimeout;			/* client I/O timeout (in milliseconds) */
     int srvtimeout;			/* server I/O timeout (in milliseconds) */
     int contimeout;			/* connect timeout (in milliseconds) */
@@ -511,6 +535,7 @@
     struct proxy *next;
     struct sockaddr_in logsrv1, logsrv2; /* 2 syslog servers */
     char logfac1, logfac2;		/* log facility for both servers. -1 = disabled */
+    int loglev1, loglev2;		/* log level for each server, 7 by default */
     int to_log;				/* things to be logged (LW_*) */
     struct timeval stop_time;		/* date to stop listening, when stopping != 0 */
     int nb_reqadd, nb_rspadd;
@@ -518,6 +543,22 @@
     struct hdr_exp *rsp_exp;		/* regular expressions for response headers */
     char *req_add[MAX_NEWHDR], *rsp_add[MAX_NEWHDR]; /* headers to be added */
     int grace;				/* grace time after stop request */
+    struct {
+	char *msg400;			/* message for error 400 */
+	int len400;			/* message length for error 400 */
+	char *msg403;			/* message for error 403 */
+	int len403;			/* message length for error 403 */
+	char *msg408;			/* message for error 408 */
+	int len408;			/* message length for error 408 */
+	char *msg500;			/* message for error 500 */
+	int len500;			/* message length for error 500 */
+	char *msg502;			/* message for error 502 */
+	int len502;			/* message length for error 502 */
+	char *msg503;			/* message for error 503 */
+	int len503;			/* message length for error 503 */
+	char *msg504;			/* message for error 504 */
+	int len504;			/* message length for error 504 */
+    } errmsg;
 };
 
 /* info about one given fd */
@@ -545,10 +586,13 @@
     int mode;
     char *chroot;
     int logfac1, logfac2;
+    int loglev1, loglev2;
     struct sockaddr_in logsrv1, logsrv2;
 } global = {
     logfac1 : -1,
     logfac2 : -1,
+    loglev1 : 7, /* max syslog level : debug */
+    loglev2 : 7,
     /* others NULL OK */
 };
 
@@ -563,7 +607,8 @@
     **pool_buffer   = NULL,
     **pool_fdtab    = NULL,
     **pool_requri   = NULL,
-    **pool_task	    = NULL;
+    **pool_task	    = NULL,
+    **pool_capture  = NULL;
 
 struct proxy *proxy  = NULL;	/* list of all existing proxies */
 struct fdtab *fdtab = NULL;	/* array of all the file descriptors */
@@ -613,6 +658,12 @@
 #define MAX_HOSTNAME_LEN	32
 static char hostname[MAX_HOSTNAME_LEN] = "";
 
+const char *HTTP_302 =
+	"HTTP/1.0 302 Found\r\n"
+	"Cache-Control: no-cache\r\n"
+	"Connection: close\r\n"
+	"Location: "; /* not terminated since it will be concatenated with the URL */
+
 const char *HTTP_400 =
 	"HTTP/1.0 400 Bad request\r\n"
 	"Cache-Control: no-cache\r\n"
@@ -627,6 +678,13 @@
 	"\r\n"
 	"<html><body><h1>403 Forbidden</h1>\nRequest forbidden by administrative rules.\n</body></html>\n";
 
+const char *HTTP_408 =
+	"HTTP/1.0 408 Request Time-out\r\n"
+	"Cache-Control: no-cache\r\n"
+	"Connection: close\r\n"
+	"\r\n"
+	"<html><body><h1>408 Request Time-out</h1>\nYour browser didn't send a complete request in time.\n</body></html>\n";
+
 const char *HTTP_500 =
 	"HTTP/1.0 500 Server Error\r\n"
 	"Cache-Control: no-cache\r\n"
@@ -635,11 +693,25 @@
 	"<html><body><h1>500 Server Error</h1>\nAn internal server error occured.\n</body></html>\n";
 
 const char *HTTP_502 =
-	"HTTP/1.0 502 Proxy Error\r\n"
+	"HTTP/1.0 502 Bad Gateway\r\n"
 	"Cache-Control: no-cache\r\n"
 	"Connection: close\r\n"
 	"\r\n"
-	"<html><body><h1>502 Proxy Error</h1>\nNo server is available to handle this request.\n</body></html>\n";
+	"<html><body><h1>502 Bad Gateway</h1>\nThe server returned an invalid or incomplete response.\n</body></html>\n";
+
+const char *HTTP_503 =
+	"HTTP/1.0 503 Service Unavailable\r\n"
+	"Cache-Control: no-cache\r\n"
+	"Connection: close\r\n"
+	"\r\n"
+	"<html><body><h1>503 Service Unavailable</h1>\nNo server is available to handle this request.\n</body></html>\n";
+
+const char *HTTP_504 =
+	"HTTP/1.0 504 Gateway Time-out\r\n"
+	"Cache-Control: no-cache\r\n"
+	"Connection: close\r\n"
+	"\r\n"
+	"<html><body><h1>504 Gateway Time-out</h1>\nThe server didn't respond in time.\n</body></html>\n";
 
 /*********************************************************************/
 /*  statistics  ******************************************************/
@@ -826,7 +898,7 @@
     int fac_level;
     int hdr_len, data_len;
     struct sockaddr_in *sa[2];
-    int facilities[2];
+    int facilities[2], loglevel[2];
     int nbloggers = 0;
     char *log_ptr;
 
@@ -870,27 +942,35 @@
 	if (global.logfac1 >= 0) {
 	    sa[nbloggers] = &global.logsrv1;
 	    facilities[nbloggers] = global.logfac1;
+	    loglevel[nbloggers] = global.loglev1;
 	    nbloggers++;
 	}
 	if (global.logfac2 >= 0) {
 	    sa[nbloggers] = &global.logsrv2;
 	    facilities[nbloggers] = global.logfac2;
+	    loglevel[nbloggers] = global.loglev2;
 	    nbloggers++;
 	}
     } else {
 	if (p->logfac1 >= 0) {
 	    sa[nbloggers] = &p->logsrv1;
 	    facilities[nbloggers] = p->logfac1;
+	    loglevel[nbloggers] = p->loglev1;
 	    nbloggers++;
 	}
 	if (p->logfac2 >= 0) {
 	    sa[nbloggers] = &p->logsrv2;
 	    facilities[nbloggers] = p->logfac2;
+	    loglevel[nbloggers] = p->loglev2;
 	    nbloggers++;
 	}
     }
 
     while (nbloggers-- > 0) {
+	/* we can filter the level of the messages that are sent to each logger */
+	if (level > loglevel[nbloggers])
+	    continue;
+	
 	/* For each target, we may have a different facility.
 	 * We can also have a different log level for each message.
 	 * This induces variations in the message header length.
@@ -1330,12 +1410,39 @@
 	pool_free(buffer, s->rep);
     if (s->logs.uri)
 	pool_free(requri, s->logs.uri);
+    if (s->logs.cli_cookie)
+	pool_free(capture, s->logs.cli_cookie);
+    if (s->logs.srv_cookie)
+	pool_free(capture, s->logs.srv_cookie);
 
     pool_free(session, s);
 }
 
 
 /*
+ * This function tries to find a running server for the proxy <px>. A first
+ * pass looks for active servers, and if none is found, a second pass also
+ * looks for backup servers.
+ * If no valid server is found, NULL is returned and px->cursrv is left undefined.
+ */
+static inline struct server *find_server(struct proxy *px) {
+    struct server *srv = px->cursrv;
+    int ignore_backup = 1;
+
+    do {
+	do  {
+	    if (srv == NULL)
+		srv = px->srv;
+	    if (srv->state & SRV_RUNNING
+		&& !((srv->state & SRV_BACKUP) && ignore_backup))
+		return srv;
+	    srv = srv->next;
+	} while (srv != px->cursrv);
+    } while (ignore_backup--);
+    return NULL;
+}
+
+/*
  * 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.
@@ -1351,22 +1458,16 @@
     }
     else if (s->proxy->options & PR_O_BALANCE) {
 	if (s->proxy->options & PR_O_BALANCE_RR) {
-	    int retry = s->proxy->nbservers;
-	    while (retry) {
-		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;
-		retry--;
-	    }
+	    struct server *srv;
 
-	    if (retry == 0) /* no server left */
+	    srv = find_server(s->proxy);
+
+	    if (srv == NULL) /* no server left */
 		return -1;
 
-	    s->srv = s->proxy->cursrv;
-	    s->srv_addr = s->srv->addr;
-	    s->proxy->cursrv = s->proxy->cursrv->next;
+	    s->srv_addr = srv->addr;
+	    s->srv = srv;
+	    s->proxy->cursrv = srv->next;
 	}
 	else /* unknown balancing algorithm */
 	    return -1;
@@ -1798,7 +1899,7 @@
  * and the request is cleared so that no server connection can be initiated.
  * The client must be in a valid state for this (HEADER, DATA ...).
  * Nothing is performed on the server side.
- * The reply buffer must be empty before this.
+ * The reply buffer doesn't need to be empty before this.
  */
 void client_retnclose(struct session *s, int len, const char *msg) {
     FD_CLR(s->cli_fd, StaticReadEvent);
@@ -1808,6 +1909,7 @@
     s->cli_state = CL_STSHUTR;
     strcpy(s->rep->data, msg);
     s->rep->l = len;
+    s->rep->r = s->rep->h = s->rep->lr = s->rep->w = s->rep->data;
     s->rep->r += len;
     s->req->l = 0;
 }
@@ -1815,11 +1917,12 @@
 
 /*
  * returns a message into the rep buffer, and flushes the req buffer.
- * The reply buffer must be empty before this.
+ * The reply buffer doesn't need to be empty before this.
  */
 void client_return(struct session *s, int len, const char *msg) {
     strcpy(s->rep->data, msg);
     s->rep->l = len;
+    s->rep->r = s->rep->h = s->rep->lr = s->rep->w = s->rep->data;
     s->rep->r += len;
     s->req->l = 0;
 }
@@ -1855,7 +1958,7 @@
     if (p->to_log & LW_DATE) {
 	struct tm *tm = localtime(&s->logs.tv_accept.tv_sec);
 
-	send_log(p, LOG_INFO, "%d.%d.%d.%d:%d [%02d/%s/%04d:%02d:%02d:%02d] %s %s %d/%d/%d/%d %d %lld \"%s\"\n",
+	send_log(p, LOG_INFO, "%d.%d.%d.%d:%d [%02d/%s/%04d:%02d:%02d:%02d] %s %s %d/%d/%d/%d %d %lld %s %s \"%s\"\n",
 		 pn[0], pn[1], pn[2], pn[3], ntohs(s->cli_addr.sin_port),
 		 tm->tm_mday, monthname[tm->tm_mon], tm->tm_year+1900,
 		 tm->tm_hour, tm->tm_min, tm->tm_sec,
@@ -1865,10 +1968,12 @@
 		 (s->logs.t_data >= 0) ? s->logs.t_data - s->logs.t_connect : -1,
 		 s->logs.t_close,
 		 s->logs.status, s->logs.bytes,
+		 s->logs.cli_cookie ? s->logs.cli_cookie : "-",
+		 s->logs.srv_cookie ? s->logs.srv_cookie : "-",
 		 uri);
     }
     else {
-	send_log(p, LOG_INFO, "%d.%d.%d.%d:%d %s %s %d/%d/%d/%d %d %lld \"%s\"\n",
+	send_log(p, LOG_INFO, "%d.%d.%d.%d:%d %s %s %d/%d/%d/%d %d %lld %s %s \"%s\"\n",
 		 pn[0], pn[1], pn[2], pn[3], ntohs(s->cli_addr.sin_port),
 		 pxid, srv,
 		 s->logs.t_request,
@@ -1876,6 +1981,8 @@
 		 (s->logs.t_data >= 0) ? s->logs.t_data - s->logs.t_connect : -1,
 		 s->logs.t_close,
 		 s->logs.status, s->logs.bytes,
+		 s->logs.cli_cookie ? s->logs.cli_cookie : "-",
+		 s->logs.srv_cookie ? s->logs.srv_cookie : "-",
 		 uri);
     }
 
@@ -1962,6 +2069,8 @@
 	s->logs.t_data = -1;
 	s->logs.t_close = 0;
 	s->logs.uri = NULL;
+	s->logs.cli_cookie = NULL;
+	s->logs.srv_cookie = NULL;
 	s->logs.status = -1;
 	s->logs.bytes = 0;
 
@@ -2173,7 +2282,7 @@
     return delta;
 }
 
-/* same except that the string len is given, which allows str to be NULL if
+/* same except that the string length is given, which allows str to be NULL if
  * len is 0.
  */
 int buffer_replace2(struct buffer *b, char *pos, char *end, char *str, int len) {
@@ -2280,8 +2389,7 @@
 		if (t->flags & SN_CLDENY) {
 		    /* no need to go further */
 		    t->logs.status = 403;
-		    if (t->proxy->mode == PR_MODE_HTTP)
-			client_retnclose(t, strlen(HTTP_403), HTTP_403);
+		    client_retnclose(t, t->proxy->errmsg.len403, t->proxy->errmsg.msg403);
 		    return 1;
 		}
 
@@ -2338,16 +2446,14 @@
 	     *   req->r  = end of data (not used at this stage)
 	     */
 
-	    if (t->logs.logwait & LW_REQ &&
-		t->proxy->mode & PR_MODE_HTTP) {
+	    if (t->logs.logwait & LW_REQ) {
 		/* we have a complete HTTP request that we must log */
 		int urilen;
 
 		if ((t->logs.uri = pool_alloc(requri)) == NULL) {
 		    Alert("HTTP logging : out of memory.\n");
 		    t->logs.status = 500;
-		    if (t->proxy->mode == PR_MODE_HTTP)
-			client_retnclose(t, strlen(HTTP_500), HTTP_500);
+		    client_retnclose(t, t->proxy->errmsg.len500, t->proxy->errmsg.msg500);
 		    return 1;
 		}
 		
@@ -2422,9 +2528,9 @@
 	     * remove it. If no application cookie persists in the header, we
 	     * *MUST* delete it
 	     */
-	    if (!delete_header && (t->proxy->cookie_name != NULL)
+	    if (!delete_header && (t->proxy->cookie_name != NULL || t->proxy->capture_name != NULL)
 		&& !(t->flags & SN_CLDENY) && (ptr >= req->h + 8)
-		&& (strncmp(req->h, "Cookie: ", 8) == 0)) {
+		&& (memcmp(req->h, "Cookie: ", 8) == 0)) {
 		char *p1, *p2, *p3, *p4;
 		char *del_colon, *del_cookie, *colon;
 		int app_cookies;
@@ -2472,42 +2578,63 @@
 		    if (*p1 == '$') {
 			/* skip this one */
 		    }
-		    else if ((p2 - p1 == strlen(t->proxy->cookie_name)) &&
-			(memcmp(p1, t->proxy->cookie_name, p2 - p1) == 0)) {
-			/* Cool... it's the right one */
-			struct server *srv = t->proxy->srv;
+		    else {
+			/* first, let's see if we want to capture it */
+			if (t->proxy->capture_name != NULL &&
+			    t->logs.cli_cookie == NULL &&
+			    (p4 - p1 >= t->proxy->capture_namelen) &&
+			    memcmp(p1, t->proxy->capture_name, t->proxy->capture_namelen) == 0) {
+			    int log_len = p4 - p1;
 
-			while (srv &&
-			       ((srv->cklen != p4 - p3) || memcmp(p3, srv->cookie, p4 - p3))) {
-			    srv = srv->next;
-			}
+			    if ((t->logs.cli_cookie = pool_alloc(capture)) == NULL) {
+				Alert("HTTP logging : out of memory.\n");
+			    }
 
-			if (srv) { /* we found the server */
-			    t->flags |= SN_DIRECT;
-			    t->srv = srv;
+			    if (log_len > t->proxy->capture_len)
+				log_len = t->proxy->capture_len;
+			    memcpy(t->logs.cli_cookie, p1, log_len);
+			    t->logs.cli_cookie[log_len] = 0;
 			}
-			/* if this cookie was set in insert+indirect mode, then it's better that the
-			 * server never sees it.
-			 */
-			if (del_cookie == NULL &&
-			    (t->proxy->options & (PR_O_COOK_INS | PR_O_COOK_IND)) == (PR_O_COOK_INS | PR_O_COOK_IND)) {
+
+			if ((p2 - p1 == t->proxy->cookie_len) && (t->proxy->cookie_name != NULL) &&
+			    (memcmp(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 &&
+				(srv->state & SRV_RUNNING || t->proxy->options & PR_O_PERSIST)) {
+				/* we found the server and it's usable */
+				t->flags |= SN_DIRECT;
+				t->srv = srv;
+			    }
+			    /* if this cookie was set in insert+indirect mode, then it's better that the
+			     * server never sees it.
+			     */
+			    if (del_cookie == NULL &&
+				(t->proxy->options & (PR_O_COOK_INS | PR_O_COOK_IND)) == (PR_O_COOK_INS | PR_O_COOK_IND)) {
 				del_cookie = p1;
 				del_colon = colon;
+			    }
 			}
-		    }
-		    else {
-			/* now we know that we must keep this cookie since it's
-			 * not ours. But if we wanted to delete our cookie
-			 * earlier, we cannot remove the complete header, but we
-			 * can remove the previous block itself.
-			 */
-			app_cookies++;
-
-			if (del_cookie != NULL) {
-			    buffer_replace2(req, del_cookie, p1, NULL, 0);
-			    p4  -= (p1 - del_cookie);
-			    ptr -= (p1 - del_cookie);
-			    del_cookie = del_colon = NULL;
+			else {
+			    /* now we know that we must keep this cookie since it's
+			     * not ours. But if we wanted to delete our cookie
+			     * earlier, we cannot remove the complete header, but we
+			     * can remove the previous block itself.
+			     */
+			    app_cookies++;
+			    
+			    if (del_cookie != NULL) {
+				buffer_replace2(req, del_cookie, p1, NULL, 0);
+				p4  -= (p1 - del_cookie);
+				ptr -= (p1 - del_cookie);
+				del_cookie = del_colon = NULL;
+			    }
 			}
 		    }
 
@@ -2565,14 +2692,11 @@
 	 */
 	if (req->l >= req->rlim - req->data) {
 	    t->logs.status = 400;
-	    if (t->proxy->mode == PR_MODE_HTTP)
-		client_retnclose(t, strlen(HTTP_400), HTTP_400);
+	    client_retnclose(t, t->proxy->errmsg.len400, t->proxy->errmsg.msg400);
 	    return 1;
 	}
-	else if (t->res_cr == RES_ERROR || t->res_cr == RES_NULL
-	         || tv_cmp2_ms(&t->crexpire, &now) <= 0) {
-
-	    /* read timeout, read error, or last read : give up.
+	else if (t->res_cr == RES_ERROR || t->res_cr == RES_NULL) {
+	    /* read error, or last read : give up.
 	     * since we are in header mode, if there's no space left for headers, we
 	     * won't be able to free more later, so the session will never terminate.
 	     */
@@ -2581,6 +2705,14 @@
 	    t->cli_state = CL_STCLOSE;
 	    return 1;
 	}
+	else if (tv_cmp2_ms(&t->crexpire, &now) <= 0) {
+
+	    /* read timeout : give up with an error message.
+	     */
+	    t->logs.status = 408;
+	    client_retnclose(t, t->proxy->errmsg.len408, t->proxy->errmsg.msg408);
+	    return 1;
+	}
 
 	return t->cli_state != CL_STHEADERS;
     }
@@ -2766,9 +2898,9 @@
 		    /* if conn_retries < 0 or other error, let's abort */
 		    tv_eternity(&t->cnexpire);
 		    t->srv_state = SV_STCLOSE;
-		    t->logs.status = 502;
+		    t->logs.status = 503;
 		    if (t->proxy->mode == PR_MODE_HTTP)
-			client_return(t, strlen(HTTP_502), HTTP_502);
+			client_return(t, t->proxy->errmsg.len503, t->proxy->errmsg.msg503);
 		}
 	    }
 	    return 1;
@@ -2797,9 +2929,9 @@
 	    /* if conn_retries < 0 or other error, let's abort */
 	    tv_eternity(&t->cnexpire);
 	    t->srv_state = SV_STCLOSE;
-	    t->logs.status = 502;
+	    t->logs.status = 503;
 	    if (t->proxy->mode == PR_MODE_HTTP)
-		client_return(t, strlen(HTTP_502), HTTP_502);
+		client_return(t, t->proxy->errmsg.len503, t->proxy->errmsg.msg503);
 	    return 1;
 	}
 	else { /* no error or write 0 */
@@ -2855,7 +2987,8 @@
 		     * requests and this one isn't.
 		     */
 		    len = sprintf(trash, "Set-Cookie: %s=%s; path=/\r\n",
-				  t->proxy->cookie_name, t->srv->cookie);
+				  t->proxy->cookie_name,
+				  t->srv->cookie ? t->srv->cookie : "");
 
 		    /* Here, we will tell an eventual cache on the client side that we don't
 		     * want it to cache this reply because HTTP/1.0 caches also cache cookies !
@@ -2963,9 +3096,10 @@
 	    }
 	    
 	    /* check for server cookies */
-	    if (!delete_header && (t->proxy->options & PR_O_COOK_ANY)
-		&& (t->proxy->cookie_name != NULL) && (ptr >= rep->h + 12)
-		&& (strncmp(rep->h, "Set-Cookie: ", 12) == 0)) {
+	    if (!delete_header /*&& (t->proxy->options & PR_O_COOK_ANY)*/
+		&& (t->proxy->cookie_name != NULL || t->proxy->capture_name != NULL)
+		&& (ptr >= rep->h + 12)
+		&& (memcmp(rep->h, "Set-Cookie: ", 12) == 0)) {
 		char *p1, *p2, *p3, *p4;
 		
 		p1 = rep->h + 12; /* first char after 'Set-Cookie: ' */
@@ -2998,9 +3132,26 @@
 		     * 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)) {
+
+		    /* first, let's see if we want to capture it */
+		    if (t->proxy->capture_name != NULL &&
+			t->logs.srv_cookie == NULL &&
+			(p4 - p1 >= t->proxy->capture_namelen) &&
+			memcmp(p1, t->proxy->capture_name, t->proxy->capture_namelen) == 0) {
+			int log_len = p4 - p1;
+
+			if ((t->logs.srv_cookie = pool_alloc(capture)) == NULL) {
+			    Alert("HTTP logging : out of memory.\n");
+			}
+
+			if (log_len > t->proxy->capture_len)
+			    log_len = t->proxy->capture_len;
+			memcpy(t->logs.srv_cookie, p1, log_len);
+			t->logs.srv_cookie[log_len] = 0;
+		    }
+
+		    if ((p2 - p1 == t->proxy->cookie_len) && (t->proxy->cookie_name != NULL) &&
+			(memcmp(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
@@ -3052,27 +3203,37 @@
 		tv_eternity(&t->srexpire);
 	}
 
-	/* read or write error */
+	/* read error, write error */
 	if (t->res_sw == RES_ERROR || t->res_sr == RES_ERROR) {
 	    tv_eternity(&t->srexpire);
 	    tv_eternity(&t->swexpire);
 	    fd_delete(t->srv_fd);
 	    t->srv_state = SV_STCLOSE;
 	    t->logs.status = 502;
-	    client_return(t, strlen(HTTP_502), HTTP_502);
+	    client_return(t, t->proxy->errmsg.len502, t->proxy->errmsg.msg502);
 	    return 1;
 	}
-	/* read timeout, last read, or end of client write
+	/* end of client write or end of server read.
 	 * since we are in header mode, if there's no space left for headers, we
 	 * won't be able to free more later, so the session will never terminate.
 	 */
-	else if (t->res_sr == RES_NULL || c == CL_STSHUTW || c == CL_STCLOSE
-		 || rep->l >= rep->rlim - rep->data || tv_cmp2_ms(&t->srexpire, &now) <= 0) {
+	else if (t->res_sr == RES_NULL || c == CL_STSHUTW || c == CL_STCLOSE || rep->l >= rep->rlim - rep->data) {
 	    FD_CLR(t->srv_fd, StaticReadEvent);
 	    tv_eternity(&t->srexpire);
 	    shutdown(t->srv_fd, SHUT_RD);
 	    t->srv_state = SV_STSHUTR;
 	    return 1;
+	}	
+	/* read timeout : return a 504 to the client.
+	 */
+	else if (FD_ISSET(t->srv_fd, StaticReadEvent) && tv_cmp2_ms(&t->srexpire, &now) <= 0) {
+	    tv_eternity(&t->srexpire);
+	    tv_eternity(&t->swexpire);
+	    fd_delete(t->srv_fd);
+	    t->srv_state = SV_STCLOSE;
+	    t->logs.status = 504;
+	    client_return(t, t->proxy->errmsg.len504, t->proxy->errmsg.msg504);
+	    return 1;
 	    
 	}	
 	/* write timeout, or last client read and buffer empty */
@@ -3081,7 +3242,7 @@
 	 * some work to do on the headers.
 	 */
 	else if (((/*c == CL_STSHUTR ||*/ c == CL_STCLOSE) && (req->l == 0)) ||
-		 (tv_cmp2_ms(&t->swexpire, &now) <= 0)) {
+		 (FD_ISSET(t->srv_fd, StaticWriteEvent) && tv_cmp2_ms(&t->swexpire, &now) <= 0)) {
 	    FD_CLR(t->srv_fd, StaticWriteEvent);
 	    tv_eternity(&t->swexpire);
 	    shutdown(t->srv_fd, SHUT_WR);
@@ -3366,7 +3527,7 @@
 	else {
 	    if (s->health == s->rise) {
 		if (!(global.mode & MODE_QUIET))
-		    Warning("server %s DOWN.\n", s->id);
+		    Warning("server %s/%s DOWN.\n", s->proxy->id, s->id);
 
 		send_log(s->proxy, LOG_ALERT, "Server %s/%s is DOWN.\n", s->proxy->id, s->id);
 	    }
@@ -3388,7 +3549,7 @@
 	    if (s->health >= s->rise) {
 		if (s->health == s->rise) {
 		    if (!(global.mode & MODE_QUIET))
-			Warning("server %s UP.\n", s->id);
+			Warning("server %s/%s UP.\n", s->proxy->id, s->id);
 		    send_log(s->proxy, LOG_NOTICE, "Server %s/%s is UP.\n", s->proxy->id, s->id);
 		}
 
@@ -3408,7 +3569,7 @@
 	    else {
 		if (s->health == s->rise) {
 		    if (!(global.mode & MODE_QUIET))
-			Warning("server %s DOWN.\n", s->id);
+			Warning("server %s/%s DOWN.\n", s->proxy->id, s->id);
 
 		    send_log(s->proxy, LOG_ALERT, "Server %s/%s is DOWN.\n", s->proxy->id, s->id);
 		}
@@ -3714,6 +3875,33 @@
 }
 
 
+/*
+ * this function dumps every server's state when the process receives SIGHUP.
+ */
+void sig_dump_state(int sig) {
+    struct proxy *p = proxy;
+
+    Warning("SIGHUP received, dumping servers states.\n");
+    while (p) {
+	struct server *s = p->srv;
+
+	send_log(p, LOG_NOTICE, "SIGUP received, dumping servers states.\n");
+	while (s) {
+	    if (s->state & SRV_RUNNING) {
+		Warning("SIGHUP: server %s/%s is UP.\n", p->id, s->id);
+		send_log(p, LOG_NOTICE, "SIGUP: server %s/%s is UP.\n", p->id, s->id);
+	    }
+	    else {
+		Warning("SIGHUP: server %s/%s is DOWN.\n", p->id, s->id);
+		send_log(p, LOG_NOTICE, "SIGHUP: server %s/%s is DOWN.\n", p->id, s->id);
+	    }
+	    s = s->next;
+	}
+	p = p->next;
+    }
+    signal(sig, sig_dump_state);
+}
+
 void dump(int sig) {
     struct task *t, *tnext;
     struct session *s;
@@ -3830,7 +4018,7 @@
     }
     else if (!strcmp(args[0], "log")) {  /* syslog server address */
 	struct sockaddr_in *sa;
-	int facility;
+	int facility, level;
 	
 	if (*(args[1]) == 0 || *(args[2]) == 0) {
 	    Alert("parsing [%s:%d] : <log> expects <address> and <facility> as arguments.\n", file, linenum);
@@ -3845,7 +4033,17 @@
 	    Alert("parsing [%s:%d] : unknown log facility <%s>\n", file, linenum, args[2]);
 	    exit(1);
 	}
-	
+
+	level = 7; /* max syslog level = debug */
+	if (*(args[3])) {
+	    while (level >= 0 && strcmp(log_levels[level], args[3]))
+		level--;
+	    if (level < 0) {
+		Alert("parsing [%s:%d] : unknown optionnal log level <%s>\n", file, linenum, args[3]);
+		exit(1);
+	    }
+	}
+
 	sa = str2sa(args[1]);
 	if (!sa->sin_port)
 	    sa->sin_port = htons(SYSLOG_PORT);
@@ -3853,10 +4051,12 @@
 	if (global.logfac1 == -1) {
 	    global.logsrv1 = *sa;
 	    global.logfac1 = facility;
+	    global.loglev1 = level;
 	}
 	else if (global.logfac2 == -1) {
 	    global.logsrv2 = *sa;
 	    global.logfac2 = facility;
+	    global.loglev2 = level;
 	}
 	else {
 	    Alert("parsing [%s:%d] : too many syslog servers\n", file, linenum);
@@ -3936,6 +4136,7 @@
 	    return -1;
 	}
 	curproxy->cookie_name = strdup(args[1]);
+	curproxy->cookie_len = strlen(curproxy->cookie_name);
 	
 	cur_arg = 2;
 	while (*(args[cur_arg])) {
@@ -3967,6 +4168,27 @@
 	    return -1;
 	}
     }
+    else if (!strcmp(args[0], "capture")) {  /* name of a cookie to capture */
+	if (curproxy->capture_name != NULL) {
+	    Alert("parsing [%s:%d] : capture already specified. Continuing.\n",
+		  file, linenum);
+	    return 0;
+	}
+	
+	if (*(args[4]) == 0) {
+	    Alert("parsing [%s:%d] : <capture> expects 'cookie' <cookie_name> 'len' <len>.\n",
+		  file, linenum);
+	    return -1;
+	}
+	curproxy->capture_name = strdup(args[2]);
+	curproxy->capture_namelen = strlen(curproxy->capture_name);
+	curproxy->capture_len = atol(args[4]);
+	if (curproxy->capture_len >= CAPTURE_LEN) {
+	    Warning("parsing [%s:%d] : truncating capture length to %d bytes.\n",
+		    file, linenum, CAPTURE_LEN - 1);
+	    curproxy->capture_len = CAPTURE_LEN - 1;
+	}
+    }
     else if (!strcmp(args[0], "contimeout")) {  /* connect timeout */
 	if (curproxy->contimeout != 0) {
 	    Alert("parsing [%s:%d] : contimeout already specified. Continuing.\n", file, linenum);
@@ -4043,6 +4265,10 @@
 	    /* use HTTP request to check servers' health */
 	    curproxy->options |= PR_O_HTTP_CHK;
 	}
+	else if (!strcmp(args[1], "persist")) {
+	    /* persist on using the server specified by the cookie, even when it's down */
+	    curproxy->options |= PR_O_PERSIST;
+	}
 	else {
 	    Alert("parsing [%s:%d] : unknown option <%s>.\n", file, linenum, args[1]);
 	    return -1;
@@ -4136,6 +4362,10 @@
 		newsrv->inter = atol(args[cur_arg + 1]);
 		cur_arg += 2;
 	    }
+	    else if (!strcmp(args[cur_arg], "backup")) {
+		newsrv->state |= SRV_BACKUP;
+		cur_arg ++;
+	    }
 	    else if (!strcmp(args[cur_arg], "check")) {
 		struct task *t;
 		
@@ -4173,10 +4403,14 @@
 	if (*(args[1]) && *(args[2]) == 0 && !strcmp(args[1], "global")) {
 	    curproxy->logfac1 = global.logfac1;
 	    curproxy->logsrv1 = global.logsrv1;
+	    curproxy->loglev1 = global.loglev1;
 	    curproxy->logfac2 = global.logfac2;
 	    curproxy->logsrv2 = global.logsrv2;
+	    curproxy->loglev2 = global.loglev2;
 	}
 	else if (*(args[1]) && *(args[2])) {
+	    int level;
+
 	    for (facility = 0; facility < NB_LOG_FACILITIES; facility++)
 		if (!strcmp(log_facilities[facility], args[2]))
 		    break;
@@ -4186,6 +4420,16 @@
 		exit(1);
 	    }
 	    
+	    level = 7; /* max syslog level = debug */
+	    if (*(args[3])) {
+		while (level >= 0 && strcmp(log_levels[level], args[3]))
+		     level--;
+		if (level < 0) {
+		    Alert("parsing [%s:%d] : unknown optionnal log level <%s>\n", file, linenum, args[3]);
+		    exit(1);
+		}
+	    }
+
 	    sa = str2sa(args[1]);
 	    if (!sa->sin_port)
 		sa->sin_port = htons(SYSLOG_PORT);
@@ -4193,10 +4437,12 @@
 	    if (curproxy->logfac1 == -1) {
 		curproxy->logsrv1 = *sa;
 		curproxy->logfac1 = facility;
+		curproxy->loglev1 = level;
 	    }
 	    else if (curproxy->logfac2 == -1) {
 		curproxy->logsrv2 = *sa;
 		curproxy->logfac2 = facility;
+		curproxy->loglev2 = level;
 	    }
 	    else {
 		Alert("parsing [%s:%d] : too many syslog servers\n", file, linenum);
@@ -4441,6 +4687,80 @@
 	
 	curproxy->rsp_add[curproxy->nb_rspadd++] = strdup(args[1]);
     }
+    else if (!strcmp(args[0], "errorloc")) { /* error location */
+	int errnum;
+	char *err;
+
+	if (*(args[2]) == 0) {
+	    Alert("parsing [%s:%d] : <errorloc> expects <error> and <url> as arguments.\n", file, linenum);
+	    return -1;
+	}
+
+	errnum = atol(args[1]);
+	err = malloc(strlen(HTTP_302) + strlen(args[2]) + 5);
+	sprintf(err, "%s%s\r\n\r\n", HTTP_302, args[2]);
+
+	if (errnum == 400) {
+	    if (curproxy->errmsg.msg400) {
+		Warning("parsing [%s:%d] : error %d already defined.\n", file, linenum, errnum);
+		free(curproxy->errmsg.msg400);
+	    }
+	    curproxy->errmsg.msg400 = err;
+	    curproxy->errmsg.len400 = strlen(err);
+	}
+	else if (errnum == 403) {
+	    if (curproxy->errmsg.msg403) {
+		Warning("parsing [%s:%d] : error %d already defined.\n", file, linenum, errnum);
+		free(curproxy->errmsg.msg403);
+	    }
+	    curproxy->errmsg.msg403 = err;
+	    curproxy->errmsg.len403 = strlen(err);
+	}
+	else if (errnum == 408) {
+	    if (curproxy->errmsg.msg408) {
+		Warning("parsing [%s:%d] : error %d already defined.\n", file, linenum, errnum);
+		free(curproxy->errmsg.msg408);
+	    }
+	    curproxy->errmsg.msg408 = err;
+	    curproxy->errmsg.len408 = strlen(err);
+	}
+	else if (errnum == 500) {
+	    if (curproxy->errmsg.msg500) {
+		Warning("parsing [%s:%d] : error %d already defined.\n", file, linenum, errnum);
+		free(curproxy->errmsg.msg500);
+	    }
+	    curproxy->errmsg.msg500 = err;
+	    curproxy->errmsg.len500 = strlen(err);
+	}
+	else if (errnum == 502) {
+	    if (curproxy->errmsg.msg502) {
+		Warning("parsing [%s:%d] : error %d already defined.\n", file, linenum, errnum);
+		free(curproxy->errmsg.msg502);
+	    }
+	    curproxy->errmsg.msg502 = err;
+	    curproxy->errmsg.len502 = strlen(err);
+	}
+	else if (errnum == 503) {
+	    if (curproxy->errmsg.msg503) {
+		Warning("parsing [%s:%d] : error %d already defined.\n", file, linenum, errnum);
+		free(curproxy->errmsg.msg503);
+	    }
+	    curproxy->errmsg.msg503 = err;
+	    curproxy->errmsg.len503 = strlen(err);
+	}
+	else if (errnum == 504) {
+	    if (curproxy->errmsg.msg504) {
+		Warning("parsing [%s:%d] : error %d already defined.\n", file, linenum, errnum);
+		free(curproxy->errmsg.msg504);
+	    }
+	    curproxy->errmsg.msg504 = err;
+	    curproxy->errmsg.len504 = strlen(err);
+	}
+	else {
+	    Warning("parsing [%s:%d] : error %d relocation will be ignored.\n", file, linenum, errnum);
+	    free(err);
+	}
+    }
     else {
 	Alert("parsing [%s:%d] : unknown keyword <%s> in <listen> section\n", file, linenum, args[0]);
 	return -1;
@@ -4637,6 +4957,34 @@
 		}
 	    }
 	}
+	if (curproxy->errmsg.msg400 == NULL) {
+	    curproxy->errmsg.msg400 = (char *)HTTP_400;
+	    curproxy->errmsg.len400 = strlen(HTTP_400);
+	}
+	if (curproxy->errmsg.msg403 == NULL) {
+	    curproxy->errmsg.msg403 = (char *)HTTP_403;
+	    curproxy->errmsg.len403 = strlen(HTTP_403);
+	}
+	if (curproxy->errmsg.msg408 == NULL) {
+	    curproxy->errmsg.msg408 = (char *)HTTP_408;
+	    curproxy->errmsg.len408 = strlen(HTTP_408);
+	}
+	if (curproxy->errmsg.msg500 == NULL) {
+	    curproxy->errmsg.msg500 = (char *)HTTP_500;
+	    curproxy->errmsg.len500 = strlen(HTTP_500);
+	}
+	if (curproxy->errmsg.msg502 == NULL) {
+	    curproxy->errmsg.msg502 = (char *)HTTP_502;
+	    curproxy->errmsg.len502 = strlen(HTTP_502);
+	}
+	if (curproxy->errmsg.msg503 == NULL) {
+	    curproxy->errmsg.msg503 = (char *)HTTP_503;
+	    curproxy->errmsg.len503 = strlen(HTTP_503);
+	}
+	if (curproxy->errmsg.msg504 == NULL) {
+	    curproxy->errmsg.msg504 = (char *)HTTP_504;
+	    curproxy->errmsg.len504 = strlen(HTTP_504);
+	}
 	curproxy = curproxy->next;
     }
     if (cfgerr > 0) {
@@ -4855,6 +5203,7 @@
 
     signal(SIGQUIT, dump);
     signal(SIGUSR1, sig_soft_stop);
+    signal(SIGHUP, sig_dump_state);
 
     /* on very high loads, a sigpipe sometimes happen just between the
      * getsockopt() which tells "it's OK to write", and the following write :-(
diff --git a/init.d/haproxy b/init.d/haproxy
index 89e3b83..295d50a 100644
--- a/init.d/haproxy
+++ b/init.d/haproxy
@@ -36,6 +36,18 @@
    if [ ! -z "$pids" ]; then
       echo "Asking $PNAME to terminate gracefully..."
       kill -USR1 $pids
+      echo "(use kill $pids to stop immediately)."
+   fi
+}
+
+# dump status
+function do_status {
+   pids=`pidof -o $$ -- $PNAME`
+   if [ ! -z "$pids" ]; then
+      echo "Dumping $PNAME status in logs."
+      kill -HUP $pids
+   else
+      echo "Process $PNAME is not running."
    fi
 }