[MEDIUM] http: capture invalid requests/responses even if accepted

It's useful to be able to accept an invalid header name in a request
or response but still be able to monitor further such errors. Now,
when an invalid request/response is received and accepted due to
an "accept-invalid-http-{request|response}" option, the invalid
request will be captured for later analysis with "show errors" on
the stats socket.
diff --git a/doc/configuration.txt b/doc/configuration.txt
index 68f946d..32a2b7d 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -592,6 +592,10 @@
 monitor-net                 X          X         X         -
 monitor-uri                 X          X         X         -
 [no] option abortonclose    X          -         X         X
+[no] option accept-invalid-
+            http-request    X          X         X         -
+[no] option accept-invalid-
+            http-response   X          -         X         X
 [no] option allbackups      X          -         X         X
 [no] option checkcache      X          -         X         X
 [no] option clitcpka        X          X         X         -
@@ -1806,6 +1810,72 @@
   See also : "timeout queue" and server's "maxconn" and "maxqueue" parameters
 
 
+option accept-invalid-http-request
+no option accept-invalid-http-request
+  Enable or disable relaxing of HTTP request parsing
+  May be used in sections :   defaults | frontend | listen | backend
+                                 yes   |    yes   |   yes  |   no
+  Arguments : none
+
+  By default, HAProxy complies with RFC2616 in terms of message parsing. This
+  means that invalid characters in header names are not permitted and cause an
+  error to be returned to the client. This is the desired behaviour as such
+  forbidden characters are essentially used to build attacks exploiting server
+  weaknesses, and bypass security filtering. Sometimes, a buggy browser or
+  server will emit invalid header names for whatever reason (configuration,
+  implementation) and the issue will not be immediately fixed. In such a case,
+  it is possible to relax HAProxy's header name parser to accept any character
+  even if that does not make sense, by specifying this option.
+
+  This option should never be enabled by default as it hides application bugs
+  and open security breaches. It should only be deployed after a problem has
+  been confirmed.
+
+  When this option is enabled, erroneous header names will still be accepted in
+  requests, but the complete request will be captured in order to permit later
+  analysis using the "show errors" request on the UNIX stats socket. Doing this
+  also helps confirming that the issue has been solved.
+
+  If this option has been enabled in a "defaults" section, it can be disabled
+  in a specific instance by prepending the "no" keyword before it.
+
+  See also : "option accept-invalid-http-response" and "show errors" on the
+             stats socket.
+
+
+option accept-invalid-http-response
+no option accept-invalid-http-response
+  Enable or disable relaxing of HTTP response parsing
+  May be used in sections :   defaults | frontend | listen | backend
+                                 yes   |     no   |   yes  |   yes
+  Arguments : none
+
+  By default, HAProxy complies with RFC2616 in terms of message parsing. This
+  means that invalid characters in header names are not permitted and cause an
+  error to be returned to the client. This is the desired behaviour as such
+  forbidden characters are essentially used to build attacks exploiting server
+  weaknesses, and bypass security filtering. Sometimes, a buggy browser or
+  server will emit invalid header names for whatever reason (configuration,
+  implementation) and the issue will not be immediately fixed. In such a case,
+  it is possible to relax HAProxy's header name parser to accept any character
+  even if that does not make sense, by specifying this option.
+
+  This option should never be enabled by default as it hides application bugs
+  and open security breaches. It should only be deployed after a problem has
+  been confirmed.
+
+  When this option is enabled, erroneous header names will still be accepted in
+  responses, but the complete response will be captured in order to permit
+  later analysis using the "show errors" request on the UNIX stats socket.
+  Doing this also helps confirming that the issue has been solved.
+
+  If this option has been enabled in a "defaults" section, it can be disabled
+  in a specific instance by prepending the "no" keyword before it.
+
+  See also : "option accept-invalid-http-request" and "show errors" on the
+             stats socket.
+
+
 option allbackups
 no option allbackups
   Use either all backup servers at a time or only the first one
diff --git a/include/proto/proto_http.h b/include/proto/proto_http.h
index 41f1922..175eb04 100644
--- a/include/proto/proto_http.h
+++ b/include/proto/proto_http.h
@@ -86,6 +86,9 @@
 void http_sess_log(struct session *s);
 void perform_http_redirect(struct session *s, struct stream_interface *si);
 void http_return_srv_error(struct session *s, struct stream_interface *si);
+void http_capture_bad_message(struct error_snapshot *es, struct session *s,
+                              struct buffer *buf, struct http_msg *msg,
+			      struct proxy *other_end);
 
 #endif /* _PROTO_PROTO_HTTP_H */
 
diff --git a/src/proto_http.c b/src/proto_http.c
index a77ab79..3325b31 100644
--- a/src/proto_http.c
+++ b/src/proto_http.c
@@ -1614,6 +1614,8 @@
 		/* 2: have we encountered a read error ? */
 		else if (req->flags & BF_READ_ERROR) {
 			/* we cannot return any message on error */
+			if (msg->err_pos >= 0)
+				http_capture_bad_message(&s->fe->invalid_req, s, req, msg, s->fe);
 			msg->msg_state = HTTP_MSG_ERROR;
 			req->analysers = 0;
 			s->fe->failed_req++;
@@ -1627,6 +1629,8 @@
 		/* 3: has the read timeout expired ? */
 		else if (req->flags & BF_READ_TIMEOUT || tick_is_expired(req->analyse_exp, now_ms)) {
 			/* read timeout : give up with an error message. */
+			if (msg->err_pos >= 0)
+				http_capture_bad_message(&s->fe->invalid_req, s, req, msg, s->fe);
 			txn->status = 408;
 			stream_int_retnclose(req->prod, error_message(s, HTTP_ERR_408));
 			msg->msg_state = HTTP_MSG_ERROR;
@@ -1641,6 +1645,8 @@
 
 		/* 4: have we encountered a close ? */
 		else if (req->flags & BF_SHUTR) {
+			if (msg->err_pos >= 0)
+				http_capture_bad_message(&s->fe->invalid_req, s, req, msg, s->fe);
 			txn->status = 400;
 			stream_int_retnclose(req->prod, error_message(s, HTTP_ERR_400));
 			msg->msg_state = HTTP_MSG_ERROR;
@@ -1672,6 +1678,9 @@
 	 * of each header's length, so we can parse them quickly.       *
 	 ****************************************************************/
 
+	if (msg->err_pos >= 0)
+		http_capture_bad_message(&s->fe->invalid_req, s, req, msg, s->fe);
+
 	req->analysers &= ~AN_REQ_HTTP_HDR;
 	req->analyse_exp = TICK_ETERNITY;
 
@@ -2331,21 +2340,13 @@
 	return 1;
 
  return_bad_req: /* let's centralize all bad requests */
-	if (unlikely(msg->msg_state == HTTP_MSG_ERROR)) {
+	if (unlikely(msg->msg_state == HTTP_MSG_ERROR) || msg->err_pos >= 0) {
 		/* we detected a parsing error. We want to archive this request
 		 * in the dedicated proxy area for later troubleshooting.
 		 */
-		struct error_snapshot *es = &s->fe->invalid_req;
-		int maxlen = MIN(req->r - req->data + msg->som, sizeof(es->buf));
-		memcpy(es->buf, req->data + msg->som, maxlen);
-		es->pos  = req->lr - req->data + msg->som;
-		es->len  = req->r - req->data + msg->som;
-		es->when = date; // user-visible date
-		es->sid  = s->uniq_id;
-		es->srv  = s->srv;
-		es->oe   = s->be;
-		es->src  = s->cli_addr;
+		http_capture_bad_message(&s->fe->invalid_req, s, req, msg, s->fe);
 	}
+
 	txn->req.msg_state = HTTP_MSG_ERROR;
 	txn->status = 400;
 	req->analysers = 0;
@@ -2570,18 +2571,10 @@
 				/* we detected a parsing error. We want to archive this response
 				 * in the dedicated proxy area for later troubleshooting.
 				 */
-				struct error_snapshot *es = &t->be->invalid_rep;
-				int maxlen = MIN(rep->r - rep->data + msg->som, sizeof(es->buf));
-				memcpy(es->buf, rep->data + msg->som, maxlen);
-				es->pos = rep->lr - rep->data + msg->som;
-				es->len = rep->r - rep->data + msg->som;
-				es->when = date; // user-visible date
-				es->sid = t->uniq_id;
-				es->srv = t->srv;
-				es->oe = t->fe;
-				es->src = t->cli_addr;
-
 			hdr_response_bad:
+				if (msg->msg_state == HTTP_MSG_ERROR || msg->err_pos >= 0)
+					http_capture_bad_message(&t->be->invalid_rep, t, rep, msg, t->fe);
+
 				buffer_shutr_now(rep);
 				buffer_shutw_now(req);
 				if (t->srv)
@@ -2603,6 +2596,8 @@
 			}
 			/* read error */
 			else if (rep->flags & BF_READ_ERROR) {
+				if (msg->err_pos >= 0)
+					http_capture_bad_message(&t->be->invalid_rep, t, rep, msg, t->fe);
 				buffer_shutr_now(rep);
 				buffer_shutw_now(req);
 				if (t->srv)
@@ -2619,6 +2614,8 @@
 			}
 			/* read timeout : return a 504 to the client. */
 			else if (rep->flags & BF_READ_TIMEOUT) {
+				if (msg->err_pos >= 0)
+					http_capture_bad_message(&t->be->invalid_rep, t, rep, msg, t->fe);
 				buffer_shutr_now(rep);
 				buffer_shutw_now(req);
 				if (t->srv)
@@ -2635,6 +2632,8 @@
 			}
 			/* close from server */
 			else if (rep->flags & BF_SHUTR) {
+				if (msg->err_pos >= 0)
+					http_capture_bad_message(&t->be->invalid_rep, t, rep, msg, t->fe);
 				buffer_shutw_now(req);
 				if (t->srv)
 					t->srv->failed_resp++;
@@ -2650,6 +2649,8 @@
 			}
 			/* write error to client (we don't send any message then) */
 			else if (rep->flags & BF_WRITE_ERROR) {
+				if (msg->err_pos >= 0)
+					http_capture_bad_message(&t->be->invalid_rep, t, rep, msg, t->fe);
 				buffer_shutr_now(rep);
 				t->be->failed_resp++;
 				rep->analysers = 0;
@@ -2670,6 +2671,9 @@
 		 * of each header's length, so we can parse them quickly.        *
 		 ****************************************************************/
 
+		if (msg->err_pos >= 0)
+			http_capture_bad_message(&t->be->invalid_rep, t, rep, msg, t->fe);
+
 		rep->analysers &= ~AN_RTR_HTTP_HDR;
 
 		/* ensure we keep this pointer to the beginning of the message */
@@ -4381,6 +4385,27 @@
 	return 1;
 }
 
+/*
+ * Capture a bad request or response and archive it in the proxy's structure.
+ */
+void http_capture_bad_message(struct error_snapshot *es, struct session *s,
+                              struct buffer *buf, struct http_msg *msg,
+			      struct proxy *other_end)
+{
+	int maxlen = MIN(buf->r - buf->data + msg->som, sizeof(es->buf));
+
+	memcpy(es->buf, buf->data + msg->som, maxlen);
+	if (msg->err_pos >= 0)
+		es->pos  = msg->err_pos + msg->som;
+	else
+		es->pos  = buf->lr - buf->data + msg->som;
+	es->len  = buf->r - buf->data + msg->som;
+	es->when = date; // user-visible date
+	es->sid  = s->uniq_id;
+	es->srv  = s->srv;
+	es->oe   = other_end;
+	es->src  = s->cli_addr;
+}
 
 /*
  * Print a debug line with a header