1
0
mirror of https://github.com/samba-team/samba.git synced 2025-02-03 13:47:25 +03:00

s4:lib/http: let http_read_response_send/recv() also consume the body if it fits into a max value

We need to consume full HTTP responses from the socket during the
authentication exchanges, otherwise our HTTP parser gets out of sync for
the next requests.

This will be important for gensec mechs which use an even number
for authentication packets.

I guess this should be done just based on the Content-Length value and
not based on the response code.

So far I saw bodies with 200 and 401 codes.

Signed-off-by: Stefan Metzmacher <metze@samba.org>
Reviewed-by: Andreas Schneider <asn@samba.org>
This commit is contained in:
Stefan Metzmacher 2017-07-20 18:12:27 +02:00 committed by Andreas Schneider
parent 7b86da08ea
commit e42f12c6be
5 changed files with 78 additions and 18 deletions

View File

@ -30,19 +30,39 @@
/**
* Determines if a response should have a body.
* Follows the rules in RFC 2616 section 4.3.
* @return 1 if the response MUST have a body; 0 if the response MUST NOT have
* a body. Returns -1 on error.
*/
static int http_response_needs_body(struct http_request *req)
{
struct http_header *h = NULL;
if (!req) return -1;
/* If response code is 503, the body contains the error description
* (2.1.2.1.3)
*/
if (req->response_code == 503)
return 1;
for (h = req->headers; h != NULL; h = h->next) {
int cmp;
int n;
char c;
unsigned long long v;
cmp = strcasecmp(h->key, "Content-Length");
if (cmp != 0) {
continue;
}
n = sscanf(h->value, "%llu%c", &v, &c);
if (n != 1) {
return -1;
}
req->remaining_content_length = v;
if (v != 0) {
return 1;
}
return 0;
}
return 0;
}
@ -92,15 +112,18 @@ static enum http_read_status http_parse_headers(struct http_read_response_state
ret = http_response_needs_body(state->response);
switch (ret) {
case 1:
if (state->response->remaining_content_length <= state->max_content_length) {
DEBUG(11, ("%s: Start of read body\n", __func__));
state->parser_state = HTTP_READING_BODY;
break;
}
/* fall through */
case 0:
DEBUG(11, ("%s: Skipping body for code %d\n", __func__,
state->response->response_code));
state->parser_state = HTTP_READING_DONE;
break;
case 1:
DEBUG(11, ("%s: Start of read body\n", __func__));
state->parser_state = HTTP_READING_BODY;
break;
case -1:
DEBUG(0, ("%s_: Error in http_response_needs_body\n", __func__));
TALLOC_FREE(line);
@ -256,9 +279,19 @@ static enum http_read_status http_parse_firstline(struct http_read_response_stat
static enum http_read_status http_read_body(struct http_read_response_state *state)
{
enum http_read_status status = HTTP_DATA_CORRUPTED;
/* TODO */
return status;
struct http_request *resp = state->response;
if (state->buffer.length < resp->remaining_content_length) {
return HTTP_MORE_DATA_EXPECTED;
}
resp->body = state->buffer;
state->buffer = data_blob_null;
talloc_steal(resp, resp->body.data);
resp->remaining_content_length = 0;
state->parser_state = HTTP_READING_DONE;
return HTTP_ALL_DATA_READ;
}
static enum http_read_status http_read_trailer(struct http_read_response_state *state)
@ -519,7 +552,8 @@ static int http_read_response_next_vector(struct tstream_context *stream,
static void http_read_response_done(struct tevent_req *);
struct tevent_req *http_read_response_send(TALLOC_CTX *mem_ctx,
struct tevent_context *ev,
struct tstream_context *stream)
struct tstream_context *stream,
size_t max_content_length)
{
struct tevent_req *req;
struct tevent_req *subreq;
@ -539,6 +573,7 @@ struct tevent_req *http_read_response_send(TALLOC_CTX *mem_ctx,
}
state->max_headers_size = HTTP_MAX_HEADER_SIZE;
state->max_content_length = (uint64_t)max_content_length;
state->parser_state = HTTP_READING_FIRSTLINE;
state->response = talloc_zero(state, struct http_request);
if (tevent_req_nomem(state->response, req)) {

View File

@ -82,6 +82,7 @@ struct http_request {
size_t headers_size;
unsigned int response_code; /* HTTP response code */
char *response_code_line; /* Readable response */
uint64_t remaining_content_length; /* data not represent in body */
DATA_BLOB body;
};
@ -101,7 +102,8 @@ NTSTATUS http_send_request_recv(struct tevent_req *);
/* HTTP response */
struct tevent_req *http_read_response_send(TALLOC_CTX *,
struct tevent_context *,
struct tstream_context *);
struct tstream_context *,
size_t max_content_length);
NTSTATUS http_read_response_recv(struct tevent_req *,
TALLOC_CTX *,
struct http_request **);

View File

@ -272,9 +272,16 @@ static void http_send_auth_request_http_req_done(struct tevent_req *subreq)
return;
}
/* If more processing required, read the response from server */
/*
* If more processing required, read the response from server
*
* We may get an empty RPCH Echo packet from the server
* on the "RPC_OUT_DATA" path. We need to consume this
* from the socket, but for now we just ignore the bytes.
*/
subreq = http_read_response_send(state, state->ev,
state->stream);
state->stream,
UINT16_MAX);
if (tevent_req_nomem(subreq, req)) {
return;
}
@ -301,6 +308,20 @@ static void http_send_auth_request_http_rep_done(struct tevent_req *subreq)
return;
}
/*
* We we asked for up to UINT16_MAX bytes of
* content, we don't expect
* state->auth_response->remaining_content_length
* to be set.
*
* For now we just ignore any bytes in
* state->auth_response->body.
*/
if (state->auth_response->remaining_content_length != 0) {
tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE);
return;
}
status = http_parse_auth_response(state->auth,
state->auth_response,
&gensec_in);

View File

@ -54,6 +54,7 @@ struct http_send_request_state {
struct http_read_response_state {
enum http_parser_state parser_state;
size_t max_headers_size;
uint64_t max_content_length;
DATA_BLOB buffer;
struct http_request *response;
};

View File

@ -482,7 +482,8 @@ struct tevent_req *roh_recv_out_channel_response_send(TALLOC_CTX *mem_ctx,
}
subreq = http_read_response_send(state, ev,
roh->default_channel_out->streams.active);
roh->default_channel_out->streams.active,
0); /* we'll get the content later */
if (tevent_req_nomem(subreq, req)) {
return tevent_req_post(req, ev);
}