diff --git a/doc/configuration.txt b/doc/configuration.txt index bcd7c27fa..6f267be58 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -4822,6 +4822,13 @@ req_len return false only when haproxy is certain that no more data will come in. This test was designed to be used with TCP request content inspection. +req_proto_http + Returns true when data in the request buffer look like HTTP and correctly + parses as such. It is the same parser as the common HTTP request parser which + is used so there should be no surprizes. This test can be used for instance + to direct HTTP traffic to a given port and HTTPS traffic to another one + using TCP request content inspection rules. + req_ssl_ver Returns true when data in the request buffer look like SSL, with a protocol version matching the specified range. Both SSLv2 hello messages and SSLv3 @@ -5027,6 +5034,7 @@ ACL name Equivalent to Usage TRUE always_true always match FALSE always_false never match LOCALHOST src 127.0.0.1/8 match connection from local host +HTTP req_proto_http match if protocol is valid HTTP HTTP_1.0 req_ver 1.0 match HTTP version 1.0 HTTP_1.1 req_ver 1.1 match HTTP version 1.1 METH_CONNECT method CONNECT match HTTP CONNECT method diff --git a/src/acl.c b/src/acl.c index ed41e91ed..a5f030238 100644 --- a/src/acl.c +++ b/src/acl.c @@ -797,6 +797,7 @@ const struct { { .name = "TRUE", .expr = {"always_true",""}}, { .name = "FALSE", .expr = {"always_false",""}}, { .name = "LOCALHOST", .expr = {"src","127.0.0.1/8",""}}, + { .name = "HTTP", .expr = {"req_proto_http",""}}, { .name = "HTTP_1.0", .expr = {"req_ver","1.0",""}}, { .name = "HTTP_1.1", .expr = {"req_ver","1.1",""}}, { .name = "METH_CONNECT", .expr = {"method","CONNECT",""}}, diff --git a/src/client.c b/src/client.c index cfd41e0e4..6210cdbe0 100644 --- a/src/client.c +++ b/src/client.c @@ -252,21 +252,23 @@ int event_accept(int fd) { txn->hdr_idx.v = NULL; txn->hdr_idx.size = txn->hdr_idx.used = 0; - if (p->mode == PR_MODE_HTTP) { - txn->status = -1; - txn->req.hdr_content_len = 0LL; - txn->rsp.hdr_content_len = 0LL; - txn->req.msg_state = HTTP_MSG_RQBEFORE; /* at the very beginning of the request */ - txn->rsp.msg_state = HTTP_MSG_RPBEFORE; /* at the very beginning of the response */ - txn->req.sol = txn->req.eol = NULL; - txn->req.som = txn->req.eoh = 0; /* relative to the buffer */ - txn->rsp.sol = txn->rsp.eol = NULL; - txn->rsp.som = txn->rsp.eoh = 0; /* relative to the buffer */ - txn->req.err_pos = txn->rsp.err_pos = -2; /* block buggy requests/responses */ - if (p->options2 & PR_O2_REQBUG_OK) - txn->req.err_pos = -1; /* let buggy requests pass */ - txn->auth_hdr.len = -1; + /* we always initialize the HTTP structure because we may use it later */ + txn->status = -1; + txn->req.hdr_content_len = 0LL; + txn->rsp.hdr_content_len = 0LL; + txn->req.msg_state = HTTP_MSG_RQBEFORE; /* at the very beginning of the request */ + txn->rsp.msg_state = HTTP_MSG_RPBEFORE; /* at the very beginning of the response */ + txn->req.sol = txn->req.eol = NULL; + txn->req.som = txn->req.eoh = 0; /* relative to the buffer */ + txn->rsp.sol = txn->rsp.eol = NULL; + txn->rsp.som = txn->rsp.eoh = 0; /* relative to the buffer */ + txn->req.err_pos = txn->rsp.err_pos = -2; /* block buggy requests/responses */ + txn->auth_hdr.len = -1; + if (p->options2 & PR_O2_REQBUG_OK) + txn->req.err_pos = -1; /* let buggy requests pass */ + if (p->mode == PR_MODE_HTTP) { + /* the captures are only used in HTTP frontends */ if (p->nb_req_cap > 0) { if ((txn->req.cap = pool_alloc2(p->req_cap_pool)) == NULL) goto out_fail_reqcap; /* no memory */ @@ -274,7 +276,6 @@ int event_accept(int fd) { memset(txn->req.cap, 0, p->nb_req_cap*sizeof(char *)); } - if (p->nb_rsp_cap > 0) { if ((txn->rsp.cap = pool_alloc2(p->rsp_cap_pool)) == NULL) goto out_fail_rspcap; /* no memory */ diff --git a/src/proto_http.c b/src/proto_http.c index 64e93d3ee..be31c4880 100644 --- a/src/proto_http.c +++ b/src/proto_http.c @@ -1498,6 +1498,46 @@ void http_msg_analyzer(struct buffer *buf, struct http_msg *msg, struct hdr_idx return; } +/* convert an HTTP/0.9 request into an HTTP/1.0 request. Returns 1 if the + * conversion succeeded, 0 in case of error. If the request was already 1.X, + * nothing is done and 1 is returned. + */ +static int http_upgrade_v09_to_v10(struct buffer *req, struct http_msg *msg, struct http_txn *txn) +{ + int delta; + char *cur_end; + + if (msg->sl.rq.v_l != 0) + return 1; + + msg->sol = req->data + msg->som; + cur_end = msg->sol + msg->sl.rq.l; + delta = 0; + + if (msg->sl.rq.u_l == 0) { + /* if no URI was set, add "/" */ + delta = buffer_replace2(req, cur_end, cur_end, " /", 2); + cur_end += delta; + msg->eoh += delta; + } + /* add HTTP version */ + delta = buffer_replace2(req, cur_end, cur_end, " HTTP/1.0\r\n", 11); + msg->eoh += delta; + cur_end += delta; + cur_end = (char *)http_parse_reqline(msg, req->data, + HTTP_MSG_RQMETH, + msg->sol, cur_end + 1, + NULL, NULL); + if (unlikely(!cur_end)) + return 0; + + /* we have a full HTTP/1.0 request now and we know that + * we have either a CR or an LF at . + */ + hdr_idx_set_start(&txn->hdr_idx, msg->sl.rq.l, *cur_end == '\r'); + return 1; +} + /* This stream analyser waits for a complete HTTP request. It returns 1 if the * processing can continue on next analysers, or zero if it either needs more * data or wants to immediately abort the request (eg: timeout, error, ...). It @@ -1739,36 +1779,8 @@ int http_wait_for_request(struct session *s, struct buffer *req, int an_bit) } /* 4. We may have to convert HTTP/0.9 requests to HTTP/1.0 */ - if (unlikely(msg->sl.rq.v_l == 0)) { - int delta; - char *cur_end; - msg->sol = req->data + msg->som; - cur_end = msg->sol + msg->sl.rq.l; - delta = 0; - - if (msg->sl.rq.u_l == 0) { - /* if no URI was set, add "/" */ - delta = buffer_replace2(req, cur_end, cur_end, " /", 2); - cur_end += delta; - msg->eoh += delta; - } - /* add HTTP version */ - delta = buffer_replace2(req, cur_end, cur_end, " HTTP/1.0\r\n", 11); - msg->eoh += delta; - cur_end += delta; - cur_end = (char *)http_parse_reqline(msg, req->data, - HTTP_MSG_RQMETH, - msg->sol, cur_end + 1, - NULL, NULL); - if (unlikely(!cur_end)) - goto return_bad_req; - - /* we have a full HTTP/1.0 request now and we know that - * we have either a CR or an LF at . - */ - hdr_idx_set_start(&txn->hdr_idx, msg->sl.rq.l, *cur_end == '\r'); - } - + if (unlikely(msg->sl.rq.v_l == 0) && !http_upgrade_v09_to_v10(req, msg, txn)) + goto return_bad_req; /* 5: we may need to capture headers */ if (unlikely((s->logs.logwait & LW_REQHDR) && s->fe->req_cap)) @@ -4900,6 +4912,57 @@ acl_fetch_path(struct proxy *px, struct session *l4, void *l7, int dir, return 1; } +static int +acl_fetch_proto_http(struct proxy *px, struct session *s, void *l7, int dir, + struct acl_expr *expr, struct acl_test *test) +{ + struct buffer *req = s->req; + struct http_txn *txn = &s->txn; + struct http_msg *msg = &txn->req; + + /* Note: hdr_idx.v cannot be NULL in this ACL because the ACL is tagged + * as a layer7 ACL, which involves automatic allocation of hdr_idx. + */ + + if (!s || !req) + return 0; + + if (unlikely(msg->msg_state == HTTP_MSG_BODY)) { + /* Already decoded as OK */ + test->flags |= ACL_TEST_F_SET_RES_PASS; + return 1; + } + + /* Try to decode HTTP request */ + if (likely(req->lr < req->r)) + http_msg_analyzer(req, msg, &txn->hdr_idx); + + if (unlikely(msg->msg_state != HTTP_MSG_BODY)) { + if ((msg->msg_state == HTTP_MSG_ERROR) || (req->flags & BF_FULL)) { + test->flags |= ACL_TEST_F_SET_RES_FAIL; + return 1; + } + /* wait for final state */ + test->flags |= ACL_TEST_F_MAY_CHANGE; + return 0; + } + + /* OK we got a valid HTTP request. We have some minor preparation to + * perform so that further checks can rely on HTTP tests. + */ + msg->sol = req->data + msg->som; + txn->meth = find_http_meth(&req->data[msg->som], msg->sl.rq.m_l); + if (txn->meth == HTTP_METH_GET || txn->meth == HTTP_METH_HEAD) + s->flags |= SN_REDIRECTABLE; + + if (unlikely(msg->sl.rq.v_l == 0) && !http_upgrade_v09_to_v10(req, msg, txn)) { + test->flags |= ACL_TEST_F_SET_RES_FAIL; + return 1; + } + + test->flags |= ACL_TEST_F_SET_RES_PASS; + return 1; +} /************************************************************************/ @@ -4908,6 +4971,8 @@ acl_fetch_path(struct proxy *px, struct session *l4, void *l7, int dir, /* Note: must not be declared as its list will be overwritten */ static struct acl_kw_list acl_kws = {{ },{ + { "req_proto_http", acl_parse_nothing, acl_fetch_proto_http, acl_match_nothing, ACL_USE_L7REQ_PERMANENT }, + { "method", acl_parse_meth, acl_fetch_meth, acl_match_meth, ACL_USE_L7REQ_PERMANENT }, { "req_ver", acl_parse_ver, acl_fetch_rqver, acl_match_str, ACL_USE_L7REQ_VOLATILE }, { "resp_ver", acl_parse_ver, acl_fetch_stver, acl_match_str, ACL_USE_L7RTR_VOLATILE },