diff options
Diffstat (limited to '')
-rw-r--r-- | libcli/http/http.c | 309 | ||||
-rw-r--r-- | libcli/http/http_internal.h | 4 | ||||
-rw-r--r-- | libcli/nbt/libnbt.h | 3 | ||||
-rw-r--r-- | libcli/nbt/nbtsocket.c | 44 | ||||
-rw-r--r-- | libcli/security/dom_sid.c | 14 | ||||
-rw-r--r-- | libcli/smb/smbXcli_base.c | 104 | ||||
-rw-r--r-- | libcli/smb/smbXcli_base.h | 5 |
7 files changed, 452 insertions, 31 deletions
diff --git a/libcli/http/http.c b/libcli/http/http.c index 96c573a..6f22214 100644 --- a/libcli/http/http.c +++ b/libcli/http/http.c @@ -28,16 +28,28 @@ #undef strcasecmp +enum http_body_type { + BODY_NONE = 0, + BODY_CONTENT_LENGTH, + BODY_CHUNKED, + BODY_ERROR = -1 +}; + /** * 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. + * @return 2 if response MUST use chunked encoding, + * 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) +static enum http_body_type http_response_needs_body( + struct http_request *req) { struct http_header *h = NULL; - if (!req) return -1; + if (!req) { + return BODY_ERROR; + } for (h = req->headers; h != NULL; h = h->next) { int cmp; @@ -45,6 +57,18 @@ static int http_response_needs_body(struct http_request *req) char c; unsigned long long v; + cmp = strcasecmp(h->key, "Transfer-Encoding"); + if (cmp == 0) { + cmp = strcasecmp(h->value, "chunked"); + if (cmp == 0) { + return BODY_CHUNKED; + } + /* unsupported Transfer-Encoding type */ + DBG_ERR("Unsupported transfer encoding type %s\n", + h->value); + return BODY_ERROR; + } + cmp = strcasecmp(h->key, "Content-Length"); if (cmp != 0) { continue; @@ -52,20 +76,25 @@ static int http_response_needs_body(struct http_request *req) n = sscanf(h->value, "%llu%c", &v, &c); if (n != 1) { - return -1; + return BODY_ERROR; } req->remaining_content_length = v; if (v != 0) { - return 1; + return BODY_CONTENT_LENGTH; } - return 0; + return BODY_NONE; } - return 0; + return BODY_NONE; } +struct http_chunk +{ + struct http_chunk *prev, *next; + DATA_BLOB blob; +}; struct http_read_response_state { enum http_parser_state parser_state; @@ -73,6 +102,7 @@ struct http_read_response_state { uint64_t max_content_length; DATA_BLOB buffer; struct http_request *response; + struct http_chunk *chunks; }; /** @@ -86,7 +116,7 @@ static enum http_read_status http_parse_headers(struct http_read_response_state char *key = NULL; char *value = NULL; int n = 0; - int ret; + enum http_body_type ret; /* Sanity checks */ if (!state || !state->response) { @@ -119,19 +149,24 @@ static enum http_read_status http_parse_headers(struct http_read_response_state ret = http_response_needs_body(state->response); switch (ret) { - case 1: + case BODY_CHUNKED: + DEBUG(11, ("%s: need to process chunks... %d\n", __func__, + state->response->response_code)); + state->parser_state = HTTP_READING_CHUNK_SIZE; + break; + case BODY_CONTENT_LENGTH: 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: + case BODY_NONE: DEBUG(11, ("%s: Skipping body for code %d\n", __func__, state->response->response_code)); state->parser_state = HTTP_READING_DONE; break; - case -1: + case BODY_ERROR: DEBUG(0, ("%s_: Error in http_response_needs_body\n", __func__)); TALLOC_FREE(line); return HTTP_DATA_CORRUPTED; @@ -162,6 +197,141 @@ error: return status; } +static bool http_response_process_chunks(struct http_read_response_state *state) +{ + struct http_chunk *chunk = NULL; + struct http_request *resp = state->response; + + for (chunk = state->chunks; chunk; chunk = chunk->next) { + DBG_DEBUG("processing chunk of size %zi\n", + chunk->blob.length); + if (resp->body.data == NULL) { + resp->body = chunk->blob; + chunk->blob = data_blob_null; + talloc_steal(resp, resp->body.data); + continue; + } + + resp->body.data = + talloc_realloc(resp, + resp->body.data, + uint8_t, + resp->body.length + chunk->blob.length); + if (!resp->body.data) { + return false; + } + memcpy(resp->body.data + resp->body.length, + chunk->blob.data, + chunk->blob.length); + + resp->body.length += chunk->blob.length; + + TALLOC_FREE(chunk->blob.data); + chunk->blob = data_blob_null; + } + return true; +} + +static enum http_read_status http_read_chunk_term(struct http_read_response_state *state) +{ + enum http_read_status status = HTTP_ALL_DATA_READ; + char *ptr = NULL; + char *line = NULL; + + /* Sanity checks */ + if (!state || !state->response) { + DBG_ERR("%s: Invalid Parameter\n", __func__); + return HTTP_DATA_CORRUPTED; + } + + line = talloc_strndup(state, (char *)state->buffer.data, state->buffer.length); + if (!line) { + DBG_ERR("%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; + } + + if (strncmp(line, "\r\n", 2) == 0) { + /* chunk terminator */ + if (state->parser_state == HTTP_READING_FINAL_CHUNK_TERM) { + if (http_response_process_chunks(state) == false) { + status = HTTP_DATA_CORRUPTED; + goto out; + } + state->parser_state = HTTP_READING_DONE; + } else { + state->parser_state = HTTP_READING_CHUNK_SIZE; + } + status = HTTP_ALL_DATA_READ; + goto out; + } + + status = HTTP_DATA_CORRUPTED; +out: + TALLOC_FREE(line); + return status; +} + +static enum http_read_status http_read_chunk_size(struct http_read_response_state *state) +{ + enum http_read_status status = HTTP_ALL_DATA_READ; + char *ptr = NULL; + char *line = NULL; + char *value = NULL; + int n = 0; + unsigned long long v; + + /* Sanity checks */ + if (!state || !state->response) { + DBG_ERR("%s: Invalid Parameter\n", __func__); + return HTTP_DATA_CORRUPTED; + } + + line = talloc_strndup(state, (char *)state->buffer.data, state->buffer.length); + if (!line) { + DBG_ERR("%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; + } + + n = sscanf(line, "%m[^\r\n]\r\n", &value); + if (n != 1) { + DBG_ERR("%s: Error parsing chunk size '%s'\n", __func__, line); + status = HTTP_DATA_CORRUPTED; + goto out; + } + + DBG_DEBUG("Got chunk size string %s\n", value); + n = sscanf(value, "%llx", &v); + if (n != 1) { + DBG_ERR("%s: Error parsing chunk size '%s'\n", __func__, line); + status = HTTP_DATA_CORRUPTED; + goto out; + } + DBG_DEBUG("Got chunk size %llu 0x%llx\n", v, v); + if (v == 0) { + state->parser_state = HTTP_READING_FINAL_CHUNK_TERM; + } else { + state->parser_state = HTTP_READING_CHUNK; + } + state->response->remaining_content_length = v; + status = HTTP_ALL_DATA_READ; +out: + if (value) { + free(value); + } + TALLOC_FREE(line); + return status; +} + /** * Parses the first line of a HTTP response */ @@ -301,6 +471,55 @@ static enum http_read_status http_read_body(struct http_read_response_state *sta return HTTP_ALL_DATA_READ; } +static enum http_read_status http_read_chunk(struct http_read_response_state *state) +{ + struct http_request *resp = state->response; + struct http_chunk *chunk = NULL; + size_t total = 0; + size_t prev = 0; + + if (state->buffer.length < resp->remaining_content_length) { + return HTTP_MORE_DATA_EXPECTED; + } + + for (chunk = state->chunks; chunk; chunk = chunk->next) { + total += chunk->blob.length; + } + + prev = total; + total = total + state->buffer.length; + if (total < prev) { + DBG_ERR("adding chunklen %zu to buf len %zu " + "will overflow\n", + state->buffer.length, + prev); + return HTTP_DATA_CORRUPTED; + } + if (total > state->max_content_length) { + DBG_DEBUG("size %zu exceeds " + "max content len %"PRIu64" skipping body\n", + total, + state->max_content_length); + state->parser_state = HTTP_READING_DONE; + goto out; + } + + /* chunk read */ + chunk = talloc_zero(state, struct http_chunk); + if (chunk == NULL) { + DBG_ERR("%s: Memory error\n", __func__); + return HTTP_DATA_CORRUPTED; + } + chunk->blob = state->buffer; + talloc_steal(chunk, chunk->blob.data); + DLIST_ADD_END(state->chunks, chunk); + state->parser_state = HTTP_READING_CHUNK_TERM; +out: + state->buffer = data_blob_null; + resp->remaining_content_length = 0; + 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; @@ -323,6 +542,16 @@ static enum http_read_status http_parse_buffer(struct http_read_response_state * case HTTP_READING_BODY: return http_read_body(state); break; + case HTTP_READING_FINAL_CHUNK_TERM: + case HTTP_READING_CHUNK_TERM: + return http_read_chunk_term(state); + break; + case HTTP_READING_CHUNK_SIZE: + return http_read_chunk_size(state); + break; + case HTTP_READING_CHUNK: + return http_read_chunk(state); + break; case HTTP_READING_TRAILER: return http_read_trailer(state); break; @@ -527,20 +756,60 @@ static int http_read_response_next_vector(struct tstream_context *stream, *_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); + case HTTP_MORE_DATA_EXPECTED: { + size_t toread = 1; + size_t total; + if (state->parser_state == HTTP_READING_BODY || + state->parser_state == HTTP_READING_CHUNK) { + struct http_request *resp = state->response; + toread = resp->remaining_content_length - + state->buffer.length; + } + + total = toread + state->buffer.length; + + if (total < state->buffer.length) { + DBG_ERR("adding %zu to buf len %zu " + "will overflow\n", + toread, + state->buffer.length); + return -1; + } + + /* + * test if content-length message exceeds the + * specified max_content_length + * Note: This check won't be hit at the moment + * due to an existing check in parse_headers + * which will skip the body. Check is here + * for completeness and to cater for future + * code changes. + */ + if (state->parser_state == HTTP_READING_BODY) { + if (total > state->max_content_length) { + DBG_ERR("content size %zu exceeds " + "max content len %"PRIu64"\n", + total, + state->max_content_length); + return -1; + } + } + + state->buffer.data = + talloc_realloc(state, state->buffer.data, + uint8_t, + state->buffer.length + toread); if (!state->buffer.data) { return -1; } - state->buffer.length++; + state->buffer.length += toread; vector[0].iov_base = (void *)(state->buffer.data + - state->buffer.length - 1); - vector[0].iov_len = 1; + state->buffer.length - toread); + vector[0].iov_len = toread; *_vector = vector; *_count = 1; break; + } case HTTP_DATA_CORRUPTED: case HTTP_REQUEST_CANCELED: case HTTP_DATA_TOO_LONG: @@ -603,7 +872,7 @@ static void http_read_response_done(struct tevent_req *subreq) { NTSTATUS status; struct tevent_req *req; - int ret; + enum http_body_type ret; int sys_errno; if (!subreq) { diff --git a/libcli/http/http_internal.h b/libcli/http/http_internal.h index ec17f7e..786ace6 100644 --- a/libcli/http/http_internal.h +++ b/libcli/http/http_internal.h @@ -28,6 +28,10 @@ enum http_parser_state { HTTP_READING_BODY, HTTP_READING_TRAILER, HTTP_READING_DONE, + HTTP_READING_CHUNK_SIZE, + HTTP_READING_CHUNK, + HTTP_READING_CHUNK_TERM, + HTTP_READING_FINAL_CHUNK_TERM, }; enum http_read_status { diff --git a/libcli/nbt/libnbt.h b/libcli/nbt/libnbt.h index 204484b..6a30c9f 100644 --- a/libcli/nbt/libnbt.h +++ b/libcli/nbt/libnbt.h @@ -331,6 +331,9 @@ NTSTATUS nbt_set_unexpected_handler(struct nbt_name_socket *nbtsock, void (*handler)(struct nbt_name_socket *, struct nbt_name_packet *, struct socket_address *), void *private_data); +NTSTATUS nbt_name_send_raw(struct nbt_name_socket *nbtsock, + struct socket_address *dest, + const DATA_BLOB pkt_blob); NTSTATUS nbt_name_reply_send(struct nbt_name_socket *nbtsock, struct socket_address *dest, struct nbt_name_packet *request); diff --git a/libcli/nbt/nbtsocket.c b/libcli/nbt/nbtsocket.c index 47e73cf..b2945ad 100644 --- a/libcli/nbt/nbtsocket.c +++ b/libcli/nbt/nbtsocket.c @@ -448,6 +448,50 @@ failed: return NULL; } +/* + send off a nbt name packet +*/ +_PUBLIC_ NTSTATUS nbt_name_send_raw(struct nbt_name_socket *nbtsock, + struct socket_address *dest, + const DATA_BLOB pkt_blob) +{ + struct nbt_name_request *req; + + req = talloc_zero(nbtsock, struct nbt_name_request); + NT_STATUS_HAVE_NO_MEMORY(req); + + req->nbtsock = nbtsock; + req->dest = socket_address_copy(req, dest); + if (req->dest == NULL) { + goto failed; + } + req->state = NBT_REQUEST_SEND; + /* + * We don't expect a response so + * just pretent it is a request, + * but we really don't care about the + * content. + */ + req->is_reply = true; + + req->encoded = data_blob_dup_talloc(req, pkt_blob); + if (req->encoded.length != pkt_blob.length) { + goto failed; + } + + talloc_set_destructor(req, nbt_name_request_destructor); + + DLIST_ADD_END(nbtsock->send_queue, req); + + TEVENT_FD_WRITEABLE(nbtsock->fde); + + return NT_STATUS_OK; + +failed: + talloc_free(req); + return NT_STATUS_NO_MEMORY; +} + /* send off a nbt name reply diff --git a/libcli/security/dom_sid.c b/libcli/security/dom_sid.c index eaece2a..21012b7 100644 --- a/libcli/security/dom_sid.c +++ b/libcli/security/dom_sid.c @@ -28,6 +28,7 @@ #include "librpc/gen_ndr/security.h" #include "dom_sid.h" #include "lib/util/smb_strtox.h" +#include "lib/util/tsort.h" /***************************************************************** Compare the auth portion of two sids. @@ -46,11 +47,12 @@ int dom_sid_compare_auth(const struct dom_sid *sid1, return 1; if (sid1->sid_rev_num != sid2->sid_rev_num) - return sid1->sid_rev_num - sid2->sid_rev_num; + return NUMERIC_CMP(sid1->sid_rev_num, sid2->sid_rev_num); for (i = 0; i < 6; i++) - if (sid1->id_auth[i] != sid2->id_auth[i]) - return sid1->id_auth[i] - sid2->id_auth[i]; + if (sid1->id_auth[i] != sid2->id_auth[i]) { + return NUMERIC_CMP(sid1->id_auth[i], sid2->id_auth[i]); + } return 0; } @@ -71,9 +73,9 @@ int dom_sid_compare(const struct dom_sid *sid1, const struct dom_sid *sid2) return 1; /* Compare most likely different rids, first: i.e start at end */ - if (sid1->num_auths != sid2->num_auths) - return sid1->num_auths - sid2->num_auths; - + if (sid1->num_auths != sid2->num_auths) { + return NUMERIC_CMP(sid1->num_auths, sid2->num_auths); + } for (i = sid1->num_auths-1; i >= 0; --i) { if (sid1->sub_auths[i] < sid2->sub_auths[i]) { return -1; diff --git a/libcli/smb/smbXcli_base.c b/libcli/smb/smbXcli_base.c index a52a615..87acddf 100644 --- a/libcli/smb/smbXcli_base.c +++ b/libcli/smb/smbXcli_base.c @@ -166,6 +166,13 @@ struct smb2cli_session { uint16_t channel_sequence; bool replay_active; bool require_signed_response; + + /* + * The following are just for torture tests + */ + bool anonymous_signing; + bool anonymous_encryption; + bool no_signing_disconnect; }; struct smbXcli_session { @@ -3999,6 +4006,9 @@ static NTSTATUS smb2cli_conn_dispatch_incoming(struct smbXcli_conn *conn, if (NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_NAME_DELETED) || NT_STATUS_EQUAL(status, NT_STATUS_FILE_CLOSED) || + (NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED) && + session != NULL && + session->smb2->no_signing_disconnect) || NT_STATUS_EQUAL(status, NT_STATUS_INVALID_PARAMETER)) { /* * if the server returns @@ -4042,8 +4052,29 @@ static NTSTATUS smb2cli_conn_dispatch_incoming(struct smbXcli_conn *conn, /* * If the signing check fails, we disconnect * the connection. + * + * Unless + * smb2cli_session_torture_no_signing_disconnect + * was called in torture tests */ - return signing_status; + + if (!NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) { + return signing_status; + } + + if (!NT_STATUS_EQUAL(status, signing_status)) { + return signing_status; + } + + if (session == NULL) { + return signing_status; + } + + if (!session->smb2->no_signing_disconnect) { + return signing_status; + } + + state->smb2.signing_skipped = true; } } @@ -6332,6 +6363,23 @@ void smb2cli_session_require_signed_response(struct smbXcli_session *session, session->smb2->require_signed_response = require_signed_response; } +void smb2cli_session_torture_anonymous_signing(struct smbXcli_session *session, + bool anonymous_signing) +{ + session->smb2->anonymous_signing = anonymous_signing; +} + +void smb2cli_session_torture_anonymous_encryption(struct smbXcli_session *session, + bool anonymous_encryption) +{ + session->smb2->anonymous_encryption = anonymous_encryption; +} + +void smb2cli_session_torture_no_signing_disconnect(struct smbXcli_session *session) +{ + session->smb2->no_signing_disconnect = true; +} + NTSTATUS smb2cli_session_update_preauth(struct smbXcli_session *session, const struct iovec *iov) { @@ -6432,6 +6480,10 @@ NTSTATUS smb2cli_session_set_session_key(struct smbXcli_session *session, conn->protocol, preauth_hash); + if (session->smb2->anonymous_encryption) { + goto skip_signing_key; + } + status = smb2_signing_key_sign_create(session->smb2, conn->smb2.server.sign_algo, &_session_key, @@ -6441,6 +6493,15 @@ NTSTATUS smb2cli_session_set_session_key(struct smbXcli_session *session, return status; } + if (session->smb2->anonymous_signing) { + /* + * skip encryption and application keys + */ + goto skip_application_key; + } + +skip_signing_key: + status = smb2_signing_key_cipher_create(session->smb2, conn->smb2.server.cipher, &_session_key, @@ -6459,6 +6520,10 @@ NTSTATUS smb2cli_session_set_session_key(struct smbXcli_session *session, return status; } + if (session->smb2->anonymous_encryption) { + goto skip_application_key; + } + status = smb2_signing_key_sign_create(session->smb2, conn->smb2.server.sign_algo, &_session_key, @@ -6468,6 +6533,8 @@ NTSTATUS smb2cli_session_set_session_key(struct smbXcli_session *session, return status; } +skip_application_key: + status = smb2_signing_key_copy(session, session->smb2->signing_key, &session->smb2_channel.signing_key); @@ -6477,6 +6544,18 @@ NTSTATUS smb2cli_session_set_session_key(struct smbXcli_session *session, check_signature = conn->mandatory_signing; + if (conn->protocol >= PROTOCOL_SMB3_11) { + check_signature = true; + } + + if (session->smb2->anonymous_signing) { + check_signature = false; + } + + if (session->smb2->anonymous_encryption) { + check_signature = false; + } + hdr_flags = IVAL(recv_iov[0].iov_base, SMB2_HDR_FLAGS); if (hdr_flags & SMB2_HDR_FLAG_SIGNED) { /* @@ -6492,10 +6571,6 @@ NTSTATUS smb2cli_session_set_session_key(struct smbXcli_session *session, check_signature = true; } - if (conn->protocol >= PROTOCOL_SMB3_11) { - check_signature = true; - } - if (check_signature) { status = smb2_signing_check_pdu(session->smb2_channel.signing_key, recv_iov, 3); @@ -6527,6 +6602,15 @@ NTSTATUS smb2cli_session_set_session_key(struct smbXcli_session *session, session->smb2->should_encrypt = false; } + if (session->smb2->anonymous_signing) { + session->smb2->should_sign = true; + } + + if (session->smb2->anonymous_encryption) { + session->smb2->should_encrypt = true; + session->smb2->should_sign = false; + } + /* * CCM and GCM algorithms must never have their * nonce wrap, or the security of the whole @@ -6698,6 +6782,16 @@ NTSTATUS smb2cli_session_set_channel_key(struct smbXcli_session *session, NTSTATUS smb2cli_session_encryption_on(struct smbXcli_session *session) { + if (session->smb2->anonymous_signing) { + return NT_STATUS_INVALID_PARAMETER_MIX; + } + + if (session->smb2->anonymous_encryption) { + SMB_ASSERT(session->smb2->should_encrypt); + SMB_ASSERT(!session->smb2->should_sign); + return NT_STATUS_OK; + } + if (!session->smb2->should_sign) { /* * We need required signing on the session diff --git a/libcli/smb/smbXcli_base.h b/libcli/smb/smbXcli_base.h index 25ccd84..69fa131 100644 --- a/libcli/smb/smbXcli_base.h +++ b/libcli/smb/smbXcli_base.h @@ -535,6 +535,11 @@ void smb2cli_session_start_replay(struct smbXcli_session *session); void smb2cli_session_stop_replay(struct smbXcli_session *session); void smb2cli_session_require_signed_response(struct smbXcli_session *session, bool require_signed_response); +void smb2cli_session_torture_anonymous_signing(struct smbXcli_session *session, + bool anonymous_signing); +void smb2cli_session_torture_anonymous_encryption(struct smbXcli_session *session, + bool anonymous_encryption); +void smb2cli_session_torture_no_signing_disconnect(struct smbXcli_session *session); NTSTATUS smb2cli_session_update_preauth(struct smbXcli_session *session, const struct iovec *iov); NTSTATUS smb2cli_session_set_session_key(struct smbXcli_session *session, |