diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-05 17:47:29 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-05 17:47:29 +0000 |
commit | 4f5791ebd03eaec1c7da0865a383175b05102712 (patch) | |
tree | 8ce7b00f7a76baa386372422adebbe64510812d4 /libcli/http | |
parent | Initial commit. (diff) | |
download | samba-upstream/2%4.17.12+dfsg.tar.xz samba-upstream/2%4.17.12+dfsg.zip |
Adding upstream version 2:4.17.12+dfsg.upstream/2%4.17.12+dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'libcli/http')
-rw-r--r-- | libcli/http/gensec/basic.c | 204 | ||||
-rw-r--r-- | libcli/http/gensec/generic.c | 286 | ||||
-rw-r--r-- | libcli/http/http.c | 882 | ||||
-rw-r--r-- | libcli/http/http.h | 144 | ||||
-rw-r--r-- | libcli/http/http_auth.c | 383 | ||||
-rw-r--r-- | libcli/http/http_conn.c | 347 | ||||
-rw-r--r-- | libcli/http/http_internal.h | 50 | ||||
-rw-r--r-- | libcli/http/wscript_build | 21 |
8 files changed, 2317 insertions, 0 deletions
diff --git a/libcli/http/gensec/basic.c b/libcli/http/gensec/basic.c new file mode 100644 index 0000000..c7a1bbb --- /dev/null +++ b/libcli/http/gensec/basic.c @@ -0,0 +1,204 @@ +/* + Unix SMB/CIFS implementation. + + HTTP library - Basic authentication mechanism gensec module + + Copyright (C) 2014 Samuel Cabrero <samuelcabrero@kernevil.me> + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include <tevent.h> +#include "lib/util/tevent_ntstatus.h" +#include "auth/auth.h" +#include "auth/gensec/gensec.h" +#include "auth/gensec/gensec_internal.h" +#include "auth/credentials/credentials.h" +#include "lib/util/base64.h" + +_PUBLIC_ NTSTATUS gensec_http_basic_init(TALLOC_CTX *); + +struct gensec_http_basic_state { + enum { + GENSEC_HTTP_BASIC_START, + GENSEC_HTTP_BASIC_DONE, + GENSEC_HTTP_BASIC_ERROR, + } step; +}; + +static NTSTATUS gensec_http_basic_client_start(struct gensec_security *gensec) +{ + struct gensec_http_basic_state *state; + + state = talloc_zero(gensec, struct gensec_http_basic_state); + if (state == NULL) { + return NT_STATUS_NO_MEMORY; + } + gensec->private_data = state; + + state->step = GENSEC_HTTP_BASIC_START; + + return NT_STATUS_OK; +} + +struct gensec_http_basic_update_state { + NTSTATUS status; + DATA_BLOB out; +}; + +static NTSTATUS gensec_http_basic_update_internal(struct gensec_security *gensec_ctx, + TALLOC_CTX *mem_ctx, + const DATA_BLOB in, + DATA_BLOB *out); + +static struct tevent_req *gensec_http_basic_update_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct gensec_security *gensec_security, + const DATA_BLOB in) +{ + struct tevent_req *req = NULL; + struct gensec_http_basic_update_state *state = NULL; + NTSTATUS status; + + req = tevent_req_create(mem_ctx, &state, + struct gensec_http_basic_update_state); + if (req == NULL) { + return NULL; + } + + status = gensec_http_basic_update_internal(gensec_security, + state, in, + &state->out); + state->status = status; + if (NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) { + tevent_req_done(req); + return tevent_req_post(req, ev); + } + if (tevent_req_nterror(req, status)) { + return tevent_req_post(req, ev); + } + + tevent_req_done(req); + return tevent_req_post(req, ev); +} + +static NTSTATUS gensec_http_basic_update_internal(struct gensec_security *gensec_ctx, + TALLOC_CTX *mem_ctx, + const DATA_BLOB in, + DATA_BLOB *out) +{ + struct gensec_http_basic_state *state; + struct cli_credentials *creds; + char *tmp, *b64; + + state = talloc_get_type_abort(gensec_ctx->private_data, + struct gensec_http_basic_state); + creds = gensec_get_credentials(gensec_ctx); + + switch (gensec_ctx->gensec_role) { + case GENSEC_CLIENT: + switch (state->step) { + case GENSEC_HTTP_BASIC_START: + tmp = talloc_asprintf(mem_ctx, "%s\\%s:%s", + cli_credentials_get_domain(creds), + cli_credentials_get_username(creds), + cli_credentials_get_password(creds)); + if (tmp == NULL) { + state->step = GENSEC_HTTP_BASIC_ERROR; + return NT_STATUS_NO_MEMORY; + } + *out = data_blob_string_const(tmp); + + b64 = base64_encode_data_blob(mem_ctx, *out); + if (b64 == NULL) { + state->step = GENSEC_HTTP_BASIC_ERROR; + return NT_STATUS_NO_MEMORY; + } + TALLOC_FREE(tmp); + + tmp = talloc_asprintf(mem_ctx, "Basic %s", b64); + if (tmp == NULL) { + state->step = GENSEC_HTTP_BASIC_ERROR; + return NT_STATUS_NO_MEMORY; + } + TALLOC_FREE(b64); + + *out = data_blob_string_const(tmp); + state->step = GENSEC_HTTP_BASIC_DONE; + return NT_STATUS_OK; + + case GENSEC_HTTP_BASIC_DONE: + case GENSEC_HTTP_BASIC_ERROR: + default: + break; + } + state->step = GENSEC_HTTP_BASIC_ERROR; + return NT_STATUS_INTERNAL_ERROR; + + case GENSEC_SERVER: + state->step = GENSEC_HTTP_BASIC_ERROR; + return NT_STATUS_NOT_IMPLEMENTED; + } + + state->step = GENSEC_HTTP_BASIC_ERROR; + return NT_STATUS_INTERNAL_ERROR; +} + +static NTSTATUS gensec_http_basic_update_recv(struct tevent_req *req, + TALLOC_CTX *out_mem_ctx, + DATA_BLOB *out) +{ + struct gensec_http_basic_update_state *state = + tevent_req_data(req, + struct gensec_http_basic_update_state); + NTSTATUS status; + + *out = data_blob_null; + + if (tevent_req_is_nterror(req, &status)) { + tevent_req_received(req); + return status; + } + + *out = state->out; + talloc_steal(out_mem_ctx, state->out.data); + status = state->status; + tevent_req_received(req); + return status; +} + +static const struct gensec_security_ops gensec_http_basic_security_ops = { + .name = "http_basic", + .auth_type = 0, + .client_start = gensec_http_basic_client_start, + .update_send = gensec_http_basic_update_send, + .update_recv = gensec_http_basic_update_recv, + .enabled = true, + .priority = GENSEC_EXTERNAL, +}; + +_PUBLIC_ NTSTATUS gensec_http_basic_init(TALLOC_CTX *ctx) +{ + NTSTATUS status; + + status = gensec_register(ctx, &gensec_http_basic_security_ops); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("Failed to register '%s' gensec backend!\n", + gensec_http_basic_security_ops.name)); + return status; + } + + return status; +} diff --git a/libcli/http/gensec/generic.c b/libcli/http/gensec/generic.c new file mode 100644 index 0000000..2f09b9d --- /dev/null +++ b/libcli/http/gensec/generic.c @@ -0,0 +1,286 @@ +/* + Unix SMB/CIFS implementation. + + HTTP library - NTLM authentication mechanism gensec module + + Copyright (C) 2014 Samuel Cabrero <samuelcabrero@kernevil.me> + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include <tevent.h> +#include "lib/util/tevent_ntstatus.h" +#include "auth/auth.h" +#include "auth/gensec/gensec.h" +#include "auth/gensec/gensec_internal.h" +#include "lib/util/base64.h" + +#undef strncasecmp + +_PUBLIC_ NTSTATUS gensec_http_generic_init(TALLOC_CTX *); + +struct gensec_http_generic_state { + struct gensec_security *sub; + DATA_BLOB prefix; +}; + +static NTSTATUS gensec_http_generic_client_start(struct gensec_security *gensec, + const char *prefix_str, + const char *mech_oid) +{ + NTSTATUS status; + struct gensec_http_generic_state *state; + + state = talloc_zero(gensec, struct gensec_http_generic_state); + if (state == NULL) { + return NT_STATUS_NO_MEMORY; + } + gensec->private_data = state; + + state->prefix = data_blob_string_const(prefix_str); + + status = gensec_subcontext_start(state, gensec, &state->sub); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + return gensec_start_mech_by_oid(state->sub, mech_oid); +} + +static NTSTATUS gensec_http_ntlm_client_start(struct gensec_security *gensec) +{ + return gensec_http_generic_client_start(gensec, "NTLM", + GENSEC_OID_NTLMSSP); +} + +static NTSTATUS gensec_http_negotiate_client_start(struct gensec_security *gensec) +{ + return gensec_http_generic_client_start(gensec, "Negotiate", + GENSEC_OID_SPNEGO); +} + +struct gensec_http_generic_update_state { + struct gensec_security *gensec; + DATA_BLOB sub_in; + NTSTATUS status; + DATA_BLOB out; +}; + +static void gensec_http_generic_update_done(struct tevent_req *subreq); + +static struct tevent_req *gensec_http_generic_update_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct gensec_security *gensec_ctx, + const DATA_BLOB in) +{ + struct gensec_http_generic_state *http_generic = + talloc_get_type_abort(gensec_ctx->private_data, + struct gensec_http_generic_state); + struct tevent_req *req = NULL; + struct gensec_http_generic_update_state *state = NULL; + struct tevent_req *subreq = NULL; + + req = tevent_req_create(mem_ctx, &state, + struct gensec_http_generic_update_state); + if (req == NULL) { + return NULL; + } + state->gensec = gensec_ctx; + + if (in.length) { + int cmp; + DATA_BLOB b64b; + size_t skip = 0; + + if (in.length < http_generic->prefix.length) { + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return tevent_req_post(req, ev); + } + + cmp = strncasecmp((const char *)in.data, + (const char *)http_generic->prefix.data, + http_generic->prefix.length); + if (cmp != 0) { + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return tevent_req_post(req, ev); + } + + if (in.length == http_generic->prefix.length) { + /* + * We expect more data, but the + * server just sent the prefix without + * a space prefixing base64 data. + * + * It means the server rejects + * the request with. + */ + tevent_req_nterror(req, NT_STATUS_LOGON_FAILURE); + return tevent_req_post(req, ev); + } + + if (in.data[http_generic->prefix.length] != ' ') { + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return tevent_req_post(req, ev); + } + skip = http_generic->prefix.length + 1; + + b64b = data_blob_const(in.data + skip, in.length - skip); + if (b64b.length != 0) { + char *b64 = NULL; + + /* + * ensure it's terminated with \0' before + * passing to base64_decode_data_blob_talloc(). + */ + b64 = talloc_strndup(state, (const char *)b64b.data, + b64b.length); + if (tevent_req_nomem(b64, req)) { + return tevent_req_post(req, ev); + } + + state->sub_in = base64_decode_data_blob_talloc(state, + b64); + TALLOC_FREE(b64); + if (tevent_req_nomem(state->sub_in.data, req)) { + return tevent_req_post(req, ev); + } + } + } + + subreq = gensec_update_send(state, ev, + http_generic->sub, + state->sub_in); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, gensec_http_generic_update_done, req); + + return req; +} + +static void gensec_http_generic_update_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, + struct tevent_req); + struct gensec_http_generic_update_state *state = + tevent_req_data(req, + struct gensec_http_generic_update_state); + struct gensec_http_generic_state *http_generic = + talloc_get_type_abort(state->gensec->private_data, + struct gensec_http_generic_state); + NTSTATUS status; + DATA_BLOB sub_out = data_blob_null; + char *b64 = NULL; + char *str = NULL; + int prefix_length; + + status = gensec_update_recv(subreq, state, &sub_out); + TALLOC_FREE(subreq); + state->status = status; + if (NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) { + status = NT_STATUS_OK; + } + if (tevent_req_nterror(req, status)) { + return; + } + + if (sub_out.length == 0) { + tevent_req_done(req); + return; + } + + b64 = base64_encode_data_blob(state, sub_out); + data_blob_free(&sub_out); + if (tevent_req_nomem(b64, req)) { + return; + } + + prefix_length = http_generic->prefix.length; + str = talloc_asprintf(state, "%*.*s %s", prefix_length, prefix_length, + (const char *)http_generic->prefix.data, b64); + TALLOC_FREE(b64); + if (tevent_req_nomem(str, req)) { + return; + } + + state->out = data_blob_string_const(str); + tevent_req_done(req); +} + +static NTSTATUS gensec_http_generic_update_recv(struct tevent_req *req, + TALLOC_CTX *out_mem_ctx, + DATA_BLOB *out) +{ + struct gensec_http_generic_update_state *state = + tevent_req_data(req, + struct gensec_http_generic_update_state); + NTSTATUS status; + + *out = data_blob_null; + + if (tevent_req_is_nterror(req, &status)) { + tevent_req_received(req); + return status; + } + + *out = state->out; + talloc_steal(out_mem_ctx, state->out.data); + status = state->status; + tevent_req_received(req); + return status; +} + +static const struct gensec_security_ops gensec_http_ntlm_security_ops = { + .name = "http_ntlm", + .auth_type = 0, + .client_start = gensec_http_ntlm_client_start, + .update_send = gensec_http_generic_update_send, + .update_recv = gensec_http_generic_update_recv, + .enabled = true, + .priority = GENSEC_EXTERNAL, +}; + +static const struct gensec_security_ops gensec_http_negotiate_security_ops = { + .name = "http_negotiate", + .auth_type = 0, + .client_start = gensec_http_negotiate_client_start, + .update_send = gensec_http_generic_update_send, + .update_recv = gensec_http_generic_update_recv, + .enabled = true, + .priority = GENSEC_EXTERNAL, + .glue = true, +}; + +_PUBLIC_ NTSTATUS gensec_http_generic_init(TALLOC_CTX *ctx) +{ + NTSTATUS status; + + status = gensec_register(ctx, &gensec_http_ntlm_security_ops); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("Failed to register '%s' gensec backend!\n", + gensec_http_ntlm_security_ops.name)); + return status; + } + + status = gensec_register(ctx, &gensec_http_negotiate_security_ops); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("Failed to register '%s' gensec backend!\n", + gensec_http_negotiate_security_ops.name)); + return status; + } + + return status; +} diff --git a/libcli/http/http.c b/libcli/http/http.c new file mode 100644 index 0000000..d20fc25 --- /dev/null +++ b/libcli/http/http.c @@ -0,0 +1,882 @@ +/* + Unix SMB/CIFS implementation. + + HTTP library + + Copyright (C) 2013 Samuel Cabrero <samuelcabrero@kernevil.me> + + 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 <http://www.gnu.org/licenses/>. +*/ + +#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; +} diff --git a/libcli/http/http.h b/libcli/http/http.h new file mode 100644 index 0000000..f219603 --- /dev/null +++ b/libcli/http/http.h @@ -0,0 +1,144 @@ +/* + Unix SMB/CIFS implementation. + + HTTP library + + Copyright (C) 2013 Samuel Cabrero <samuelcabrero@kernevil.me> + + 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 <http://www.gnu.org/licenses/>. +*/ + +#ifndef _HTTP_H_ +#define _HTTP_H_ + +#include <limits.h> +#include <sys/uio.h> + +#include <tevent.h> +#include "lib/tsocket/tsocket.h" + +/* Response codes */ +#define HTTP_OK 200 /* request completed ok */ +#define HTTP_NOCONTENT 204 /* request does not have content */ +#define HTTP_MOVEPERM 301 /* uri moved permanently */ +#define HTTP_MOVETEMP 302 /* uri moved temporarily */ +#define HTTP_NOTMODIFIED 304 /* page was not modified from last */ +#define HTTP_BADREQUEST 400 /* invalid http request was made */ +#define HTTP_NOTFOUND 404 /* could not find content for uri */ +#define HTTP_BADMETHOD 405 /* method not allowed for this uri */ +#define HTTP_ENTITYTOOLARGE 413 /* */ +#define HTTP_EXPECTATIONFAILED 417 /* can't handle this expectation */ +#define HTTP_INTERNAL 500 /* internal error */ +#define HTTP_NOTIMPLEMENTED 501 /* not implemented */ +#define HTTP_SERVUNAVAIL 503 /* server is not available */ + +#define HTTP_MAX_HEADER_SIZE 0x1FFFF + +struct cli_credentials; +struct loadparm_ctx; + +enum http_cmd_type { + HTTP_REQ_GET = 1 << 0, + HTTP_REQ_POST = 1 << 1, + HTTP_REQ_HEAD = 1 << 2, + HTTP_REQ_PUT = 1 << 3, + HTTP_REQ_DELETE = 1 << 4, + HTTP_REQ_OPTIONS = 1 << 5, + HTTP_REQ_TRACE = 1 << 6, + HTTP_REQ_CONNECT = 1 << 7, + HTTP_REQ_PATCH = 1 << 8, + HTTP_REQ_RPC_IN_DATA = 1 << 9, + HTTP_REQ_RPC_OUT_DATA = 1 << 10, +}; + +enum http_auth_method { + HTTP_AUTH_BASIC=1, + HTTP_AUTH_NTLM, + HTTP_AUTH_NEGOTIATE, +}; + +struct http_header { + struct http_header *next, *prev; + char *key; + char *value; +}; + +struct http_request { + enum http_cmd_type type; /* HTTP command type */ + char major; /* HTTP version major number */ + char minor; /* HTTP version minor number */ + char *uri; /* URI after HTTP request was parsed */ + struct http_header *headers; + 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; +}; + +/* HTTP header handling functions */ +int http_remove_header(struct http_header **, const char *); +int http_add_header(TALLOC_CTX *, struct http_header **, const char *, const char *); +int http_replace_header(TALLOC_CTX *, struct http_header **, const char *, const char *); + +/* HTTP(s) connect */ + +struct http_conn; +struct tstream_tls_params; + +struct tevent_req *http_connect_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + const char *http_server, + uint16_t http_port, + struct cli_credentials *credentials, + struct tstream_tls_params *tls_params); +int http_connect_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct http_conn **http_conn); + +struct tevent_req *http_disconnect_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct http_conn *http_conn); +int http_disconnect_recv(struct tevent_req *req); + +struct tevent_queue *http_conn_send_queue(struct http_conn *http_conn); +struct tstream_context *http_conn_tstream(struct http_conn *http_conn); + +/* HTTP request */ +struct tevent_req *http_send_request_send(TALLOC_CTX *, + struct tevent_context *, + struct http_conn *, + struct http_request *); +NTSTATUS http_send_request_recv(struct tevent_req *); + +/* HTTP response */ +struct tevent_req *http_read_response_send(TALLOC_CTX *, + struct tevent_context *, + struct http_conn *, + size_t max_content_length); +NTSTATUS http_read_response_recv(struct tevent_req *, + TALLOC_CTX *, + struct http_request **); + +/* HTTP authenticated request */ +struct tevent_req *http_send_auth_request_send(TALLOC_CTX *, + struct tevent_context *, + struct http_conn *, + const struct http_request *, + struct cli_credentials *, + struct loadparm_context *, + enum http_auth_method); +NTSTATUS http_send_auth_request_recv(struct tevent_req *); + +#endif /* _HTTP_H_ */ diff --git a/libcli/http/http_auth.c b/libcli/http/http_auth.c new file mode 100644 index 0000000..3dcf52c --- /dev/null +++ b/libcli/http/http_auth.c @@ -0,0 +1,383 @@ +/* + Unix SMB/CIFS implementation. + + HTTP library + + Copyright (C) 2014 Samuel Cabrero <samuelcabrero@kernevil.me> + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "http.h" +#include "http_internal.h" +#include "lib/util/tevent_ntstatus.h" +#include "lib/param/param.h" +#include "tevent.h" +#include "auth/gensec/gensec.h" +#include "auth/credentials/credentials.h" +#include "lib/util/data_blob.h" + +#undef strcasecmp +#undef strncasecmp + +/** + * Copy the request headers from src to dst + */ +static NTSTATUS http_copy_header(const struct http_request *src, + struct http_request *dst) +{ + struct http_header *h; + + dst->type = src->type; + dst->major = src->major; + dst->minor = src->minor; + dst->uri = talloc_strdup(dst, src->uri); + + for (h = src->headers; h != NULL; h = h->next) { + http_add_header(dst, &dst->headers, h->key, h->value); + } + dst->headers_size = src->headers_size; + + return NT_STATUS_OK; +} + +/* + * Retrieve the WWW-Authenticate header from server response based on the + * authentication scheme being used. + */ +static NTSTATUS http_parse_auth_response(const DATA_BLOB prefix, + struct http_request *auth_response, + DATA_BLOB *in) +{ + struct http_header *h; + + for (h = auth_response->headers; h != NULL; h = h->next) { + int cmp; + + cmp = strcasecmp(h->key, "WWW-Authenticate"); + if (cmp != 0) { + continue; + } + + cmp = strncasecmp(h->value, + (const char *)prefix.data, + prefix.length); + if (cmp != 0) { + continue; + } + + *in = data_blob_string_const(h->value); + return NT_STATUS_OK; + } + + return NT_STATUS_NOT_SUPPORTED; +} + +struct http_auth_state { + struct tevent_context *ev; + + struct http_conn *http_conn; + + enum http_auth_method auth; + DATA_BLOB prefix; + + struct gensec_security *gensec_ctx; + NTSTATUS gensec_status; + + const struct http_request *original_request; + struct http_request *next_request; + struct http_request *auth_response; +}; + + +static void http_send_auth_request_gensec_done(struct tevent_req *subreq); +static void http_send_auth_request_http_req_done(struct tevent_req *subreq); +static void http_send_auth_request_http_rep_done(struct tevent_req *subreq); + +struct tevent_req *http_send_auth_request_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct http_conn *http_conn, + const struct http_request *original_request, + struct cli_credentials *credentials, + struct loadparm_context *lp_ctx, + enum http_auth_method auth) +{ + struct tevent_req *req = NULL; + struct http_auth_state *state = NULL; + struct tevent_req *subreq = NULL; + DATA_BLOB gensec_in = data_blob_null; + NTSTATUS status; + struct http_header *h = NULL; + const char *mech_name = NULL; + + req = tevent_req_create(mem_ctx, &state, struct http_auth_state); + if (req == NULL) { + return NULL; + } + state->ev = ev; + state->http_conn = http_conn; + state->auth = auth; + state->original_request = original_request; + + status = gensec_init(); + if (tevent_req_nterror(req, status)) { + return tevent_req_post(req, ev); + } + + status = gensec_client_start(state, &state->gensec_ctx, + lpcfg_gensec_settings(state, lp_ctx)); + if (tevent_req_nterror(req, status)) { + return tevent_req_post(req, ev); + } + + status = gensec_set_credentials(state->gensec_ctx, credentials); + if (tevent_req_nterror(req, status)) { + return tevent_req_post(req, ev); + } + + for (h = original_request->headers; h != NULL; h = h->next) { + int cmp; + + cmp = strcasecmp(h->key, "Host"); + if (cmp != 0) { + continue; + } + + status = gensec_set_target_service(state->gensec_ctx, "http"); + if (tevent_req_nterror(req, status)) { + return tevent_req_post(req, ev); + } + + status = gensec_set_target_hostname(state->gensec_ctx, h->value); + if (tevent_req_nterror(req, status)) { + return tevent_req_post(req, ev); + } + break; + } + + switch (state->auth) { + case HTTP_AUTH_BASIC: + mech_name = "http_basic"; + state->prefix = data_blob_string_const("Basic"); + break; + case HTTP_AUTH_NTLM: + mech_name = "http_ntlm"; + state->prefix = data_blob_string_const("NTLM"); + break; + case HTTP_AUTH_NEGOTIATE: + mech_name = "http_negotiate"; + state->prefix = data_blob_string_const("Negotiate"); + break; + default: + tevent_req_nterror(req, NT_STATUS_NOT_SUPPORTED); + return tevent_req_post(req, ev); + } + + status = gensec_start_mech_by_name(state->gensec_ctx, mech_name); + if (tevent_req_nterror(req, status)) { + return tevent_req_post(req, ev); + } + + subreq = gensec_update_send(state, state->ev, + state->gensec_ctx, + gensec_in); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, http_send_auth_request_gensec_done, req); + + return req; +} + +static void http_send_auth_request_gensec_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, + struct tevent_req); + struct http_auth_state *state = + tevent_req_data(req, + struct http_auth_state); + DATA_BLOB gensec_out = data_blob_null; + NTSTATUS status; + int ret; + + TALLOC_FREE(state->auth_response); + + status = gensec_update_recv(subreq, state, &gensec_out); + TALLOC_FREE(subreq); + state->gensec_status = status; + if (NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) { + status = NT_STATUS_OK; + } + if (tevent_req_nterror(req, status)) { + return; + } + + state->next_request = talloc_zero(state, struct http_request); + if (tevent_req_nomem(state->next_request, req)) { + return; + } + + status = http_copy_header(state->original_request, state->next_request); + if (tevent_req_nterror(req, status)) { + return; + } + + if (!NT_STATUS_IS_OK(state->gensec_status)) { + /* + * More preprocessing required before we + * can include the content. + */ + ret = http_replace_header(state->next_request, + &state->next_request->headers, + "Content-Length", "0"); + if (ret != 0) { + tevent_req_oom(req); + return; + } + } else { + state->next_request->body = state->original_request->body; + } + + if (gensec_out.length > 0) { + ret = http_add_header(state->next_request, + &state->next_request->headers, + "Authorization", + (char *)gensec_out.data); + if (ret != 0) { + tevent_req_oom(req); + return; + } + data_blob_free(&gensec_out); + } + + subreq = http_send_request_send(state, state->ev, + state->http_conn, + state->next_request); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, + http_send_auth_request_http_req_done, + req); +} + +static void http_send_auth_request_http_req_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, + struct tevent_req); + struct http_auth_state *state = + tevent_req_data(req, + struct http_auth_state); + NTSTATUS status; + + TALLOC_FREE(state->next_request); + + status = http_send_request_recv(subreq); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + + /* + * If no more processing required, it is done + * + * The caller will use http_read_response_send/recv + * in order to get the high level response. + */ + if (NT_STATUS_IS_OK(state->gensec_status)) { + tevent_req_done(req); + return; + } + + /* + * 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->http_conn, + UINT16_MAX); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, + http_send_auth_request_http_rep_done, + req); +} + +static void http_send_auth_request_http_rep_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, + struct tevent_req); + struct http_auth_state *state = + tevent_req_data(req, + struct http_auth_state); + DATA_BLOB gensec_in = data_blob_null; + NTSTATUS status; + + status = http_read_response_recv(subreq, state, + &state->auth_response); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + 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->prefix, + state->auth_response, + &gensec_in); + if (tevent_req_nterror(req, status)) { + return; + } + + subreq = gensec_update_send(state, state->ev, + state->gensec_ctx, + gensec_in); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, http_send_auth_request_gensec_done, req); +} + +NTSTATUS http_send_auth_request_recv(struct tevent_req *req) +{ + NTSTATUS status; + + if (tevent_req_is_nterror(req, &status)) { + tevent_req_received(req); + return status; + } + tevent_req_received(req); + + return NT_STATUS_OK; +} diff --git a/libcli/http/http_conn.c b/libcli/http/http_conn.c new file mode 100644 index 0000000..ac39a0a --- /dev/null +++ b/libcli/http/http_conn.c @@ -0,0 +1,347 @@ +/* + Unix SMB/CIFS implementation. + + HTTP library + + Copyright (C) 2019 Ralph Boehme <slow@samba.org> + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "lib/util/tevent_ntstatus.h" +#include "libcli/dns/dns_lookup.h" +#include "lib/tsocket/tsocket.h" +#include "lib/util/util_net.h" +#include "lib/tls/tls.h" +#include "lib/util/tevent_unix.h" +#include "http.h" +#include "http_internal.h" + +struct http_connect_state { + struct tevent_context *ev; + const char *http_server; + const char *http_server_ip; + uint16_t http_port; + struct tsocket_address *local_address; + struct tsocket_address *remote_address; + struct cli_credentials *credentials; + struct tstream_tls_params *tls_params; + + struct http_conn *http_conn; +}; + +static void http_connect_dns_done(struct tevent_req *subreq); +static void http_connect_tcp_connect(struct tevent_req *req); +static void http_connect_tcp_done(struct tevent_req *subreq); +static void http_connect_tls_done(struct tevent_req *subreq); + +struct tevent_req *http_connect_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + const char *http_server, + uint16_t http_port, + struct cli_credentials *credentials, + struct tstream_tls_params *tls_params) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct http_connect_state *state = NULL; + int ret; + + DBG_DEBUG("Connecting to [%s] over HTTP%s\n", + http_server, tls_params != NULL ? "S" : ""); + + req = tevent_req_create(mem_ctx, &state, struct http_connect_state); + if (req == NULL) { + return NULL; + } + + *state = (struct http_connect_state) { + .ev = ev, + .http_port = http_port, + .credentials = credentials, + .tls_params = tls_params, + }; + + state->http_server = talloc_strdup(state, http_server); + if (tevent_req_nomem(state->http_server, req)) { + return tevent_req_post(req, ev); + } + + state->http_conn = talloc_zero(state, struct http_conn); + if (tevent_req_nomem(state->http_conn, req)) { + return tevent_req_post(req, ev); + } + + state->http_conn->send_queue = tevent_queue_create(state->http_conn, + "HTTP send queue"); + if (tevent_req_nomem(state->http_conn->send_queue, req)) { + return tevent_req_post(req, ev); + } + + ret = tsocket_address_inet_from_strings(state, + "ip", + NULL, + 0, + &state->local_address); + if (ret != 0) { + tevent_req_error(req, errno); + return tevent_req_post(req, ev); + } + + if (!is_ipaddress(http_server)) { + subreq = dns_lookup_send(state, + ev, + NULL, + http_server, + DNS_QCLASS_IN, + DNS_QTYPE_A); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, http_connect_dns_done, req); + return req; + } + state->http_server_ip = state->http_server; + + http_connect_tcp_connect(req); + if (!tevent_req_is_in_progress(req)) { + return tevent_req_post(req, ev); + } + + return req; +} + +static void http_connect_dns_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct http_connect_state *state = tevent_req_data( + req, struct http_connect_state); + struct dns_name_packet *dns_reply = NULL; + struct dns_res_rec *an = NULL; + uint16_t i; + int ret; + + ret = dns_lookup_recv(subreq, state, &dns_reply); + TALLOC_FREE(subreq); + if (ret != 0) { + tevent_req_error(req, ret); + return; + } + + for (i = 0; i < dns_reply->ancount; i++) { + an = &dns_reply->answers[i]; + if (an->rr_type == DNS_QTYPE_A) { + break; + } + } + if (i >= dns_reply->ancount) { + tevent_req_error(req, ENOENT); + return; + } + + state->http_server_ip = talloc_strdup(state, an->rdata.ipv4_record); + if (tevent_req_nomem(state->http_server_ip, req)) { + return; + } + http_connect_tcp_connect(req); +} + +static void http_connect_tcp_connect(struct tevent_req *req) +{ + struct http_connect_state *state = tevent_req_data( + req, struct http_connect_state); + struct tevent_req *subreq = NULL; + int ret; + + ret = tsocket_address_inet_from_strings(state, + "ip", + state->http_server_ip, + state->http_port, + &state->remote_address); + if (ret != 0) { + int saved_errno = errno; + + DBG_ERR("Cannot create remote socket address, error: %s (%d)\n", + strerror(errno), errno); + tevent_req_error(req, saved_errno); + return; + } + + subreq = tstream_inet_tcp_connect_send(state, + state->ev, + state->local_address, + state->remote_address); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, http_connect_tcp_done, req); +} + +static void http_connect_tcp_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct http_connect_state *state = tevent_req_data( + req, struct http_connect_state); + int error; + int ret; + + ret = tstream_inet_tcp_connect_recv(subreq, + &error, + state->http_conn, + &state->http_conn->tstreams.raw, + NULL); + TALLOC_FREE(subreq); + if (ret != 0) { + tevent_req_error(req, error); + return; + } + + state->http_conn->tstreams.active = state->http_conn->tstreams.raw; + DBG_DEBUG("Socket connected\n"); + + if (state->tls_params == NULL) { + tevent_req_done(req); + return; + } + + DBG_DEBUG("Starting TLS\n"); + + subreq = tstream_tls_connect_send(state, + state->ev, + state->http_conn->tstreams.active, + state->tls_params); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, http_connect_tls_done, req); +} + +static void http_connect_tls_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct http_connect_state *state = tevent_req_data( + req, struct http_connect_state); + int error; + int ret; + + ret = tstream_tls_connect_recv(subreq, + &error, + state->http_conn, + &state->http_conn->tstreams.tls); + TALLOC_FREE(subreq); + if (ret != 0) { + tevent_req_error(req, error); + return; + } + + state->http_conn->tstreams.active = state->http_conn->tstreams.tls; + + DBG_DEBUG("TLS handshake completed\n"); + tevent_req_done(req); +} + +int http_connect_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct http_conn **http_conn) +{ + struct http_connect_state *state = tevent_req_data( + req, struct http_connect_state); + int error; + + if (tevent_req_is_unix_error(req, &error)) { + tevent_req_received(req); + return error; + } + + *http_conn = talloc_move(mem_ctx, &state->http_conn); + tevent_req_received(req); + + return 0; +} + +struct tevent_queue *http_conn_send_queue(struct http_conn *http_conn) +{ + return http_conn->send_queue; +} + +struct tstream_context *http_conn_tstream(struct http_conn *http_conn) +{ + return http_conn->tstreams.active; +} + +struct http_conn_disconnect_state { + struct tevent_context *ev; + struct http_conn *http_conn; +}; + +static void http_conn_disconnect_done(struct tevent_req *subreq); + +struct tevent_req *http_disconnect_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct http_conn *http_conn) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct http_conn_disconnect_state *state = NULL; + + req = tevent_req_create(mem_ctx, &state, + struct http_conn_disconnect_state); + if (req == NULL) { + return NULL; + } + + *state = (struct http_conn_disconnect_state) { + .ev = ev, + .http_conn = http_conn, + }; + + if (http_conn->tstreams.active == NULL) { + tevent_req_error(req, ENOTCONN); + return tevent_req_post(req, ev); + } + + subreq = tstream_disconnect_send(state, ev, http_conn->tstreams.active); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, http_conn_disconnect_done, req); + + return req; +} + +static void http_conn_disconnect_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + int ret; + int error; + + ret = tstream_disconnect_recv(subreq, &error); + TALLOC_FREE(subreq); + if (ret == -1) { + tevent_req_error(req, error); + return; + } + + tevent_req_done(req); +} + +int http_disconnect_recv(struct tevent_req *req) +{ + return tevent_req_simple_recv_unix(req); +} diff --git a/libcli/http/http_internal.h b/libcli/http/http_internal.h new file mode 100644 index 0000000..ec17f7e --- /dev/null +++ b/libcli/http/http_internal.h @@ -0,0 +1,50 @@ +/* + Unix SMB/CIFS implementation. + + HTTP library + + Copyright (C) 2014 Samuel Cabrero <samuelcabrero@kernevil.me> + + 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 <http://www.gnu.org/licenses/>. +*/ + +#ifndef _HTTP_INTERNAL_H_ +#define _HTTP_INTERNAL_H_ + +enum http_parser_state { + HTTP_READING_FIRSTLINE, + HTTP_READING_HEADERS, + HTTP_READING_BODY, + HTTP_READING_TRAILER, + HTTP_READING_DONE, +}; + +enum http_read_status { + HTTP_ALL_DATA_READ, + HTTP_MORE_DATA_EXPECTED, + HTTP_DATA_CORRUPTED, + HTTP_REQUEST_CANCELED, + HTTP_DATA_TOO_LONG, +}; + +struct http_conn { + struct tevent_queue *send_queue; + struct { + struct tstream_context *raw; + struct tstream_context *tls; + struct tstream_context *active; + } tstreams; +}; + +#endif /* _HTTP_INTERNAL_H_ */ diff --git a/libcli/http/wscript_build b/libcli/http/wscript_build new file mode 100644 index 0000000..da8768f --- /dev/null +++ b/libcli/http/wscript_build @@ -0,0 +1,21 @@ +#!/usr/bin/env python + +bld.SAMBA_LIBRARY('http', + source='http.c http_auth.c http_conn.c', + deps='talloc tevent gensec dns_lookup', + private_library=True, +) + +bld.SAMBA_MODULE('gensec_http_basic', + source='gensec/basic.c', + subsystem='gensec', + init_function='gensec_http_basic_init', + deps='samba-util auth_session' +) + +bld.SAMBA_MODULE('gensec_http_generic', + source='gensec/generic.c', + subsystem='gensec', + init_function='gensec_http_generic_init', + deps='samba-util auth_session' +) |