[MEDIUM] introduce "timeout http-request" in frontends

In order to offer DoS protection, it may be required to lower the maximum
accepted time to receive a complete HTTP request without affecting the client
timeout. This helps protecting against established connections on which
nothing is sent. The client timeout cannot offer a good protection against
this abuse because it is an inactivity timeout, which means that if the
attacker sends one character every now and then, the timeout will not
trigger. With the HTTP request timeout, no matter what speed the client
types, the request will be aborted if it does not complete in time.
diff --git a/doc/configuration.txt b/doc/configuration.txt
index ea3d342..3cd48d3 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -603,6 +603,7 @@
 timeout clitimeout          X          X         X         -  (deprecated)
 timeout connect             X          -         X         X
 timeout contimeout          X          -         X         X  (deprecated)
+timeout httpreq             X          X         X         -
 timeout queue               X          -         X         X
 timeout server              X          -         X         X
 timeout srvtimeout          X          -         X         X  (deprecated)
@@ -952,7 +953,8 @@
   This parameter is provided for compatibility but is currently deprecated.
   Please use "timeout client" instead.
 
-  See also : "timeout client", "timeout server", "srvtimeout".
+  See also : "timeout client", "timeout http-request", "timeout server", and
+             "srvtimeout".
 
 
 contimeout <timeout>
@@ -2060,6 +2062,40 @@
   See also : "timeout queue", "timeout server", "contimeout".
 
 
+timeout http-request <timeout>
+  Set the maximum allowed time to wait for a complete HTTP request
+  May be used in sections :   defaults | frontend | listen | backend
+                                 yes   |    yes   |   yes  |   no
+  Arguments :
+    <timeout> is the timeout value is specified in milliseconds by default, but
+              can be in any other unit if the number is suffixed by the unit,
+              as explained at the top of this document.
+
+  In order to offer DoS protection, it may be required to lower the maximum
+  accepted time to receive a complete HTTP request without affecting the client
+  timeout. This helps protecting against established connections on which
+  nothing is sent. The client timeout cannot offer a good protection against
+  this abuse because it is an inactivity timeout, which means that if the
+  attacker sends one character every now and then, the timeout will not
+  trigger. With the HTTP request timeout, no matter what speed the client
+  types, the request will be aborted if it does not complete in time.
+
+  Note that this timeout only applies to the header part of the request, and
+  not to any data. As soon as the empty line is received, this timeout is not
+  used anymore.
+
+  Generally it is enough to set it to a few seconds, as most clients send the
+  full request immediately upon connection. Add 3 or more seconds to cover TCP
+  retransmits but that's all. Setting it to very low values (eg: 50 ms) will
+  generally work on local networks as long as there are no packet losses. This
+  will prevent people from sending bare HTTP requests using telnet.
+
+  If this parameter is not set, the client timeout still applies between each
+  chunk of the incoming request.
+
+  See also : "timeout client".
+
+
 2.3) Using ACLs
 ---------------
 
diff --git a/include/types/proto_http.h b/include/types/proto_http.h
index c5e051e..6453061 100644
--- a/include/types/proto_http.h
+++ b/include/types/proto_http.h
@@ -241,6 +241,7 @@
 	char *srv_cookie;		/* cookie presented by the server, in capture mode */
 	int status;			/* HTTP status from the server, negative if from proxy */
 	unsigned int flags;             /* transaction flags */
+	struct timeval exp;             /* expiration date for the transaction (generally a request) */
 };
 
 /* This structure is used by http_find_header() to return values of headers.
diff --git a/include/types/proxy.h b/include/types/proxy.h
index 8a72d82..5d397d4 100644
--- a/include/types/proxy.h
+++ b/include/types/proxy.h
@@ -176,7 +176,8 @@
 		struct timeval queue;           /* queue timeout, defaults to connect if unspecified */
 		struct timeval connect;		/* connect timeout (in milliseconds) */
 		struct timeval server;		/* server I/O timeout (in milliseconds) */
-		struct timeval appsession;
+		struct timeval appsession;	/* appsession cookie expiration */
+		struct timeval httpreq;		/* maximum time for complete HTTP request */
 	} timeout;
 	char *id;				/* proxy id */
 	struct list pendconns;			/* pending connections with no server assigned yet */
diff --git a/src/cfgparse.c b/src/cfgparse.c
index 655a40e..b9cf62f 100644
--- a/src/cfgparse.c
+++ b/src/cfgparse.c
@@ -521,6 +521,7 @@
 	tv_eternity(&defproxy.timeout.appsession);
 	tv_eternity(&defproxy.timeout.queue);
 	tv_eternity(&defproxy.timeout.tarpit);
+	tv_eternity(&defproxy.timeout.httpreq);
 }
 
 /*
@@ -603,6 +604,7 @@
 		tv_eternity(&curproxy->timeout.appsession);
 		tv_eternity(&curproxy->timeout.queue);
 		tv_eternity(&curproxy->timeout.tarpit);
+		tv_eternity(&curproxy->timeout.httpreq);
 
 		curproxy->last_change = now.tv_sec;
 		curproxy->id = strdup(args[1]);
@@ -663,6 +665,7 @@
 		if (curproxy->cap & PR_CAP_FE) {
 			curproxy->timeout.client = defproxy.timeout.client;
 			curproxy->timeout.tarpit = defproxy.timeout.tarpit;
+			curproxy->timeout.httpreq = defproxy.timeout.httpreq;
 			curproxy->uri_auth  = defproxy.uri_auth;
 			curproxy->mon_net = defproxy.mon_net;
 			curproxy->mon_mask = defproxy.mon_mask;
diff --git a/src/client.c b/src/client.c
index cfcfb0c..d1d763d 100644
--- a/src/client.c
+++ b/src/client.c
@@ -390,6 +390,7 @@
 		tv_eternity(&s->req->cex);
 		tv_eternity(&s->rep->rex);
 		tv_eternity(&s->rep->wex);
+		tv_eternity(&s->txn.exp);
 		tv_eternity(&t->expire);
 
 		if (tv_isset(&s->fe->timeout.client)) {
@@ -403,6 +404,11 @@
 			}
 		}
 
+		if (s->cli_state == CL_STHEADERS && tv_isset(&s->fe->timeout.httpreq)) {
+			tv_add(&s->txn.exp, &now, &s->fe->timeout.httpreq);
+			tv_bound(&t->expire, &s->txn.exp);
+		}
+
 		task_queue(t);
 
 		if (p->mode != PR_MODE_HEALTH)
diff --git a/src/proto_http.c b/src/proto_http.c
index f387d0b..16028ce 100644
--- a/src/proto_http.c
+++ b/src/proto_http.c
@@ -620,11 +620,12 @@
 		s->req->flags &= BF_CLEAR_READ & BF_CLEAR_WRITE;
 		s->rep->flags &= BF_CLEAR_READ & BF_CLEAR_WRITE;
 
-		t->expire = s->req->rex;
 		tv_min(&t->expire, &s->req->rex, &s->req->wex);
 		tv_bound(&t->expire, &s->req->cex);
 		tv_bound(&t->expire, &s->rep->rex);
 		tv_bound(&t->expire, &s->rep->wex);
+		if (s->cli_state == CL_STHEADERS)
+			tv_bound(&t->expire, &s->txn.exp);
 
 		/* restore t to its place in the task list */
 		task_queue(t);
@@ -1585,7 +1586,8 @@
 			}
 
 			/* 3: has the read timeout expired ? */
-			else if (unlikely(tv_isle(&req->rex, &now))) {
+			else if (unlikely(tv_isle(&req->rex, &now) ||
+					  tv_isle(&txn->exp, &now))) {
 				/* read timeout : give up with an error message. */
 				txn->status = 408;
 				client_retnclose(t, error_message(t, HTTP_ERR_408));
diff --git a/src/proxy.c b/src/proxy.c
index 82bb2ae..e2e6b15 100644
--- a/src/proxy.c
+++ b/src/proxy.c
@@ -104,6 +104,10 @@
 		tv = &proxy->timeout.tarpit;
 		td = &defpx->timeout.tarpit;
 		cap = PR_CAP_FE;
+	} else if (!strcmp(args[0], "http-request")) {
+		tv = &proxy->timeout.httpreq;
+		td = &defpx->timeout.httpreq;
+		cap = PR_CAP_FE;
 	} else if (!strcmp(args[0], "server") || !strcmp(args[0], "srvtimeout")) {
 		name = "server";
 		tv = &proxy->timeout.server;
@@ -123,7 +127,9 @@
 		td = &defpx->timeout.queue;
 		cap = PR_CAP_BE;
 	} else {
-		snprintf(err, errlen, "timeout '%s': must be 'client', 'server', 'connect', 'appsession', 'queue', or 'tarpit'",
+		snprintf(err, errlen,
+			 "timeout '%s': must be 'client', 'server', 'connect', "
+			 "'appsession', 'queue', 'http-request' or 'tarpit'",
 			 args[0]);
 		return -1;
 	}
diff --git a/tests/test-timeout.cfg b/tests/test-timeout.cfg
index 7053f5c..1c08425 100644
--- a/tests/test-timeout.cfg
+++ b/tests/test-timeout.cfg
@@ -10,6 +10,7 @@
         retries    1
         redispatch
         timeout    client  15m
+        timeout    http-request 6s
         timeout    tarpit  20s
         timeout    queue   60s
         timeout    connect 5s