/* Unix SMB/CIFS implementation. HTTP library Copyright (C) 2013 Samuel Cabrero This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "includes.h" #include "lib/util/tevent_ntstatus.h" #include "http.h" #include "http_internal.h" #include "util/tevent_werror.h" #include "lib/util/dlinklist.h" #undef strcasecmp /** * Determines if a response should have a body. * @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; 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; } 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; }; /** * Parses the HTTP headers */ static enum http_read_status http_parse_headers(struct http_read_response_state *state) { enum http_read_status status = HTTP_ALL_DATA_READ; char *ptr = NULL; char *line = NULL; char *key = NULL; char *value = NULL; int n = 0; int ret; /* Sanity checks */ if (!state || !state->response) { DEBUG(0, ("%s: Invalid Parameter\n", __func__)); return HTTP_DATA_CORRUPTED; } if (state->buffer.length > state->max_headers_size) { DEBUG(0, ("%s: Headers too long: %zi, maximum length is %zi\n", __func__, state->buffer.length, state->max_headers_size)); return HTTP_DATA_TOO_LONG; } line = talloc_strndup(state, (char *)state->buffer.data, state->buffer.length); if (!line) { DEBUG(0, ("%s: Memory error\n", __func__)); return HTTP_DATA_CORRUPTED; } ptr = strstr(line, "\r\n"); if (ptr == NULL) { TALLOC_FREE(line); return HTTP_MORE_DATA_EXPECTED; } state->response->headers_size += state->buffer.length; if (strncmp(line, "\r\n", 2) == 0) { DEBUG(11,("%s: All headers read\n", __func__)); 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(0, ("%s_: Error in http_response_needs_body\n", __func__)); TALLOC_FREE(line); return HTTP_DATA_CORRUPTED; break; } TALLOC_FREE(line); return HTTP_ALL_DATA_READ; } n = sscanf(line, "%m[^:]: %m[^\r\n]\r\n", &key, &value); if (n != 2) { DEBUG(0, ("%s: Error parsing header '%s'\n", __func__, line)); status = HTTP_DATA_CORRUPTED; goto error; } if (http_add_header(state->response, &state->response->headers, key, value) == -1) { DEBUG(0, ("%s: Error adding header\n", __func__)); status = HTTP_DATA_CORRUPTED; goto error; } error: free(key); free(value); TALLOC_FREE(line); return status; } /** * Parses the first line of a HTTP response */ static bool http_parse_response_line(struct http_read_response_state *state) { bool status = true; char *protocol; char *msg = NULL; char major; char minor; int code; char *line = NULL; int n; /* Sanity checks */ if (!state) { DEBUG(0, ("%s: Input parameter is NULL\n", __func__)); return false; } line = talloc_strndup(state, (char*)state->buffer.data, state->buffer.length); if (!line) { DEBUG(0, ("%s: Memory error\n", __func__)); return false; } n = sscanf(line, "%m[^/]/%c.%c %d %m[^\r\n]\r\n", &protocol, &major, &minor, &code, &msg); DEBUG(11, ("%s: Header parsed(%i): protocol->%s, major->%c, minor->%c, " "code->%d, message->%s\n", __func__, n, protocol, major, minor, code, msg)); if (n != 5) { DEBUG(0, ("%s: Error parsing header\n", __func__)); status = false; goto error; } if (major != '1') { DEBUG(0, ("%s: Bad HTTP major number '%c'\n", __func__, major)); status = false; goto error; } if (code == 0) { DEBUG(0, ("%s: Bad response code '%d'", __func__, code)); status = false; goto error; } if (msg == NULL) { DEBUG(0, ("%s: Error parsing HTTP data\n", __func__)); status = false; goto error; } state->response->major = major; state->response->minor = minor; state->response->response_code = code; state->response->response_code_line = talloc_strndup(state->response, msg, strlen(msg)); error: free(protocol); free(msg); TALLOC_FREE(line); return status; } /* * Parses header lines from a request or a response into the specified * request object given a buffer. * * Returns * HTTP_DATA_CORRUPTED on error * HTTP_MORE_DATA_EXPECTED when we need to read more headers * HTTP_DATA_TOO_LONG on error * HTTP_ALL_DATA_READ when all headers have been read */ static enum http_read_status http_parse_firstline(struct http_read_response_state *state) { enum http_read_status status = HTTP_ALL_DATA_READ; char *ptr = NULL; char *line; /* Sanity checks */ if (!state) { DEBUG(0, ("%s: Invalid Parameter\n", __func__)); return HTTP_DATA_CORRUPTED; } if (state->buffer.length > state->max_headers_size) { DEBUG(0, ("%s: Headers too long: %zi, maximum length is %zi\n", __func__, state->buffer.length, state->max_headers_size)); return HTTP_DATA_TOO_LONG; } line = talloc_strndup(state, (char *)state->buffer.data, state->buffer.length); if (!line) { DEBUG(0, ("%s: Not enough memory\n", __func__)); return HTTP_DATA_CORRUPTED; } ptr = strstr(line, "\r\n"); if (ptr == NULL) { TALLOC_FREE(line); return HTTP_MORE_DATA_EXPECTED; } state->response->headers_size = state->buffer.length; if (!http_parse_response_line(state)) { status = HTTP_DATA_CORRUPTED; } /* Next state, read HTTP headers */ state->parser_state = HTTP_READING_HEADERS; TALLOC_FREE(line); return status; } static enum http_read_status http_read_body(struct http_read_response_state *state) { 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) { enum http_read_status status = HTTP_DATA_CORRUPTED; /* TODO */ return status; } static enum http_read_status http_parse_buffer(struct http_read_response_state *state) { if (!state) { DEBUG(0, ("%s: Invalid parameter\n", __func__)); return HTTP_DATA_CORRUPTED; } switch (state->parser_state) { case HTTP_READING_FIRSTLINE: return http_parse_firstline(state); case HTTP_READING_HEADERS: return http_parse_headers(state); case HTTP_READING_BODY: return http_read_body(state); break; case HTTP_READING_TRAILER: return http_read_trailer(state); break; case HTTP_READING_DONE: /* All read */ return HTTP_ALL_DATA_READ; default: DEBUG(0, ("%s: Illegal parser state %d", __func__, state->parser_state)); break; } return HTTP_DATA_CORRUPTED; } static int http_header_is_valid_value(const char *value) { const char *p = NULL; /* Sanity checks */ if (!value) { DEBUG(0, ("%s: Invalid parameter\n", __func__)); return -1; } p = value; while ((p = strpbrk(p, "\r\n")) != NULL) { /* Expect only one new line */ p += strspn(p, "\r\n"); /* Expect a space or tab for continuation */ if (*p != ' ' && *p != '\t') return (0); } return 1; } static int http_add_header_internal(TALLOC_CTX *mem_ctx, struct http_header **headers, const char *key, const char *value, bool replace) { struct http_header *tail = NULL; struct http_header *h = NULL; /* Sanity checks */ if (!headers || !key || !value) { DEBUG(0, ("Invalid parameter\n")); return -1; } if (replace) { for (h = *headers; h != NULL; h = h->next) { if (strcasecmp(key, h->key) == 0) { break; } } if (h != NULL) { /* Replace header value */ if (h->value) { talloc_free(h->value); } h->value = talloc_strdup(h, value); DEBUG(11, ("%s: Replaced HTTP header: key '%s', value '%s'\n", __func__, h->key, h->value)); return 0; } } /* Add new header */ h = talloc(mem_ctx, struct http_header); h->key = talloc_strdup(h, key); h->value = talloc_strdup(h, value); DLIST_ADD_END(*headers, h); tail = DLIST_TAIL(*headers); if (tail != h) { DEBUG(0, ("%s: Error adding header\n", __func__)); return -1; } DEBUG(11, ("%s: Added HTTP header: key '%s', value '%s'\n", __func__, h->key, h->value)); return 0; } int http_add_header(TALLOC_CTX *mem_ctx, struct http_header **headers, const char *key, const char *value) { if (strchr(key, '\r') != NULL || strchr(key, '\n') != NULL) { DEBUG(0, ("%s: Dropping illegal header key\n", __func__)); return -1; } if (!http_header_is_valid_value(value)) { DEBUG(0, ("%s: Dropping illegal header value\n", __func__)); return -1; } return (http_add_header_internal(mem_ctx, headers, key, value, false)); } int http_replace_header(TALLOC_CTX *mem_ctx, struct http_header **headers, const char *key, const char *value) { if (strchr(key, '\r') != NULL || strchr(key, '\n') != NULL) { DEBUG(0, ("%s: Dropping illegal header key\n", __func__)); return -1; } if (!http_header_is_valid_value(value)) { DEBUG(0, ("%s: Dropping illegal header value\n", __func__)); return -1; } return (http_add_header_internal(mem_ctx, headers, key, value, true)); } /** * Remove a header from the headers list. * * Returns 0, if the header was successfully removed. * Returns -1, if the header could not be found. */ int http_remove_header(struct http_header **headers, const char *key) { struct http_header *header; /* Sanity checks */ if (!headers || !key) { DEBUG(0, ("%s: Invalid parameter\n", __func__)); return -1; } for(header = *headers; header != NULL; header = header->next) { if (strcmp(key, header->key) == 0) { DLIST_REMOVE(*headers, header); return 0; } } return -1; } static int http_read_response_next_vector(struct tstream_context *stream, void *private_data, TALLOC_CTX *mem_ctx, struct iovec **_vector, size_t *_count) { struct http_read_response_state *state; struct iovec *vector; /* Sanity checks */ if (!stream || !private_data || !_vector || !_count) { DEBUG(0, ("%s: Invalid Parameter\n", __func__)); return -1; } state = talloc_get_type_abort(private_data, struct http_read_response_state); vector = talloc_array(mem_ctx, struct iovec, 1); if (!vector) { DEBUG(0, ("%s: No more memory\n", __func__)); return -1; } if (state->buffer.data == NULL) { /* Allocate buffer */ state->buffer.data = talloc_zero_array(state, uint8_t, 1); if (!state->buffer.data) { DEBUG(0, ("%s: No more memory\n", __func__)); return -1; } state->buffer.length = 1; /* Return now, nothing to parse yet */ vector[0].iov_base = (void *)(state->buffer.data); vector[0].iov_len = 1; *_vector = vector; *_count = 1; return 0; } switch (http_parse_buffer(state)) { case HTTP_ALL_DATA_READ: if (state->parser_state == HTTP_READING_DONE) { /* Full request or response parsed */ *_vector = NULL; *_count = 0; } else { /* Free current buffer and allocate new one */ TALLOC_FREE(state->buffer.data); state->buffer.data = talloc_zero_array(state, uint8_t, 1); if (!state->buffer.data) { return -1; } state->buffer.length = 1; vector[0].iov_base = (void *)(state->buffer.data); vector[0].iov_len = 1; *_vector = vector; *_count = 1; } break; case HTTP_MORE_DATA_EXPECTED: /* TODO Optimize, allocating byte by byte */ state->buffer.data = talloc_realloc(state, state->buffer.data, uint8_t, state->buffer.length + 1); if (!state->buffer.data) { return -1; } state->buffer.length++; vector[0].iov_base = (void *)(state->buffer.data + state->buffer.length - 1); vector[0].iov_len = 1; *_vector = vector; *_count = 1; break; case HTTP_DATA_CORRUPTED: case HTTP_REQUEST_CANCELED: case HTTP_DATA_TOO_LONG: return -1; break; default: DEBUG(0, ("%s: Unexpected status\n", __func__)); break; } return 0; } /** * Reads a HTTP response */ 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 http_conn *http_conn, size_t max_content_length) { struct tevent_req *req; struct tevent_req *subreq; struct http_read_response_state *state; DEBUG(11, ("%s: Reading HTTP response\n", __func__)); /* Sanity checks */ if (ev == NULL || http_conn == NULL) { DEBUG(0, ("%s: Invalid parameter\n", __func__)); return NULL; } req = tevent_req_create(mem_ctx, &state, struct http_read_response_state); if (req == NULL) { return NULL; } 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)) { return tevent_req_post(req, ev); } subreq = tstream_readv_pdu_send(state, ev, http_conn->tstreams.active, http_read_response_next_vector, state); if (tevent_req_nomem(subreq,req)) { return tevent_req_post(req, ev); } tevent_req_set_callback(subreq, http_read_response_done, req); return req; } static void http_read_response_done(struct tevent_req *subreq) { NTSTATUS status; struct tevent_req *req; int ret; int sys_errno; if (!subreq) { DEBUG(0, ("%s: Invalid parameter\n", __func__)); return; } req = tevent_req_callback_data(subreq, struct tevent_req); ret = tstream_readv_pdu_recv(subreq, &sys_errno); DEBUG(11, ("%s: HTTP response read (%d bytes)\n", __func__, ret)); TALLOC_FREE(subreq); if (ret == -1) { status = map_nt_error_from_unix_common(sys_errno); DEBUG(0, ("%s: Failed to read HTTP response: %s\n", __func__, nt_errstr(status))); tevent_req_nterror(req, status); return; } tevent_req_done(req); } NTSTATUS http_read_response_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, struct http_request **response) { NTSTATUS status; struct http_read_response_state *state; if (!mem_ctx || !response || !req) { DEBUG(0, ("%s: Invalid parameter\n", __func__)); return NT_STATUS_INVALID_PARAMETER; } if (tevent_req_is_nterror(req, &status)) { tevent_req_received(req); return status; } state = tevent_req_data(req, struct http_read_response_state); *response = state->response; talloc_steal(mem_ctx, state->response); tevent_req_received(req); return NT_STATUS_OK; } static const char *http_method_str(enum http_cmd_type type) { const char *method; switch (type) { case HTTP_REQ_POST: method = "POST"; break; case HTTP_REQ_RPC_IN_DATA: method = "RPC_IN_DATA"; break; case HTTP_REQ_RPC_OUT_DATA: method = "RPC_OUT_DATA"; break; default: method = NULL; break; } return method; } static NTSTATUS http_push_request_line(TALLOC_CTX *mem_ctx, DATA_BLOB *buffer, const struct http_request *req) { const char *method; char *str; /* Sanity checks */ if (!buffer || !req) { DEBUG(0, ("%s: Invalid parameter\n", __func__)); return NT_STATUS_INVALID_PARAMETER; } method = http_method_str(req->type); if (method == NULL) { return NT_STATUS_INVALID_PARAMETER; } str = talloc_asprintf(mem_ctx, "%s %s HTTP/%c.%c\r\n", method, req->uri, req->major, req->minor); if (str == NULL) return NT_STATUS_NO_MEMORY; if (!data_blob_append(mem_ctx, buffer, str, strlen(str))) { talloc_free(str); return NT_STATUS_NO_MEMORY; } talloc_free(str); return NT_STATUS_OK; } static NTSTATUS http_push_headers(TALLOC_CTX *mem_ctx, DATA_BLOB *blob, struct http_request *req) { struct http_header *header = NULL; char *header_str = NULL; size_t len; /* Sanity checks */ if (!blob || !req) { DEBUG(0, ("%s: Invalid parameter\n", __func__)); return NT_STATUS_INVALID_PARAMETER; } for (header = req->headers; header != NULL; header = header->next) { header_str = talloc_asprintf(mem_ctx, "%s: %s\r\n", header->key, header->value); if (header_str == NULL) { return NT_STATUS_NO_MEMORY; } len = strlen(header_str); if (!data_blob_append(mem_ctx, blob, header_str, len)) { talloc_free(header_str); return NT_STATUS_NO_MEMORY; } talloc_free(header_str); } if (!data_blob_append(mem_ctx, blob, "\r\n",2)) { return NT_STATUS_NO_MEMORY; } return NT_STATUS_OK; } static NTSTATUS http_push_body(TALLOC_CTX *mem_ctx, DATA_BLOB *blob, struct http_request *req) { /* Sanity checks */ if (!blob || !req) { DEBUG(0, ("%s: Invalid parameter\n", __func__)); return NT_STATUS_INVALID_PARAMETER; } if (req->body.length) { if (!data_blob_append(mem_ctx, blob, req->body.data, req->body.length)) { return NT_STATUS_NO_MEMORY; } } return NT_STATUS_OK; } struct http_send_request_state { struct tevent_context *ev; struct loadparm_context *lp_ctx; struct cli_credentials *credentials; struct http_request *request; DATA_BLOB buffer; struct iovec iov; ssize_t nwritten; int sys_errno; }; /** * Sends and HTTP request */ static void http_send_request_done(struct tevent_req *); struct tevent_req *http_send_request_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct http_conn *http_conn, struct http_request *request) { struct tevent_req *req; struct tevent_req *subreq; struct http_send_request_state *state = NULL; NTSTATUS status; DEBUG(11, ("%s: Sending HTTP request\n", __func__)); /* Sanity checks */ if (ev == NULL || request == NULL || http_conn == NULL) { DEBUG(0, ("%s: Invalid parameter\n", __func__)); return NULL; } req = tevent_req_create(mem_ctx, &state, struct http_send_request_state); if (req == NULL) { return NULL; } state->ev = ev; state->request = request; /* Push the request line */ status = http_push_request_line(state, &state->buffer, state->request); if (!NT_STATUS_IS_OK(status)) { tevent_req_nterror(req, status); return tevent_req_post(req, ev); } /* Push the headers */ status = http_push_headers(mem_ctx, &state->buffer, request); if (!NT_STATUS_IS_OK(status)) { tevent_req_nterror(req, status); return tevent_req_post(req, ev); } /* Push the body */ status = http_push_body(mem_ctx, &state->buffer, request); if (!NT_STATUS_IS_OK(status)) { tevent_req_nterror(req, status); return tevent_req_post(req, ev); } state->iov.iov_base = (char *) state->buffer.data; state->iov.iov_len = state->buffer.length; subreq = tstream_writev_queue_send(state, ev, http_conn->tstreams.active, http_conn->send_queue, &state->iov, 1); if (tevent_req_nomem(subreq, req)) { return tevent_req_post(req, ev); } tevent_req_set_callback(subreq, http_send_request_done, req); return req; } static void http_send_request_done(struct tevent_req *subreq) { NTSTATUS status; struct tevent_req *req; struct http_send_request_state *state; req = tevent_req_callback_data(subreq, struct tevent_req); state = tevent_req_data(req, struct http_send_request_state); state->nwritten = tstream_writev_queue_recv(subreq, &state->sys_errno); TALLOC_FREE(subreq); if (state->nwritten == -1 && state->sys_errno != 0) { status = map_nt_error_from_unix_common(state->sys_errno); DEBUG(0, ("%s: Failed to send HTTP request: %s\n", __func__, nt_errstr(status))); tevent_req_nterror(req, status); return; } tevent_req_done(req); } NTSTATUS http_send_request_recv(struct tevent_req *req) { NTSTATUS status; if (!req) { DEBUG(0, ("%s: Invalid parameter\n", __func__)); return NT_STATUS_INVALID_PARAMETER; } if (tevent_req_is_nterror(req, &status)) { tevent_req_received(req); return status; } tevent_req_received(req); return NT_STATUS_OK; }