s4:lib/http: let http_read_response_send/recv() also consume the body if it fits...
authorStefan Metzmacher <metze@samba.org>
Thu, 20 Jul 2017 16:12:27 +0000 (18:12 +0200)
committerAndreas Schneider <asn@cryptomilk.org>
Mon, 7 Aug 2017 13:20:01 +0000 (15:20 +0200)
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>
source4/lib/http/http.c
source4/lib/http/http.h
source4/lib/http/http_auth.c
source4/lib/http/http_internal.h
source4/librpc/rpc/dcerpc_roh_channel_out.c

index c6976ee..8fac33f 100644 (file)
 
 /**
  * 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)) {
index 35fe8a7..a871462 100644 (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 **);
index 3d2148e..d134bd6 100644 (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);
index 72ea9df..040466d 100644 (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;
 };
index b370e56..8a337ad 100644 (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);
        }