BUG/MINOR: http: report real parser state in error captures

Error captures almost always report a state 26 (MSG_ERROR) making it
very hard to know what the parser was expecting. The reason is that
we have to switch to MSG_ERROR to trigger the dump, and then during
the dump we capture the current state which is already MSG_ERROR. With
this change we now copy the current state into an err_state field that
will be reported as the faulty state.

This patch looks a bit large because the parser doesn't update the
current state until it runs out of data so the current state is never
known when jumping to ther error label! Thus the code had to be updated
to take copies of the current state before switching to MSG_ERROR based
on the switch/case values.

As a bonus, it now shows the current state in human-readable form and
not only in numeric form ; in the past it was not an issue since it was
always 26 (MSG_ERROR).

At least now we can get exploitable invalid request/response reports :

  [05/Jan/2017:19:28:57.095] frontend f (#2): invalid request
    backend <NONE> (#-1), server <NONE> (#-1), event #1
    src 127.0.0.1:39894, session #4, session flags 0x00000080
    HTTP msg state MSG_RQURI(4), msg flags 0x00000000, tx flags 0x00000000
    HTTP chunk len 0 bytes, HTTP body len 0 bytes
    buffer flags 0x00908002, out 0 bytes, total 20 bytes
    pending 20 bytes, wrapping at 16384, error at position 5:

    00000  GET /\e HTTP/1.0\r\n
    00017  \r\n
    00019  \n

  [05/Jan/2017:19:28:33.827] backend b (#3): invalid response
    frontend f (#2), server s1 (#1), event #0
    src 127.0.0.1:39718, session #0, session flags 0x000004ce
    HTTP msg state MSG_HDR_NAME(17), msg flags 0x00000000, tx flags 0x08300000
    HTTP chunk len 0 bytes, HTTP body len 0 bytes
    buffer flags 0x80008002, out 0 bytes, total 59 bytes
    pending 59 bytes, wrapping at 16384, error at position 31:

    00000  HTTP/1.1 200 OK\r\n
    00017  Content-length : 10\r\n
    00038  \r\n
    00040  0a\r\n
    00044  0123456789\r\n
    00056  0\r\n

This should be backported to 1.7 and 1.6 at least to help with bug
reports.
diff --git a/include/types/proto_http.h b/include/types/proto_http.h
index 0c1f50f..66f7397 100644
--- a/include/types/proto_http.h
+++ b/include/types/proto_http.h
@@ -319,8 +319,9 @@
  */
 struct http_msg {
 	enum ht_state msg_state;               /* where we are in the current message parsing */
+	enum ht_state err_state;               /* the state where the parsing error was detected, only is MSG_ERROR */
 	unsigned char flags;                   /* flags describing the message (HTTP version, ...) */
-	/* 6 bytes unused here */
+	/* 5 bytes unused here */
 	struct channel *chn;                   /* pointer to the channel transporting the message */
 	unsigned int next;                     /* pointer to next byte to parse, relative to buf->p */
 	int sov;                               /* current header: start of value ; data: start of body */
diff --git a/src/proto_http.c b/src/proto_http.c
index 0aa7c27..a92e716 100644
--- a/src/proto_http.c
+++ b/src/proto_http.c
@@ -1350,7 +1350,7 @@
 
 /* either we find an LF at <ptr> or we jump to <bad>.
  */
-#define EXPECT_LF_HERE(ptr, bad)	do { if (unlikely(*(ptr) != '\n')) goto bad; } while (0)
+#define EXPECT_LF_HERE(ptr, bad, st)	do { if (unlikely(*(ptr) != '\n')) { state = st; goto bad;}; } while (0)
 
 /* plays with variables <ptr>, <end> and <state>. Jumps to <good> if OK,
  * otherwise to <http_msg_ood> with <state> set to <st>.
@@ -1401,6 +1401,7 @@
 			msg->sl.st.v_l = ptr - msg_start;
 			EAT_AND_JUMP_OR_RETURN(http_msg_rpver_sp, HTTP_MSG_RPVER_SP);
 		}
+		msg->err_state = HTTP_MSG_RPVER;
 		state = HTTP_MSG_ERROR;
 		break;
 
@@ -1413,6 +1414,7 @@
 		if (likely(HTTP_IS_SPHT(*ptr)))
 			EAT_AND_JUMP_OR_RETURN(http_msg_rpver_sp, HTTP_MSG_RPVER_SP);
 		/* so it's a CR/LF, this is invalid */
+		msg->err_state = HTTP_MSG_RPVER_SP;
 		state = HTTP_MSG_ERROR;
 		break;
 
@@ -1525,6 +1527,7 @@
 			msg->sl.rq.v_l = 0;
 			goto http_msg_rqline_eol;
 		}
+		msg->err_state = HTTP_MSG_RQMETH;
 		state = HTTP_MSG_ERROR;
 		break;
 
@@ -1586,6 +1589,7 @@
 		/* OK forbidden chars, 0..31 or 127 */
 	invalid_char:
 		msg->err_pos = ptr - msg_start;
+		msg->err_state = HTTP_MSG_RQURI;
 		state = HTTP_MSG_ERROR;
 		break;
 
@@ -1619,6 +1623,7 @@
 		}
 
 		/* neither an HTTP_VER token nor a CRLF */
+		msg->err_state = HTTP_MSG_RQVER;
 		state = HTTP_MSG_ERROR;
 		break;
 
@@ -1777,8 +1782,10 @@
 			goto http_msg_rpver;
 		}
 
-		if (unlikely(!HTTP_IS_CRLF(*ptr)))
+		if (unlikely(!HTTP_IS_CRLF(*ptr))) {
+			state = HTTP_MSG_RPBEFORE;
 			goto http_msg_invalid;
+		}
 
 		if (unlikely(*ptr == '\n'))
 			EAT_AND_JUMP_OR_RETURN(http_msg_rpbefore, HTTP_MSG_RPBEFORE);
@@ -1787,7 +1794,7 @@
 
 	case HTTP_MSG_RPBEFORE_CR:
 	http_msg_rpbefore_cr:
-		EXPECT_LF_HERE(ptr, http_msg_invalid);
+		EXPECT_LF_HERE(ptr, http_msg_invalid, HTTP_MSG_RPBEFORE_CR);
 		EAT_AND_JUMP_OR_RETURN(http_msg_rpbefore, HTTP_MSG_RPBEFORE);
 		/* stop here */
 
@@ -1816,7 +1823,7 @@
 	case HTTP_MSG_RPLINE_END:
 	http_msg_rpline_end:
 		/* msg->sol must point to the first of CR or LF. */
-		EXPECT_LF_HERE(ptr, http_msg_invalid);
+		EXPECT_LF_HERE(ptr, http_msg_invalid, HTTP_MSG_RPLINE_END);
 		EAT_AND_JUMP_OR_RETURN(http_msg_hdr_first, HTTP_MSG_HDR_FIRST);
 		/* stop here */
 
@@ -1842,8 +1849,10 @@
 			goto http_msg_rqmeth;
 		}
 
-		if (unlikely(!HTTP_IS_CRLF(*ptr)))
+		if (unlikely(!HTTP_IS_CRLF(*ptr))) {
+			state = HTTP_MSG_RQBEFORE;
 			goto http_msg_invalid;
+		}
 
 		if (unlikely(*ptr == '\n'))
 			EAT_AND_JUMP_OR_RETURN(http_msg_rqbefore, HTTP_MSG_RQBEFORE);
@@ -1852,7 +1861,7 @@
 
 	case HTTP_MSG_RQBEFORE_CR:
 	http_msg_rqbefore_cr:
-		EXPECT_LF_HERE(ptr, http_msg_invalid);
+		EXPECT_LF_HERE(ptr, http_msg_invalid, HTTP_MSG_RQBEFORE_CR);
 		EAT_AND_JUMP_OR_RETURN(http_msg_rqbefore, HTTP_MSG_RQBEFORE);
 		/* stop here */
 
@@ -1886,7 +1895,7 @@
 		if (unlikely(msg->sl.rq.v_l == 0))
 			goto http_msg_last_lf;
 
-		EXPECT_LF_HERE(ptr, http_msg_invalid);
+		EXPECT_LF_HERE(ptr, http_msg_invalid, HTTP_MSG_RQLINE_END);
 		EAT_AND_JUMP_OR_RETURN(http_msg_hdr_first, HTTP_MSG_HDR_FIRST);
 		/* stop here */
 
@@ -1913,8 +1922,10 @@
 		if (likely(*ptr == ':'))
 			EAT_AND_JUMP_OR_RETURN(http_msg_hdr_l1_sp, HTTP_MSG_HDR_L1_SP);
 
-		if (likely(msg->err_pos < -1) || *ptr == '\n')
+		if (likely(msg->err_pos < -1) || *ptr == '\n') {
+			state = HTTP_MSG_HDR_NAME;
 			goto http_msg_invalid;
+		}
 
 		if (msg->err_pos == -1) /* capture error pointer */
 			msg->err_pos = ptr - buf->p; /* >= 0 now */
@@ -1941,7 +1952,7 @@
 
 	case HTTP_MSG_HDR_L1_LF:
 	http_msg_hdr_l1_lf:
-		EXPECT_LF_HERE(ptr, http_msg_invalid);
+		EXPECT_LF_HERE(ptr, http_msg_invalid, HTTP_MSG_HDR_L1_LF);
 		EAT_AND_JUMP_OR_RETURN(http_msg_hdr_l1_lws, HTTP_MSG_HDR_L1_LWS);
 
 	case HTTP_MSG_HDR_L1_LWS:
@@ -1998,7 +2009,7 @@
 
 	case HTTP_MSG_HDR_L2_LF:
 	http_msg_hdr_l2_lf:
-		EXPECT_LF_HERE(ptr, http_msg_invalid);
+		EXPECT_LF_HERE(ptr, http_msg_invalid, HTTP_MSG_HDR_L2_LF);
 		EAT_AND_JUMP_OR_RETURN(http_msg_hdr_l2_lws, HTTP_MSG_HDR_L2_LWS);
 
 	case HTTP_MSG_HDR_L2_LWS:
@@ -2018,8 +2029,10 @@
 		 * header into the index.
 		 */
 		if (unlikely(hdr_idx_add(msg->eol - msg->sol, buf->p[msg->eol] == '\r',
-					 idx, idx->tail) < 0))
+					 idx, idx->tail) < 0)) {
+			state = HTTP_MSG_HDR_L2_LWS;
 			goto http_msg_invalid;
+		}
 
 		msg->sol = ptr - buf->p;
 		if (likely(!HTTP_IS_CRLF(*ptr))) {
@@ -2036,7 +2049,7 @@
 		 * Sets ->sov and ->next to the total header length, ->eoh to
 		 * the last CRLF, and ->eol to the last CRLF length (1 or 2).
 		 */
-		EXPECT_LF_HERE(ptr, http_msg_invalid);
+		EXPECT_LF_HERE(ptr, http_msg_invalid, HTTP_MSG_LAST_LF);
 		ptr++;
 		msg->sov = msg->next = ptr - buf->p;
 		msg->eoh = msg->sol;
@@ -2064,6 +2077,7 @@
 
  http_msg_invalid:
 	/* invalid message */
+	msg->err_state = state;
 	msg->msg_state = HTTP_MSG_ERROR;
 	msg->next = ptr - buf->p;
 	return;
@@ -2711,11 +2725,12 @@
 
 			/* we cannot return any message on error */
 			if (msg->err_pos >= 0) {
-				http_capture_bad_message(&sess->fe->invalid_req, s, msg, msg->msg_state, sess->fe);
+				http_capture_bad_message(&sess->fe->invalid_req, s, msg, msg->err_state, sess->fe);
 				stream_inc_http_err_ctr(s);
 			}
 
 			txn->status = 400;
+			msg->err_state = msg->msg_state;
 			msg->msg_state = HTTP_MSG_ERROR;
 			http_reply_and_close(s, txn->status, NULL);
 			req->analysers &= AN_REQ_FLT_END;
@@ -2743,10 +2758,11 @@
 
 			/* read timeout : give up with an error message. */
 			if (msg->err_pos >= 0) {
-				http_capture_bad_message(&sess->fe->invalid_req, s, msg, msg->msg_state, sess->fe);
+				http_capture_bad_message(&sess->fe->invalid_req, s, msg, msg->err_state, sess->fe);
 				stream_inc_http_err_ctr(s);
 			}
 			txn->status = 408;
+			msg->err_state = msg->msg_state;
 			msg->msg_state = HTTP_MSG_ERROR;
 			http_reply_and_close(s, txn->status, http_error_message(s, HTTP_ERR_408));
 			req->analysers &= AN_REQ_FLT_END;
@@ -2774,8 +2790,9 @@
 				goto failed_keep_alive;
 
 			if (msg->err_pos >= 0)
-				http_capture_bad_message(&sess->fe->invalid_req, s, msg, msg->msg_state, sess->fe);
+				http_capture_bad_message(&sess->fe->invalid_req, s, msg, msg->err_state, sess->fe);
 			txn->status = 400;
+			msg->err_state = msg->msg_state;
 			msg->msg_state = HTTP_MSG_ERROR;
 			http_reply_and_close(s, txn->status, http_error_message(s, HTTP_ERR_400));
 			req->analysers &= AN_REQ_FLT_END;
@@ -2866,7 +2883,7 @@
 	 * to block on that, so we have to capture it now.
 	 */
 	if (unlikely(msg->err_pos >= 0))
-		http_capture_bad_message(&sess->fe->invalid_req, s, msg, msg->msg_state, sess->fe);
+		http_capture_bad_message(&sess->fe->invalid_req, s, msg, msg->err_state, sess->fe);
 
 	/*
 	 * 1: identify the method
@@ -3144,9 +3161,10 @@
 		/* we detected a parsing error. We want to archive this request
 		 * in the dedicated proxy area for later troubleshooting.
 		 */
-		http_capture_bad_message(&sess->fe->invalid_req, s, msg, msg->msg_state, sess->fe);
+		http_capture_bad_message(&sess->fe->invalid_req, s, msg, msg->err_state, sess->fe);
 	}
 
+	txn->req.err_state = txn->req.msg_state;
 	txn->req.msg_state = HTTP_MSG_ERROR;
 	txn->status = 400;
 	http_reply_and_close(s, txn->status, http_error_message(s, HTTP_ERR_400));
@@ -4475,9 +4493,10 @@
 		/* we detected a parsing error. We want to archive this request
 		 * in the dedicated proxy area for later troubleshooting.
 		 */
-		http_capture_bad_message(&sess->fe->invalid_req, s, msg, msg->msg_state, sess->fe);
+		http_capture_bad_message(&sess->fe->invalid_req, s, msg, msg->err_state, sess->fe);
 	}
 
+	txn->req.err_state = txn->req.msg_state;
 	txn->req.msg_state = HTTP_MSG_ERROR;
 	txn->status = 400;
 	http_reply_and_close(s, txn->status, http_error_message(s, HTTP_ERR_400));
@@ -4546,6 +4565,7 @@
 
 		/* Note that for now we don't reuse existing proxy connections */
 		if (unlikely((conn = si_alloc_conn(&s->si[1])) == NULL)) {
+			txn->req.err_state = txn->req.msg_state;
 			txn->req.msg_state = HTTP_MSG_ERROR;
 			txn->status = 500;
 			req->analysers &= AN_REQ_FLT_END;
@@ -4818,9 +4838,10 @@
 		/* we detected a parsing error. We want to archive this request
 		 * in the dedicated proxy area for later troubleshooting.
 		 */
-		http_capture_bad_message(&sess->fe->invalid_req, s, msg, msg->msg_state, sess->fe);
+		http_capture_bad_message(&sess->fe->invalid_req, s, msg, msg->err_state, sess->fe);
 	}
 
+	txn->req.err_state = txn->req.msg_state;
 	txn->req.msg_state = HTTP_MSG_ERROR;
 	txn->status = 400;
 	req->analysers &= AN_REQ_FLT_END;
@@ -5012,6 +5033,7 @@
 	return 1;
 
  return_bad_req: /* let's centralize all bad requests */
+	txn->req.err_state = txn->req.msg_state;
 	txn->req.msg_state = HTTP_MSG_ERROR;
 	txn->status = 400;
 	http_reply_and_close(s, txn->status, http_error_message(s, HTTP_ERR_400));
@@ -5405,6 +5427,7 @@
 			goto http_msg_closed;
 		}
 		else if (chn->flags & CF_SHUTW) {
+			txn->req.err_state = txn->req.msg_state;
 			txn->req.msg_state = HTTP_MSG_ERROR;
 			goto wait_other_side;
 		}
@@ -5531,6 +5554,7 @@
 			goto http_msg_closed;
 		}
 		else if (chn->flags & CF_SHUTW) {
+			txn->req.err_state = txn->req.msg_state;
 			txn->rsp.msg_state = HTTP_MSG_ERROR;
 			s->be->be_counters.cli_aborts++;
 			if (objt_server(s->target))
@@ -5665,6 +5689,7 @@
 		/* Output closed while we were sending data. We must abort and
 		 * wake the other side up.
 		 */
+		msg->err_state = msg->msg_state;
 		msg->msg_state = HTTP_MSG_ERROR;
 		http_resync_states(s);
 		return 1;
@@ -5724,7 +5749,6 @@
 	if ((txn->flags & TX_CON_WANT_MSK) != TX_CON_WANT_TUN)
 		channel_dont_close(req);
 
-	ret = msg->msg_state;
 	if (http_resync_states(s)) {
 		/* some state changes occurred, maybe the analyser
 		 * was disabled too. */
@@ -5735,7 +5759,7 @@
 				goto aborted_xfer;
 			}
 			if (msg->err_pos >= 0)
-				http_capture_bad_message(&sess->fe->invalid_req, s, msg, ret, s->be);
+				http_capture_bad_message(&sess->fe->invalid_req, s, msg, msg->err_state, s->be);
 			goto return_bad_req;
 		}
 		return 1;
@@ -5810,6 +5834,7 @@
 		sess->listener->counters->failed_req++;
 
  return_bad_req_stats_ok:
+	txn->req.err_state = txn->req.msg_state;
 	txn->req.msg_state = HTTP_MSG_ERROR;
 	if (txn->status) {
 		/* Note: we don't send any error if some data were already sent */
@@ -5832,6 +5857,7 @@
 	return 0;
 
  aborted_xfer:
+	txn->req.err_state = txn->req.msg_state;
 	txn->req.msg_state = HTTP_MSG_ERROR;
 	if (txn->status) {
 		/* Note: we don't send any error if some data were already sent */
@@ -5968,7 +5994,7 @@
 			 */
 		hdr_response_bad:
 			if (msg->msg_state == HTTP_MSG_ERROR || msg->err_pos >= 0)
-				http_capture_bad_message(&s->be->invalid_rep, s, msg, msg->msg_state, sess->fe);
+				http_capture_bad_message(&s->be->invalid_rep, s, msg, msg->err_state, sess->fe);
 
 			s->be->be_counters.failed_resp++;
 			if (objt_server(s->target)) {
@@ -6001,7 +6027,7 @@
 		/* read error */
 		else if (rep->flags & CF_READ_ERROR) {
 			if (msg->err_pos >= 0)
-				http_capture_bad_message(&s->be->invalid_rep, s, msg, msg->msg_state, sess->fe);
+				http_capture_bad_message(&s->be->invalid_rep, s, msg, msg->err_state, sess->fe);
 			else if (txn->flags & TX_NOT_FIRST)
 				goto abort_keep_alive;
 
@@ -6028,7 +6054,7 @@
 		/* read timeout : return a 504 to the client. */
 		else if (rep->flags & CF_READ_TIMEOUT) {
 			if (msg->err_pos >= 0)
-				http_capture_bad_message(&s->be->invalid_rep, s, msg, msg->msg_state, sess->fe);
+				http_capture_bad_message(&s->be->invalid_rep, s, msg, msg->err_state, sess->fe);
 
 			s->be->be_counters.failed_resp++;
 			if (objt_server(s->target)) {
@@ -6076,7 +6102,7 @@
 		/* close from server, capture the response if the server has started to respond */
 		else if (rep->flags & CF_SHUTR) {
 			if (msg->msg_state >= HTTP_MSG_RPVER || msg->err_pos >= 0)
-				http_capture_bad_message(&s->be->invalid_rep, s, msg, msg->msg_state, sess->fe);
+				http_capture_bad_message(&s->be->invalid_rep, s, msg, msg->err_state, sess->fe);
 			else if (txn->flags & TX_NOT_FIRST)
 				goto abort_keep_alive;
 
@@ -6103,7 +6129,7 @@
 		/* write error to client (we don't send any message then) */
 		else if (rep->flags & CF_WRITE_ERROR) {
 			if (msg->err_pos >= 0)
-				http_capture_bad_message(&s->be->invalid_rep, s, msg, msg->msg_state, sess->fe);
+				http_capture_bad_message(&s->be->invalid_rep, s, msg, msg->err_state, sess->fe);
 			else if (txn->flags & TX_NOT_FIRST)
 				goto abort_keep_alive;
 
@@ -6131,7 +6157,7 @@
 	 */
 
 	if (unlikely(msg->err_pos >= 0))
-		http_capture_bad_message(&s->be->invalid_rep, s, msg, msg->msg_state, sess->fe);
+		http_capture_bad_message(&s->be->invalid_rep, s, msg, msg->err_state, sess->fe);
 
 	/*
 	 * 1: get the status code
@@ -6850,6 +6876,7 @@
 		/* Output closed while we were sending data. We must abort and
 		 * wake the other side up.
 		 */
+		msg->err_state = msg->msg_state;
 		msg->msg_state = HTTP_MSG_ERROR;
 		http_resync_states(s);
 		return 1;
@@ -6886,7 +6913,6 @@
 	    (txn->flags & TX_CON_WANT_MSK) == TX_CON_WANT_SCL)
 		channel_dont_close(res);
 
-	ret = msg->msg_state;
 	if (http_resync_states(s)) {
 		/* some state changes occurred, maybe the analyser was disabled
 		 * too. */
@@ -6897,7 +6923,7 @@
 				goto aborted_xfer;
 			}
 			if (msg->err_pos >= 0)
-				http_capture_bad_message(&s->be->invalid_rep, s, msg, ret, strm_fe(s));
+				http_capture_bad_message(&s->be->invalid_rep, s, msg, msg->err_state, strm_fe(s));
 			goto return_bad_res;
 		}
 		return 1;
@@ -6963,6 +6989,7 @@
 		objt_server(s->target)->counters.failed_resp++;
 
  return_bad_res_stats_ok:
+	txn->rsp.err_state = txn->rsp.msg_state;
 	txn->rsp.msg_state = HTTP_MSG_ERROR;
 	/* don't send any error message as we're in the body */
 	http_reply_and_close(s, txn->status, NULL);
@@ -6978,6 +7005,7 @@
 	return 0;
 
  aborted_xfer:
+	txn->rsp.err_state = txn->rsp.msg_state;
 	txn->rsp.msg_state = HTTP_MSG_ERROR;
 	/* don't send any error message as we're in the body */
 	http_reply_and_close(s, txn->status, NULL);
@@ -10158,6 +10186,7 @@
 			 */
 			if (unlikely(s->req.buf->i + s->req.buf->p >
 				     s->req.buf->data + s->req.buf->size - global.tune.maxrewrite)) {
+				msg->err_state = msg->msg_state;
 				msg->msg_state = HTTP_MSG_ERROR;
 				smp->data.u.sint = 1;
 				return 1;
@@ -13046,14 +13075,14 @@
 			chunk_appendf(&trash,
 				     ", server %s (#%d), event #%u\n"
 				     "  src %s:%d, session #%d, session flags 0x%08x\n"
-				     "  HTTP msg state %d, msg flags 0x%08x, tx flags 0x%08x\n"
+				     "  HTTP msg state %s(%d), msg flags 0x%08x, tx flags 0x%08x\n"
 				     "  HTTP chunk len %lld bytes, HTTP body len %lld bytes\n"
 				     "  buffer flags 0x%08x, out %d bytes, total %lld bytes\n"
 				     "  pending %d bytes, wrapping at %d, error at position %d:\n \n",
 				     es->srv ? es->srv->id : "<NONE>", es->srv ? es->srv->puid : -1,
 				     es->ev_id,
 				     pn, port, es->sid, es->s_flags,
-				     es->state, es->m_flags, es->t_flags,
+				     http_msg_state_str(es->state), es->state, es->m_flags, es->t_flags,
 				     es->m_clen, es->m_blen,
 				     es->b_flags, es->b_out, es->b_tot,
 				     es->len, es->b_wrap, es->pos);