diff options
Diffstat (limited to 'source4/libcli/smb2')
27 files changed, 4584 insertions, 0 deletions
diff --git a/source4/libcli/smb2/break.c b/source4/libcli/smb2/break.c new file mode 100644 index 0000000000..fe0cceb829 --- /dev/null +++ b/source4/libcli/smb2/break.c @@ -0,0 +1,74 @@ +/* + Unix SMB/CIFS implementation. + + SMB2 client oplock break handling + + Copyright (C) Stefan Metzmacher 2008 + + 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 "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" + +/* + send a break request +*/ +struct smb2_request *smb2_break_send(struct smb2_tree *tree, struct smb2_break *io) +{ + struct smb2_request *req; + + req = smb2_request_init_tree(tree, SMB2_OP_BREAK, 0x18, false, 0); + if (req == NULL) return NULL; + + SCVAL(req->out.body, 0x02, io->in.oplock_level); + SCVAL(req->out.body, 0x03, io->in.reserved); + SIVAL(req->out.body, 0x04, io->in.reserved2); + smb2_push_handle(req->out.body+0x08, &io->in.file.handle); + + smb2_transport_send(req); + + return req; +} + + +/* + recv a break reply +*/ +NTSTATUS smb2_break_recv(struct smb2_request *req, struct smb2_break *io) +{ + if (!smb2_request_receive(req) || + !smb2_request_is_ok(req)) { + return smb2_request_destroy(req); + } + + SMB2_CHECK_PACKET_RECV(req, 0x18, false); + + io->out.oplock_level = CVAL(req->in.body, 0x02); + io->out.reserved = CVAL(req->in.body, 0x03); + io->out.reserved2 = IVAL(req->in.body, 0x04); + smb2_pull_handle(req->in.body+0x08, &io->out.file.handle); + + return smb2_request_destroy(req); +} + +/* + sync flush request +*/ +NTSTATUS smb2_break(struct smb2_tree *tree, struct smb2_break *io) +{ + struct smb2_request *req = smb2_break_send(tree, io); + return smb2_break_recv(req, io); +} diff --git a/source4/libcli/smb2/cancel.c b/source4/libcli/smb2/cancel.c new file mode 100644 index 0000000000..65f02187c1 --- /dev/null +++ b/source4/libcli/smb2/cancel.c @@ -0,0 +1,77 @@ +/* + Unix SMB/CIFS implementation. + + SMB2 client notify calls + + Copyright (C) Stefan Metzmacher 2006 + + 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 "libcli/raw/libcliraw.h" +#include "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" + +/* + send a cancel request +*/ +NTSTATUS smb2_cancel(struct smb2_request *r) +{ + NTSTATUS status; + struct smb2_request *c; + uint32_t old_timeout; + uint64_t old_seqnum; + + /* + * if we don't get a pending id yet, we just + * mark the request for pending, so that we directly + * send the cancel after getting the pending id + */ + if (!r->cancel.can_cancel) { + r->cancel.do_cancel++; + return NT_STATUS_OK; + } + + /* we don't want a seqmun for a SMB2 Cancel */ + old_seqnum = r->transport->seqnum; + c = smb2_request_init(r->transport, SMB2_OP_CANCEL, 0x04, false, 0); + r->transport->seqnum = old_seqnum; + NT_STATUS_HAVE_NO_MEMORY(c); + c->seqnum = 0; + + SIVAL(c->out.hdr, SMB2_HDR_FLAGS, 0x00000002); + SSVAL(c->out.hdr, SMB2_HDR_CREDIT, 0x0030); + SIVAL(c->out.hdr, SMB2_HDR_PID, r->cancel.pending_id); + SBVAL(c->out.hdr, SMB2_HDR_MESSAGE_ID, c->seqnum); + if (r->session) { + SBVAL(c->out.hdr, SMB2_HDR_SESSION_ID, r->session->uid); + } + + SSVAL(c->out.body, 0x02, 0); + + old_timeout = c->transport->options.request_timeout; + c->transport->options.request_timeout = 0; + smb2_transport_send(c); + c->transport->options.request_timeout = old_timeout; + + if (c->state == SMB2_REQUEST_ERROR) { + status = c->status; + } else { + status = NT_STATUS_OK; + } + + talloc_free(c); + return status; +} diff --git a/source4/libcli/smb2/close.c b/source4/libcli/smb2/close.c new file mode 100644 index 0000000000..4e6f33095f --- /dev/null +++ b/source4/libcli/smb2/close.c @@ -0,0 +1,80 @@ +/* + Unix SMB/CIFS implementation. + + SMB2 client close handling + + Copyright (C) Andrew Tridgell 2005 + + 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 "libcli/raw/libcliraw.h" +#include "libcli/raw/raw_proto.h" +#include "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" + +/* + send a close request +*/ +struct smb2_request *smb2_close_send(struct smb2_tree *tree, struct smb2_close *io) +{ + struct smb2_request *req; + + req = smb2_request_init_tree(tree, SMB2_OP_CLOSE, 0x18, false, 0); + if (req == NULL) return NULL; + + SSVAL(req->out.body, 0x02, io->in.flags); + SIVAL(req->out.body, 0x04, 0); /* pad */ + smb2_push_handle(req->out.body+0x08, &io->in.file.handle); + + smb2_transport_send(req); + + return req; +} + + +/* + recv a close reply +*/ +NTSTATUS smb2_close_recv(struct smb2_request *req, struct smb2_close *io) +{ + if (!smb2_request_receive(req) || + !smb2_request_is_ok(req)) { + return smb2_request_destroy(req); + } + + SMB2_CHECK_PACKET_RECV(req, 0x3C, false); + + io->out.flags = SVAL(req->in.body, 0x02); + io->out._pad = IVAL(req->in.body, 0x04); + io->out.create_time = smbcli_pull_nttime(req->in.body, 0x08); + io->out.access_time = smbcli_pull_nttime(req->in.body, 0x10); + io->out.write_time = smbcli_pull_nttime(req->in.body, 0x18); + io->out.change_time = smbcli_pull_nttime(req->in.body, 0x20); + io->out.alloc_size = BVAL(req->in.body, 0x28); + io->out.size = BVAL(req->in.body, 0x30); + io->out.file_attr = IVAL(req->in.body, 0x38); + + return smb2_request_destroy(req); +} + +/* + sync close request +*/ +NTSTATUS smb2_close(struct smb2_tree *tree, struct smb2_close *io) +{ + struct smb2_request *req = smb2_close_send(tree, io); + return smb2_close_recv(req, io); +} diff --git a/source4/libcli/smb2/config.mk b/source4/libcli/smb2/config.mk new file mode 100644 index 0000000000..322bca1416 --- /dev/null +++ b/source4/libcli/smb2/config.mk @@ -0,0 +1,10 @@ +[SUBSYSTEM::LIBCLI_SMB2] +PUBLIC_DEPENDENCIES = LIBCLI_RAW LIBPACKET gensec + +LIBCLI_SMB2_OBJ_FILES = $(addprefix $(libclisrcdir)/smb2/, \ + transport.o request.o negprot.o session.o tcon.o \ + create.o close.o connect.o getinfo.o write.o read.o \ + setinfo.o find.o ioctl.o logoff.o tdis.o flush.o \ + lock.o notify.o cancel.o keepalive.o break.o util.o signing.o) + +$(eval $(call proto_header_template,$(libclisrcdir)/smb2/smb2_proto.h,$(LIBCLI_SMB2_OBJ_FILES:.o=.c))) diff --git a/source4/libcli/smb2/connect.c b/source4/libcli/smb2/connect.c new file mode 100644 index 0000000000..43151943d3 --- /dev/null +++ b/source4/libcli/smb2/connect.c @@ -0,0 +1,291 @@ +/* + Unix SMB/CIFS implementation. + + SMB2 composite connection setup + + Copyright (C) Andrew Tridgell 2005 + + 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 "libcli/raw/libcliraw.h" +#include "libcli/raw/raw_proto.h" +#include "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" +#include "libcli/composite/composite.h" +#include "libcli/resolve/resolve.h" +#include "param/param.h" + +struct smb2_connect_state { + struct cli_credentials *credentials; + struct resolve_context *resolve_ctx; + const char *host; + const char *share; + struct smbcli_options options; + struct smb2_negprot negprot; + struct smb2_tree_connect tcon; + struct smb2_session *session; + struct smb2_tree *tree; +}; + +/* + continue after tcon reply +*/ +static void continue_tcon(struct smb2_request *req) +{ + struct composite_context *c = talloc_get_type(req->async.private_data, + struct composite_context); + struct smb2_connect_state *state = talloc_get_type(c->private_data, + struct smb2_connect_state); + + c->status = smb2_tree_connect_recv(req, &state->tcon); + if (!composite_is_ok(c)) return; + + state->tree->tid = state->tcon.out.tid; + + composite_done(c); +} + +/* + continue after a session setup +*/ +static void continue_session(struct composite_context *creq) +{ + struct composite_context *c = talloc_get_type(creq->async.private_data, + struct composite_context); + struct smb2_connect_state *state = talloc_get_type(c->private_data, + struct smb2_connect_state); + struct smb2_request *req; + + c->status = smb2_session_setup_spnego_recv(creq); + if (!composite_is_ok(c)) return; + + state->tree = smb2_tree_init(state->session, state, true); + if (composite_nomem(state->tree, c)) return; + + state->tcon.in.reserved = 0; + state->tcon.in.path = talloc_asprintf(state, "\\\\%s\\%s", + state->host, state->share); + if (composite_nomem(state->tcon.in.path, c)) return; + + req = smb2_tree_connect_send(state->tree, &state->tcon); + if (composite_nomem(req, c)) return; + + req->async.fn = continue_tcon; + req->async.private_data = c; +} + +/* + continue after negprot reply +*/ +static void continue_negprot(struct smb2_request *req) +{ + struct composite_context *c = talloc_get_type(req->async.private_data, + struct composite_context); + struct smb2_connect_state *state = talloc_get_type(c->private_data, + struct smb2_connect_state); + struct smb2_transport *transport = req->transport; + struct composite_context *creq; + + c->status = smb2_negprot_recv(req, c, &state->negprot); + if (!composite_is_ok(c)) return; + + transport->negotiate.system_time = state->negprot.out.system_time; + transport->negotiate.server_start_time = state->negprot.out.server_start_time; + transport->negotiate.security_mode = state->negprot.out.security_mode; + + switch (transport->options.signing) { + case SMB_SIGNING_OFF: + if (transport->negotiate.security_mode & SMB2_NEGOTIATE_SIGNING_REQUIRED) { + composite_error(c, NT_STATUS_ACCESS_DENIED); + return; + } + transport->signing_required = false; + break; + case SMB_SIGNING_SUPPORTED: + if (transport->negotiate.security_mode & SMB2_NEGOTIATE_SIGNING_REQUIRED) { + transport->signing_required = true; + } else { + transport->signing_required = false; + } + break; + case SMB_SIGNING_AUTO: + if (transport->negotiate.security_mode & SMB2_NEGOTIATE_SIGNING_ENABLED) { + transport->signing_required = true; + } else { + transport->signing_required = false; + } + break; + case SMB_SIGNING_REQUIRED: + if (transport->negotiate.security_mode & SMB2_NEGOTIATE_SIGNING_ENABLED) { + transport->signing_required = true; + } else { + composite_error(c, NT_STATUS_ACCESS_DENIED); + return; + } + break; + } + + + state->session = smb2_session_init(transport, global_loadparm, state, true); + if (composite_nomem(state->session, c)) return; + + creq = smb2_session_setup_spnego_send(state->session, state->credentials); + + composite_continue(c, creq, continue_session, c); +} + +/* + continue after a socket connect completes +*/ +static void continue_socket(struct composite_context *creq) +{ + struct composite_context *c = talloc_get_type(creq->async.private_data, + struct composite_context); + struct smb2_connect_state *state = talloc_get_type(c->private_data, + struct smb2_connect_state); + struct smbcli_socket *sock; + struct smb2_transport *transport; + struct smb2_request *req; + uint16_t dialects[2]; + + c->status = smbcli_sock_connect_recv(creq, state, &sock); + if (!composite_is_ok(c)) return; + + transport = smb2_transport_init(sock, state, &state->options); + if (composite_nomem(transport, c)) return; + + ZERO_STRUCT(state->negprot); + state->negprot.in.dialect_count = 2; + switch (transport->options.signing) { + case SMB_SIGNING_OFF: + state->negprot.in.security_mode = 0; + break; + case SMB_SIGNING_SUPPORTED: + case SMB_SIGNING_AUTO: + state->negprot.in.security_mode = SMB2_NEGOTIATE_SIGNING_ENABLED; + break; + case SMB_SIGNING_REQUIRED: + state->negprot.in.security_mode = + SMB2_NEGOTIATE_SIGNING_ENABLED | SMB2_NEGOTIATE_SIGNING_REQUIRED; + break; + } + state->negprot.in.capabilities = 0; + unix_to_nt_time(&state->negprot.in.start_time, time(NULL)); + dialects[0] = 0; + dialects[1] = SMB2_DIALECT_REVISION; + state->negprot.in.dialects = dialects; + + req = smb2_negprot_send(transport, &state->negprot); + if (composite_nomem(req, c)) return; + + req->async.fn = continue_negprot; + req->async.private_data = c; +} + + +/* + continue after a resolve finishes +*/ +static void continue_resolve(struct composite_context *creq) +{ + struct composite_context *c = talloc_get_type(creq->async.private_data, + struct composite_context); + struct smb2_connect_state *state = talloc_get_type(c->private_data, + struct smb2_connect_state); + const char *addr; + const char *ports[2] = { "445", NULL }; + + c->status = resolve_name_recv(creq, state, &addr); + if (!composite_is_ok(c)) return; + + creq = smbcli_sock_connect_send(state, addr, ports, state->host, state->resolve_ctx, c->event_ctx); + + composite_continue(c, creq, continue_socket, c); +} + +/* + a composite function that does a full negprot/sesssetup/tcon, returning + a connected smb2_tree + */ +struct composite_context *smb2_connect_send(TALLOC_CTX *mem_ctx, + const char *host, + const char *share, + struct resolve_context *resolve_ctx, + struct cli_credentials *credentials, + struct event_context *ev, + struct smbcli_options *options) +{ + struct composite_context *c; + struct smb2_connect_state *state; + struct nbt_name name; + struct composite_context *creq; + + c = composite_create(mem_ctx, ev); + if (c == NULL) return NULL; + + state = talloc(c, struct smb2_connect_state); + if (composite_nomem(state, c)) return c; + c->private_data = state; + + state->credentials = credentials; + state->options = *options; + state->host = talloc_strdup(c, host); + if (composite_nomem(state->host, c)) return c; + state->share = talloc_strdup(c, share); + if (composite_nomem(state->share, c)) return c; + state->resolve_ctx = talloc_reference(state, resolve_ctx); + + ZERO_STRUCT(name); + name.name = host; + + creq = resolve_name_send(resolve_ctx, &name, c->event_ctx); + composite_continue(c, creq, continue_resolve, c); + return c; +} + +/* + receive a connect reply +*/ +NTSTATUS smb2_connect_recv(struct composite_context *c, TALLOC_CTX *mem_ctx, + struct smb2_tree **tree) +{ + NTSTATUS status; + struct smb2_connect_state *state = talloc_get_type(c->private_data, + struct smb2_connect_state); + status = composite_wait(c); + if (NT_STATUS_IS_OK(status)) { + *tree = talloc_steal(mem_ctx, state->tree); + } + talloc_free(c); + return status; +} + +/* + sync version of smb2_connect +*/ +NTSTATUS smb2_connect(TALLOC_CTX *mem_ctx, + const char *host, const char *share, + struct resolve_context *resolve_ctx, + struct cli_credentials *credentials, + struct smb2_tree **tree, + struct event_context *ev, + struct smbcli_options *options) +{ + struct composite_context *c = smb2_connect_send(mem_ctx, host, share, + resolve_ctx, + credentials, ev, options); + return smb2_connect_recv(c, mem_ctx, tree); +} diff --git a/source4/libcli/smb2/create.c b/source4/libcli/smb2/create.c new file mode 100644 index 0000000000..8a40e56a00 --- /dev/null +++ b/source4/libcli/smb2/create.c @@ -0,0 +1,419 @@ +/* + Unix SMB/CIFS implementation. + + SMB2 client tree handling + + Copyright (C) Andrew Tridgell 2005 + + 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 "libcli/raw/libcliraw.h" +#include "libcli/raw/raw_proto.h" +#include "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" +#include "librpc/gen_ndr/ndr_security.h" + + +/* + parse a set of SMB2 create blobs +*/ +NTSTATUS smb2_create_blob_parse(TALLOC_CTX *mem_ctx, const DATA_BLOB buffer, + struct smb2_create_blobs *blobs) +{ + const uint8_t *data = buffer.data; + uint32_t remaining = buffer.length; + + while (remaining > 0) { + uint32_t next; + uint32_t name_offset, name_length; + uint32_t reserved, data_offset; + uint32_t data_length; + char *tag; + DATA_BLOB b; + NTSTATUS status; + + if (remaining < 16) { + return NT_STATUS_INVALID_PARAMETER; + } + next = IVAL(data, 0); + name_offset = SVAL(data, 4); + name_length = SVAL(data, 6); + reserved = SVAL(data, 8); + data_offset = SVAL(data, 10); + data_length = IVAL(data, 12); + + if ((next & 0x7) != 0 || + next > remaining || + name_offset < 16 || + name_offset > remaining || + name_length != 4 || /* windows enforces this */ + name_offset + name_length > remaining || + data_offset < name_offset + name_length || + data_offset > remaining || + data_offset + (uint64_t)data_length > remaining) { + return NT_STATUS_INVALID_PARAMETER; + } + + tag = talloc_strndup(mem_ctx, (const char *)data + name_offset, name_length); + if (tag == NULL) { + return NT_STATUS_NO_MEMORY; + } + + b = data_blob_const(data+data_offset, data_length); + status = smb2_create_blob_add(mem_ctx, blobs, tag, b); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + talloc_free(tag); + + if (next == 0) break; + + remaining -= next; + data += next; + + if (remaining < 16) { + return NT_STATUS_INVALID_PARAMETER; + } + } + + return NT_STATUS_OK; +} + + +/* + add a blob to a smb2_create attribute blob +*/ +static NTSTATUS smb2_create_blob_push_one(TALLOC_CTX *mem_ctx, DATA_BLOB *buffer, + const struct smb2_create_blob *blob, + bool last) +{ + uint32_t ofs = buffer->length; + size_t tag_length = strlen(blob->tag); + uint8_t pad = smb2_padding_size(blob->data.length+tag_length, 4); + + if (!data_blob_realloc(mem_ctx, buffer, + buffer->length + 0x14 + tag_length + blob->data.length + pad)) + return NT_STATUS_NO_MEMORY; + + if (last) { + SIVAL(buffer->data, ofs+0x00, 0); + } else { + SIVAL(buffer->data, ofs+0x00, 0x14 + tag_length + blob->data.length + pad); + } + SSVAL(buffer->data, ofs+0x04, 0x10); /* offset of tag */ + SIVAL(buffer->data, ofs+0x06, tag_length); /* tag length */ + SSVAL(buffer->data, ofs+0x0A, 0x14 + tag_length); /* offset of data */ + SIVAL(buffer->data, ofs+0x0C, blob->data.length); + memcpy(buffer->data+ofs+0x10, blob->tag, tag_length); + SIVAL(buffer->data, ofs+0x10+tag_length, 0); /* pad? */ + memcpy(buffer->data+ofs+0x14+tag_length, blob->data.data, blob->data.length); + memset(buffer->data+ofs+0x14+tag_length+blob->data.length, 0, pad); + + return NT_STATUS_OK; +} + + +/* + create a buffer of a set of create blobs +*/ +NTSTATUS smb2_create_blob_push(TALLOC_CTX *mem_ctx, DATA_BLOB *buffer, + const struct smb2_create_blobs blobs) +{ + int i; + NTSTATUS status; + + *buffer = data_blob(NULL, 0); + for (i=0; i < blobs.num_blobs; i++) { + bool last = false; + const struct smb2_create_blob *c; + + if ((i + 1) == blobs.num_blobs) { + last = true; + } + + c = &blobs.blobs[i]; + status = smb2_create_blob_push_one(mem_ctx, buffer, c, last); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } + return NT_STATUS_OK; +} + + +NTSTATUS smb2_create_blob_add(TALLOC_CTX *mem_ctx, struct smb2_create_blobs *b, + const char *tag, DATA_BLOB data) +{ + struct smb2_create_blob *array; + + array = talloc_realloc(mem_ctx, b->blobs, + struct smb2_create_blob, + b->num_blobs + 1); + NT_STATUS_HAVE_NO_MEMORY(array); + b->blobs = array; + + b->blobs[b->num_blobs].tag = talloc_strdup(b->blobs, tag); + NT_STATUS_HAVE_NO_MEMORY(b->blobs[b->num_blobs].tag); + + if (data.data) { + b->blobs[b->num_blobs].data = data_blob_talloc(b->blobs, + data.data, + data.length); + NT_STATUS_HAVE_NO_MEMORY(b->blobs[b->num_blobs].data.data); + } else { + b->blobs[b->num_blobs].data = data_blob(NULL, 0); + } + + b->num_blobs += 1; + + return NT_STATUS_OK; +} + +/* + send a create request +*/ +struct smb2_request *smb2_create_send(struct smb2_tree *tree, struct smb2_create *io) +{ + struct smb2_request *req; + NTSTATUS status; + DATA_BLOB blob; + struct smb2_create_blobs blobs; + int i; + + ZERO_STRUCT(blobs); + + req = smb2_request_init_tree(tree, SMB2_OP_CREATE, 0x38, true, 0); + if (req == NULL) return NULL; + + SCVAL(req->out.body, 0x02, io->in.security_flags); + SCVAL(req->out.body, 0x03, io->in.oplock_level); + SIVAL(req->out.body, 0x04, io->in.impersonation_level); + SBVAL(req->out.body, 0x08, io->in.create_flags); + SBVAL(req->out.body, 0x10, io->in.reserved); + SIVAL(req->out.body, 0x18, io->in.desired_access); + SIVAL(req->out.body, 0x1C, io->in.file_attributes); + SIVAL(req->out.body, 0x20, io->in.share_access); + SIVAL(req->out.body, 0x24, io->in.create_disposition); + SIVAL(req->out.body, 0x28, io->in.create_options); + + status = smb2_push_o16s16_string(&req->out, 0x2C, io->in.fname); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(req); + return NULL; + } + + /* now add all the optional blobs */ + if (io->in.eas.num_eas != 0) { + DATA_BLOB b = data_blob_talloc(req, NULL, + ea_list_size_chained(io->in.eas.num_eas, io->in.eas.eas, 4)); + ea_put_list_chained(b.data, io->in.eas.num_eas, io->in.eas.eas, 4); + status = smb2_create_blob_add(req, &blobs, + SMB2_CREATE_TAG_EXTA, b); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(req); + return NULL; + } + data_blob_free(&b); + } + + /* an empty MxAc tag seems to be used to ask the server to + return the maximum access mask allowed on the file */ + if (io->in.query_maximal_access) { + /* TODO: MS-SMB2 2.2.13.2.5 says this can contain a timestamp? What to do + with that if it doesn't match? */ + status = smb2_create_blob_add(req, &blobs, + SMB2_CREATE_TAG_MXAC, data_blob(NULL, 0)); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(req); + return NULL; + } + } + + if (io->in.alloc_size != 0) { + uint8_t data[8]; + SBVAL(data, 0, io->in.alloc_size); + status = smb2_create_blob_add(req, &blobs, + SMB2_CREATE_TAG_ALSI, data_blob_const(data, 8)); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(req); + return NULL; + } + } + + if (io->in.durable_open) { + status = smb2_create_blob_add(req, &blobs, + SMB2_CREATE_TAG_DHNQ, data_blob_talloc_zero(req, 16)); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(req); + return NULL; + } + } + + if (io->in.durable_handle) { + uint8_t data[16]; + smb2_push_handle(data, io->in.durable_handle); + status = smb2_create_blob_add(req, &blobs, + SMB2_CREATE_TAG_DHNC, data_blob_const(data, 16)); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(req); + return NULL; + } + } + + if (io->in.timewarp) { + uint8_t data[8]; + SBVAL(data, 0, io->in.timewarp); + status = smb2_create_blob_add(req, &blobs, + SMB2_CREATE_TAG_TWRP, data_blob_const(data, 8)); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(req); + return NULL; + } + } + + if (io->in.sec_desc) { + enum ndr_err_code ndr_err; + DATA_BLOB sd_blob; + ndr_err = ndr_push_struct_blob(&sd_blob, req, NULL, + io->in.sec_desc, + (ndr_push_flags_fn_t)ndr_push_security_descriptor); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + talloc_free(req); + return NULL; + } + status = smb2_create_blob_add(req, &blobs, + SMB2_CREATE_TAG_SECD, sd_blob); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(req); + return NULL; + } + } + + if (io->in.query_on_disk_id) { + status = smb2_create_blob_add(req, &blobs, + SMB2_CREATE_TAG_QFID, data_blob(NULL, 0)); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(req); + return NULL; + } + } + + /* and any custom blobs */ + for (i=0;i<io->in.blobs.num_blobs;i++) { + status = smb2_create_blob_add(req, &blobs, + io->in.blobs.blobs[i].tag, + io->in.blobs.blobs[i].data); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(req); + return NULL; + } + } + + + status = smb2_create_blob_push(req, &blob, blobs); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(req); + return NULL; + } + + status = smb2_push_o32s32_blob(&req->out, 0x30, blob); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(req); + return NULL; + } + + data_blob_free(&blob); + + smb2_transport_send(req); + + return req; +} + + +/* + recv a create reply +*/ +NTSTATUS smb2_create_recv(struct smb2_request *req, TALLOC_CTX *mem_ctx, struct smb2_create *io) +{ + NTSTATUS status; + DATA_BLOB blob; + int i; + + if (!smb2_request_receive(req) || + !smb2_request_is_ok(req)) { + return smb2_request_destroy(req); + } + + SMB2_CHECK_PACKET_RECV(req, 0x58, true); + ZERO_STRUCT(io->out); + io->out.oplock_level = CVAL(req->in.body, 0x02); + io->out.reserved = CVAL(req->in.body, 0x03); + io->out.create_action = IVAL(req->in.body, 0x04); + io->out.create_time = smbcli_pull_nttime(req->in.body, 0x08); + io->out.access_time = smbcli_pull_nttime(req->in.body, 0x10); + io->out.write_time = smbcli_pull_nttime(req->in.body, 0x18); + io->out.change_time = smbcli_pull_nttime(req->in.body, 0x20); + io->out.alloc_size = BVAL(req->in.body, 0x28); + io->out.size = BVAL(req->in.body, 0x30); + io->out.file_attr = IVAL(req->in.body, 0x38); + io->out.reserved2 = IVAL(req->in.body, 0x3C); + smb2_pull_handle(req->in.body+0x40, &io->out.file.handle); + status = smb2_pull_o32s32_blob(&req->in, mem_ctx, req->in.body+0x50, &blob); + if (!NT_STATUS_IS_OK(status)) { + smb2_request_destroy(req); + return status; + } + + status = smb2_create_blob_parse(mem_ctx, blob, &io->out.blobs); + if (!NT_STATUS_IS_OK(status)) { + smb2_request_destroy(req); + return status; + } + + /* pull out the parsed blobs */ + for (i=0;i<io->out.blobs.num_blobs;i++) { + if (strcmp(io->out.blobs.blobs[i].tag, SMB2_CREATE_TAG_MXAC) == 0) { + /* TODO: this also contains a status field in + first 4 bytes */ + if (io->out.blobs.blobs[i].data.length != 8) { + smb2_request_destroy(req); + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + io->out.maximal_access = IVAL(io->out.blobs.blobs[i].data.data, 4); + } + if (strcmp(io->out.blobs.blobs[i].tag, SMB2_CREATE_TAG_QFID) == 0) { + if (io->out.blobs.blobs[i].data.length != 32) { + smb2_request_destroy(req); + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + memcpy(io->out.on_disk_id, io->out.blobs.blobs[i].data.data, 32); + } + } + + data_blob_free(&blob); + + return smb2_request_destroy(req); +} + +/* + sync create request +*/ +NTSTATUS smb2_create(struct smb2_tree *tree, TALLOC_CTX *mem_ctx, struct smb2_create *io) +{ + struct smb2_request *req = smb2_create_send(tree, io); + return smb2_create_recv(req, mem_ctx, io); +} diff --git a/source4/libcli/smb2/find.c b/source4/libcli/smb2/find.c new file mode 100644 index 0000000000..8ebfd81bcd --- /dev/null +++ b/source4/libcli/smb2/find.c @@ -0,0 +1,180 @@ +/* + Unix SMB/CIFS implementation. + + SMB2 client find calls + + Copyright (C) Andrew Tridgell 2005 + + 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 "libcli/raw/libcliraw.h" +#include "libcli/raw/raw_proto.h" +#include "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" + +/* + send a find request +*/ +struct smb2_request *smb2_find_send(struct smb2_tree *tree, struct smb2_find *io) +{ + struct smb2_request *req; + NTSTATUS status; + + req = smb2_request_init_tree(tree, SMB2_OP_FIND, 0x20, true, 0); + if (req == NULL) return NULL; + + SCVAL(req->out.body, 0x02, io->in.level); + SCVAL(req->out.body, 0x03, io->in.continue_flags); + SIVAL(req->out.body, 0x04, io->in.file_index); + smb2_push_handle(req->out.body+0x08, &io->in.file.handle); + + status = smb2_push_o16s16_string(&req->out, 0x18, io->in.pattern); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(req); + return NULL; + } + + SIVAL(req->out.body, 0x1C, io->in.max_response_size); + + smb2_transport_send(req); + + return req; +} + + +/* + recv a find reply +*/ +NTSTATUS smb2_find_recv(struct smb2_request *req, TALLOC_CTX *mem_ctx, + struct smb2_find *io) +{ + NTSTATUS status; + + if (!smb2_request_receive(req) || + smb2_request_is_error(req)) { + return smb2_request_destroy(req); + } + + SMB2_CHECK_PACKET_RECV(req, 0x08, true); + + status = smb2_pull_o16s32_blob(&req->in, mem_ctx, + req->in.body+0x02, &io->out.blob); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + return smb2_request_destroy(req); +} + +/* + sync find request +*/ +NTSTATUS smb2_find(struct smb2_tree *tree, TALLOC_CTX *mem_ctx, + struct smb2_find *io) +{ + struct smb2_request *req = smb2_find_send(tree, io); + return smb2_find_recv(req, mem_ctx, io); +} + + +/* + a varient of smb2_find_recv that parses the resulting blob into + smb_search_data structures +*/ +NTSTATUS smb2_find_level_recv(struct smb2_request *req, TALLOC_CTX *mem_ctx, + uint8_t level, uint_t *count, + union smb_search_data **io) +{ + struct smb2_find f; + NTSTATUS status; + DATA_BLOB b; + enum smb_search_data_level smb_level; + uint_t next_ofs=0; + + switch (level) { + case SMB2_FIND_DIRECTORY_INFO: + smb_level = RAW_SEARCH_DATA_DIRECTORY_INFO; + break; + case SMB2_FIND_FULL_DIRECTORY_INFO: + smb_level = RAW_SEARCH_DATA_FULL_DIRECTORY_INFO; + break; + case SMB2_FIND_BOTH_DIRECTORY_INFO: + smb_level = RAW_SEARCH_DATA_BOTH_DIRECTORY_INFO; + break; + case SMB2_FIND_NAME_INFO: + smb_level = RAW_SEARCH_DATA_NAME_INFO; + break; + case SMB2_FIND_ID_FULL_DIRECTORY_INFO: + smb_level = RAW_SEARCH_DATA_ID_FULL_DIRECTORY_INFO; + break; + case SMB2_FIND_ID_BOTH_DIRECTORY_INFO: + smb_level = RAW_SEARCH_DATA_ID_BOTH_DIRECTORY_INFO; + break; + default: + return NT_STATUS_INVALID_INFO_CLASS; + } + + status = smb2_find_recv(req, mem_ctx, &f); + NT_STATUS_NOT_OK_RETURN(status); + + b = f.out.blob; + *io = NULL; + *count = 0; + + do { + union smb_search_data *io2; + + io2 = talloc_realloc(mem_ctx, *io, union smb_search_data, (*count)+1); + if (io2 == NULL) { + data_blob_free(&f.out.blob); + talloc_free(*io); + return NT_STATUS_NO_MEMORY; + } + *io = io2; + + status = smb_raw_search_common(*io, smb_level, &b, (*io) + (*count), + &next_ofs, STR_UNICODE); + + if (NT_STATUS_IS_OK(status) && + next_ofs >= b.length) { + data_blob_free(&f.out.blob); + talloc_free(*io); + return NT_STATUS_INFO_LENGTH_MISMATCH; + } + + (*count)++; + + b = data_blob_const(b.data+next_ofs, b.length - next_ofs); + } while (NT_STATUS_IS_OK(status) && next_ofs != 0); + + data_blob_free(&f.out.blob); + + return NT_STATUS_OK; +} + +/* + a varient of smb2_find that parses the resulting blob into + smb_search_data structures +*/ +NTSTATUS smb2_find_level(struct smb2_tree *tree, TALLOC_CTX *mem_ctx, + struct smb2_find *f, + uint_t *count, union smb_search_data **io) +{ + struct smb2_request *req; + + req = smb2_find_send(tree, f); + return smb2_find_level_recv(req, mem_ctx, f->in.level, count, io); +} diff --git a/source4/libcli/smb2/flush.c b/source4/libcli/smb2/flush.c new file mode 100644 index 0000000000..577d1ba1ba --- /dev/null +++ b/source4/libcli/smb2/flush.c @@ -0,0 +1,70 @@ +/* + Unix SMB/CIFS implementation. + + SMB2 client flush handling + + Copyright (C) Andrew Tridgell 2005 + + 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 "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" + +/* + send a flush request +*/ +struct smb2_request *smb2_flush_send(struct smb2_tree *tree, struct smb2_flush *io) +{ + struct smb2_request *req; + + req = smb2_request_init_tree(tree, SMB2_OP_FLUSH, 0x18, false, 0); + if (req == NULL) return NULL; + + SSVAL(req->out.body, 0x02, io->in.reserved1); + SIVAL(req->out.body, 0x04, io->in.reserved2); + smb2_push_handle(req->out.body+0x08, &io->in.file.handle); + + smb2_transport_send(req); + + return req; +} + + +/* + recv a flush reply +*/ +NTSTATUS smb2_flush_recv(struct smb2_request *req, struct smb2_flush *io) +{ + if (!smb2_request_receive(req) || + !smb2_request_is_ok(req)) { + return smb2_request_destroy(req); + } + + SMB2_CHECK_PACKET_RECV(req, 0x04, false); + + io->out.reserved = SVAL(req->in.body, 0x02); + + return smb2_request_destroy(req); +} + +/* + sync flush request +*/ +NTSTATUS smb2_flush(struct smb2_tree *tree, struct smb2_flush *io) +{ + struct smb2_request *req = smb2_flush_send(tree, io); + return smb2_flush_recv(req, io); +} diff --git a/source4/libcli/smb2/getinfo.c b/source4/libcli/smb2/getinfo.c new file mode 100644 index 0000000000..b462bab1de --- /dev/null +++ b/source4/libcli/smb2/getinfo.c @@ -0,0 +1,219 @@ +/* + Unix SMB/CIFS implementation. + + SMB2 client getinfo calls + + Copyright (C) Andrew Tridgell 2005 + + 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 "libcli/raw/libcliraw.h" +#include "libcli/raw/raw_proto.h" +#include "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" + +/* + send a getinfo request +*/ +struct smb2_request *smb2_getinfo_send(struct smb2_tree *tree, struct smb2_getinfo *io) +{ + struct smb2_request *req; + NTSTATUS status; + + req = smb2_request_init_tree(tree, SMB2_OP_GETINFO, 0x28, true, + io->in.blob.length); + if (req == NULL) return NULL; + + SCVAL(req->out.body, 0x02, io->in.info_type); + SCVAL(req->out.body, 0x03, io->in.info_class); + SIVAL(req->out.body, 0x04, io->in.output_buffer_length); + SIVAL(req->out.body, 0x0C, io->in.reserved); + SIVAL(req->out.body, 0x08, io->in.input_buffer_length); + SIVAL(req->out.body, 0x10, io->in.additional_information); + SIVAL(req->out.body, 0x14, io->in.getinfo_flags); + smb2_push_handle(req->out.body+0x18, &io->in.file.handle); + + /* this blob is used for quota queries */ + status = smb2_push_o32s32_blob(&req->out, 0x08, io->in.blob); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(req); + return NULL; + } + smb2_transport_send(req); + + return req; +} + + +/* + recv a getinfo reply +*/ +NTSTATUS smb2_getinfo_recv(struct smb2_request *req, TALLOC_CTX *mem_ctx, + struct smb2_getinfo *io) +{ + NTSTATUS status; + + if (!smb2_request_receive(req) || + smb2_request_is_error(req)) { + return smb2_request_destroy(req); + } + + SMB2_CHECK_PACKET_RECV(req, 0x08, true); + + status = smb2_pull_o16s16_blob(&req->in, mem_ctx, req->in.body+0x02, &io->out.blob); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + return smb2_request_destroy(req); +} + +/* + sync getinfo request +*/ +NTSTATUS smb2_getinfo(struct smb2_tree *tree, TALLOC_CTX *mem_ctx, + struct smb2_getinfo *io) +{ + struct smb2_request *req = smb2_getinfo_send(tree, io); + return smb2_getinfo_recv(req, mem_ctx, io); +} + + +/* + map a generic info level to a SMB2 info level +*/ +uint16_t smb2_getinfo_map_level(uint16_t level, uint8_t class) +{ + if (class == SMB2_GETINFO_FILE && + level == RAW_FILEINFO_SEC_DESC) { + return SMB2_GETINFO_SECURITY; + } + if ((level & 0xFF) == class) { + return level; + } else if (level > 1000) { + return ((level-1000)<<8) | class; + } + DEBUG(0,("Unable to map SMB2 info level 0x%04x of class %d\n", level, class)); + return 0; +} + +/* + level specific getinfo call - async send +*/ +struct smb2_request *smb2_getinfo_file_send(struct smb2_tree *tree, union smb_fileinfo *io) +{ + struct smb2_getinfo b; + uint16_t smb2_level = smb2_getinfo_map_level(io->generic.level, SMB2_GETINFO_FILE); + + if (smb2_level == 0) { + return NULL; + } + + ZERO_STRUCT(b); + b.in.info_type = smb2_level & 0xFF; + b.in.info_class = smb2_level >> 8; + b.in.output_buffer_length = 0x10000; + b.in.input_buffer_length = 0; + b.in.file.handle = io->generic.in.file.handle; + + if (io->generic.level == RAW_FILEINFO_SEC_DESC) { + b.in.additional_information = io->query_secdesc.in.secinfo_flags; + } + if (io->generic.level == RAW_FILEINFO_SMB2_ALL_EAS) { + b.in.getinfo_flags = io->all_eas.in.continue_flags; + } + + return smb2_getinfo_send(tree, &b); +} + +/* + recv a getinfo reply and parse the level info +*/ +NTSTATUS smb2_getinfo_file_recv(struct smb2_request *req, TALLOC_CTX *mem_ctx, + union smb_fileinfo *io) +{ + struct smb2_getinfo b; + NTSTATUS status; + + status = smb2_getinfo_recv(req, mem_ctx, &b); + NT_STATUS_NOT_OK_RETURN(status); + + status = smb_raw_fileinfo_passthru_parse(&b.out.blob, mem_ctx, io->generic.level, io); + data_blob_free(&b.out.blob); + + return status; +} + +/* + level specific getinfo call +*/ +NTSTATUS smb2_getinfo_file(struct smb2_tree *tree, TALLOC_CTX *mem_ctx, + union smb_fileinfo *io) +{ + struct smb2_request *req = smb2_getinfo_file_send(tree, io); + return smb2_getinfo_file_recv(req, mem_ctx, io); +} + + +/* + level specific getinfo call - async send +*/ +struct smb2_request *smb2_getinfo_fs_send(struct smb2_tree *tree, union smb_fsinfo *io) +{ + struct smb2_getinfo b; + uint16_t smb2_level = smb2_getinfo_map_level(io->generic.level, SMB2_GETINFO_FS); + + if (smb2_level == 0) { + return NULL; + } + + ZERO_STRUCT(b); + b.in.output_buffer_length = 0x10000; + b.in.file.handle = io->generic.handle; + b.in.info_type = smb2_level & 0xFF; + b.in.info_class = smb2_level >> 8; + + return smb2_getinfo_send(tree, &b); +} + +/* + recv a getinfo reply and parse the level info +*/ +NTSTATUS smb2_getinfo_fs_recv(struct smb2_request *req, TALLOC_CTX *mem_ctx, + union smb_fsinfo *io) +{ + struct smb2_getinfo b; + NTSTATUS status; + + status = smb2_getinfo_recv(req, mem_ctx, &b); + NT_STATUS_NOT_OK_RETURN(status); + + status = smb_raw_fsinfo_passthru_parse(b.out.blob, mem_ctx, io->generic.level, io); + data_blob_free(&b.out.blob); + + return status; +} + +/* + level specific getinfo call +*/ +NTSTATUS smb2_getinfo_fs(struct smb2_tree *tree, TALLOC_CTX *mem_ctx, + union smb_fsinfo *io) +{ + struct smb2_request *req = smb2_getinfo_fs_send(tree, io); + return smb2_getinfo_fs_recv(req, mem_ctx, io); +} + diff --git a/source4/libcli/smb2/ioctl.c b/source4/libcli/smb2/ioctl.c new file mode 100644 index 0000000000..d81bca517f --- /dev/null +++ b/source4/libcli/smb2/ioctl.c @@ -0,0 +1,109 @@ +/* + Unix SMB/CIFS implementation. + + SMB2 client ioctl call + + Copyright (C) Andrew Tridgell 2005 + + 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 "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" + +/* + send a ioctl request +*/ +struct smb2_request *smb2_ioctl_send(struct smb2_tree *tree, struct smb2_ioctl *io) +{ + NTSTATUS status; + struct smb2_request *req; + + req = smb2_request_init_tree(tree, SMB2_OP_IOCTL, 0x38, true, + io->in.in.length+io->in.out.length); + if (req == NULL) return NULL; + + SSVAL(req->out.body, 0x02, 0); /* pad */ + SIVAL(req->out.body, 0x04, io->in.function); + smb2_push_handle(req->out.body+0x08, &io->in.file.handle); + + status = smb2_push_o32s32_blob(&req->out, 0x18, io->in.out); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(req); + return NULL; + } + + SIVAL(req->out.body, 0x20, io->in.unknown2); + + status = smb2_push_o32s32_blob(&req->out, 0x24, io->in.in); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(req); + return NULL; + } + + SIVAL(req->out.body, 0x2C, io->in.max_response_size); + SBVAL(req->out.body, 0x30, io->in.flags); + + smb2_transport_send(req); + + return req; +} + + +/* + recv a ioctl reply +*/ +NTSTATUS smb2_ioctl_recv(struct smb2_request *req, + TALLOC_CTX *mem_ctx, struct smb2_ioctl *io) +{ + NTSTATUS status; + + if (!smb2_request_receive(req) || + smb2_request_is_error(req)) { + return smb2_request_destroy(req); + } + + SMB2_CHECK_PACKET_RECV(req, 0x30, true); + + io->out._pad = SVAL(req->in.body, 0x02); + io->out.function = IVAL(req->in.body, 0x04); + smb2_pull_handle(req->in.body+0x08, &io->out.file.handle); + + status = smb2_pull_o32s32_blob(&req->in, mem_ctx, req->in.body+0x18, &io->out.in); + if (!NT_STATUS_IS_OK(status)) { + smb2_request_destroy(req); + return status; + } + + status = smb2_pull_o32s32_blob(&req->in, mem_ctx, req->in.body+0x20, &io->out.out); + if (!NT_STATUS_IS_OK(status)) { + smb2_request_destroy(req); + return status; + } + + io->out.unknown2 = IVAL(req->in.body, 0x28); + io->out.unknown3 = IVAL(req->in.body, 0x2C); + + return smb2_request_destroy(req); +} + +/* + sync ioctl request +*/ +NTSTATUS smb2_ioctl(struct smb2_tree *tree, TALLOC_CTX *mem_ctx, struct smb2_ioctl *io) +{ + struct smb2_request *req = smb2_ioctl_send(tree, io); + return smb2_ioctl_recv(req, mem_ctx, io); +} diff --git a/source4/libcli/smb2/keepalive.c b/source4/libcli/smb2/keepalive.c new file mode 100644 index 0000000000..402b063e81 --- /dev/null +++ b/source4/libcli/smb2/keepalive.c @@ -0,0 +1,65 @@ +/* + Unix SMB/CIFS implementation. + + SMB2 client keepalive handling + + Copyright (C) Andrew Tridgell 2005 + + 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 "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" + +/* + send a keepalive request +*/ +struct smb2_request *smb2_keepalive_send(struct smb2_transport *transport) +{ + struct smb2_request *req; + + req = smb2_request_init(transport, SMB2_OP_KEEPALIVE, 0x04, false, 0); + if (req == NULL) return NULL; + + SSVAL(req->out.body, 0x02, 0); + + smb2_transport_send(req); + + return req; +} + + +/* + recv a keepalive reply +*/ +NTSTATUS smb2_keepalive_recv(struct smb2_request *req) +{ + if (!smb2_request_receive(req) || + !smb2_request_is_ok(req)) { + return smb2_request_destroy(req); + } + + SMB2_CHECK_PACKET_RECV(req, 0x04, false); + return smb2_request_destroy(req); +} + +/* + sync keepalive request +*/ +NTSTATUS smb2_keepalive(struct smb2_transport *transport) +{ + struct smb2_request *req = smb2_keepalive_send(transport); + return smb2_keepalive_recv(req); +} diff --git a/source4/libcli/smb2/lock.c b/source4/libcli/smb2/lock.c new file mode 100644 index 0000000000..62c6e5dba7 --- /dev/null +++ b/source4/libcli/smb2/lock.c @@ -0,0 +1,82 @@ +/* + Unix SMB/CIFS implementation. + + SMB2 client lock handling + + Copyright (C) Stefan Metzmacher 2006 + + 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 "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" + +/* + send a lock request +*/ +struct smb2_request *smb2_lock_send(struct smb2_tree *tree, struct smb2_lock *io) +{ + struct smb2_request *req; + int i; + + req = smb2_request_init_tree(tree, SMB2_OP_LOCK, + 24 + io->in.lock_count*24, false, 0); + if (req == NULL) return NULL; + + /* this is quite bizarre - the spec says we must lie about the length! */ + SSVAL(req->out.body, 0, 0x30); + + SSVAL(req->out.body, 0x02, io->in.lock_count); + SIVAL(req->out.body, 0x04, io->in.reserved); + smb2_push_handle(req->out.body+0x08, &io->in.file.handle); + + for (i=0;i<io->in.lock_count;i++) { + SBVAL(req->out.body, 0x18 + i*24, io->in.locks[i].offset); + SBVAL(req->out.body, 0x20 + i*24, io->in.locks[i].length); + SIVAL(req->out.body, 0x28 + i*24, io->in.locks[i].flags); + SIVAL(req->out.body, 0x2C + i*24, io->in.locks[i].reserved); + } + + smb2_transport_send(req); + + return req; +} + + +/* + recv a lock reply +*/ +NTSTATUS smb2_lock_recv(struct smb2_request *req, struct smb2_lock *io) +{ + if (!smb2_request_receive(req) || + smb2_request_is_error(req)) { + return smb2_request_destroy(req); + } + + SMB2_CHECK_PACKET_RECV(req, 0x04, false); + + io->out.reserved = SVAL(req->in.body, 0x02); + + return smb2_request_destroy(req); +} + +/* + sync lock request +*/ +NTSTATUS smb2_lock(struct smb2_tree *tree, struct smb2_lock *io) +{ + struct smb2_request *req = smb2_lock_send(tree, io); + return smb2_lock_recv(req, io); +} diff --git a/source4/libcli/smb2/logoff.c b/source4/libcli/smb2/logoff.c new file mode 100644 index 0000000000..e3f83f27d8 --- /dev/null +++ b/source4/libcli/smb2/logoff.c @@ -0,0 +1,69 @@ +/* + Unix SMB/CIFS implementation. + + SMB2 client logoff handling + + Copyright (C) Andrew Tridgell 2005 + + 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 "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" + +/* + send a logoff request +*/ +struct smb2_request *smb2_logoff_send(struct smb2_session *session) +{ + struct smb2_request *req; + + req = smb2_request_init(session->transport, SMB2_OP_LOGOFF, 0x04, false, 0); + if (req == NULL) return NULL; + + req->session = session; + + SBVAL(req->out.hdr, SMB2_HDR_SESSION_ID, session->uid); + + SSVAL(req->out.body, 0x02, 0); + + smb2_transport_send(req); + + return req; +} + + +/* + recv a logoff reply +*/ +NTSTATUS smb2_logoff_recv(struct smb2_request *req) +{ + if (!smb2_request_receive(req) || + !smb2_request_is_ok(req)) { + return smb2_request_destroy(req); + } + + SMB2_CHECK_PACKET_RECV(req, 0x04, false); + return smb2_request_destroy(req); +} + +/* + sync logoff request +*/ +NTSTATUS smb2_logoff(struct smb2_session *session) +{ + struct smb2_request *req = smb2_logoff_send(session); + return smb2_logoff_recv(req); +} diff --git a/source4/libcli/smb2/negprot.c b/source4/libcli/smb2/negprot.c new file mode 100644 index 0000000000..c1f0cf0b24 --- /dev/null +++ b/source4/libcli/smb2/negprot.c @@ -0,0 +1,113 @@ +/* + Unix SMB/CIFS implementation. + + SMB2 client negprot handling + + Copyright (C) Andrew Tridgell 2005 + + 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 "libcli/raw/libcliraw.h" +#include "libcli/raw/raw_proto.h" +#include "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" +#include "librpc/ndr/libndr.h" + +/* + send a negprot request +*/ +struct smb2_request *smb2_negprot_send(struct smb2_transport *transport, + struct smb2_negprot *io) +{ + struct smb2_request *req; + uint16_t size = 0x24 + io->in.dialect_count*2; + enum ndr_err_code ndr_err; + int i; + + req = smb2_request_init(transport, SMB2_OP_NEGPROT, size, false, 0); + if (req == NULL) return NULL; + + + SSVAL(req->out.body, 0x00, 0x24); + SSVAL(req->out.body, 0x02, io->in.dialect_count); + SSVAL(req->out.body, 0x04, io->in.security_mode); + SSVAL(req->out.body, 0x06, io->in.reserved); + SIVAL(req->out.body, 0x08, io->in.capabilities); + ndr_err = smbcli_push_guid(req->out.body, 0x0C, &io->in.client_guid); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + talloc_free(req); + return NULL; + } + smbcli_push_nttime(req->out.body, 0x1C, io->in.start_time); + for (i=0;i<io->in.dialect_count;i++) { + SSVAL(req->out.body, 0x24 + i*2, io->in.dialects[i]); + } + + smb2_transport_send(req); + + return req; +} + +/* + recv a negprot reply +*/ +NTSTATUS smb2_negprot_recv(struct smb2_request *req, TALLOC_CTX *mem_ctx, + struct smb2_negprot *io) +{ + NTSTATUS status; + enum ndr_err_code ndr_err; + + if (!smb2_request_receive(req) || + smb2_request_is_error(req)) { + return smb2_request_destroy(req); + } + + SMB2_CHECK_PACKET_RECV(req, 0x40, true); + + io->out.security_mode = SVAL(req->in.body, 0x02); + io->out.dialect_revision = SVAL(req->in.body, 0x04); + io->out.reserved = SVAL(req->in.body, 0x06); + ndr_err = smbcli_pull_guid(req->in.body, 0x08, &io->in.client_guid); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + smb2_request_destroy(req); + return NT_STATUS_INTERNAL_ERROR; + } + io->out.capabilities = IVAL(req->in.body, 0x18); + io->out.max_transact_size = IVAL(req->in.body, 0x1C); + io->out.max_read_size = IVAL(req->in.body, 0x20); + io->out.max_write_size = IVAL(req->in.body, 0x24); + io->out.system_time = smbcli_pull_nttime(req->in.body, 0x28); + io->out.server_start_time = smbcli_pull_nttime(req->in.body, 0x30); + io->out.reserved2 = IVAL(req->in.body, 0x3C); + + status = smb2_pull_o16s16_blob(&req->in, mem_ctx, req->in.body+0x38, &io->out.secblob); + if (!NT_STATUS_IS_OK(status)) { + smb2_request_destroy(req); + return status; + } + + return smb2_request_destroy(req); +} + +/* + sync negprot request +*/ +NTSTATUS smb2_negprot(struct smb2_transport *transport, + TALLOC_CTX *mem_ctx, struct smb2_negprot *io) +{ + struct smb2_request *req = smb2_negprot_send(transport, io); + return smb2_negprot_recv(req, mem_ctx, io); +} diff --git a/source4/libcli/smb2/notify.c b/source4/libcli/smb2/notify.c new file mode 100644 index 0000000000..ef7341cae8 --- /dev/null +++ b/source4/libcli/smb2/notify.c @@ -0,0 +1,114 @@ +/* + Unix SMB/CIFS implementation. + + SMB2 client notify calls + + Copyright (C) Stefan Metzmacher 2006 + + 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 "libcli/raw/libcliraw.h" +#include "libcli/raw/raw_proto.h" +#include "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" + +/* + send a notify request +*/ +struct smb2_request *smb2_notify_send(struct smb2_tree *tree, struct smb2_notify *io) +{ + struct smb2_request *req; + uint32_t old_timeout; + + req = smb2_request_init_tree(tree, SMB2_OP_NOTIFY, 0x20, false, 0); + if (req == NULL) return NULL; + + SSVAL(req->out.hdr, SMB2_HDR_CREDIT, 0x0030); + + SSVAL(req->out.body, 0x02, io->in.recursive); + SIVAL(req->out.body, 0x04, io->in.buffer_size); + smb2_push_handle(req->out.body+0x08, &io->in.file.handle); + SIVAL(req->out.body, 0x18, io->in.completion_filter); + SIVAL(req->out.body, 0x1C, io->in.unknown); + + old_timeout = req->transport->options.request_timeout; + req->transport->options.request_timeout = 0; + smb2_transport_send(req); + req->transport->options.request_timeout = old_timeout; + + return req; +} + + +/* + recv a notify reply +*/ +NTSTATUS smb2_notify_recv(struct smb2_request *req, TALLOC_CTX *mem_ctx, + struct smb2_notify *io) +{ + NTSTATUS status; + DATA_BLOB blob; + uint32_t ofs, i; + + if (!smb2_request_receive(req) || + !smb2_request_is_ok(req)) { + return smb2_request_destroy(req); + } + + SMB2_CHECK_PACKET_RECV(req, 0x08, true); + + status = smb2_pull_o16s32_blob(&req->in, mem_ctx, req->in.body+0x02, &blob); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + io->out.changes = NULL; + io->out.num_changes = 0; + + /* count them */ + for (ofs=0; blob.length - ofs > 12; ) { + uint32_t next = IVAL(blob.data, ofs); + io->out.num_changes++; + if (next == 0 || (ofs + next) >= blob.length) break; + ofs += next; + } + + /* allocate array */ + io->out.changes = talloc_array(mem_ctx, struct notify_changes, io->out.num_changes); + if (!io->out.changes) { + return NT_STATUS_NO_MEMORY; + } + + for (i=ofs=0; i<io->out.num_changes; i++) { + io->out.changes[i].action = IVAL(blob.data, ofs+4); + smbcli_blob_pull_string(NULL, mem_ctx, &blob, + &io->out.changes[i].name, + ofs+8, ofs+12, STR_UNICODE); + ofs += IVAL(blob.data, ofs); + } + + return smb2_request_destroy(req); +} + +/* + sync notify request +*/ +NTSTATUS smb2_notify(struct smb2_tree *tree, TALLOC_CTX *mem_ctx, + struct smb2_notify *io) +{ + struct smb2_request *req = smb2_notify_send(tree, io); + return smb2_notify_recv(req, mem_ctx, io); +} diff --git a/source4/libcli/smb2/read.c b/source4/libcli/smb2/read.c new file mode 100644 index 0000000000..9d40e32a4d --- /dev/null +++ b/source4/libcli/smb2/read.c @@ -0,0 +1,87 @@ +/* + Unix SMB/CIFS implementation. + + SMB2 client read call + + Copyright (C) Andrew Tridgell 2005 + + 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 "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" + +/* + send a read request +*/ +struct smb2_request *smb2_read_send(struct smb2_tree *tree, struct smb2_read *io) +{ + struct smb2_request *req; + + req = smb2_request_init_tree(tree, SMB2_OP_READ, 0x30, true, 0); + if (req == NULL) return NULL; + + SCVAL(req->out.body, 0x02, 0); /* pad */ + SCVAL(req->out.body, 0x03, 0); /* reserved */ + SIVAL(req->out.body, 0x04, io->in.length); + SBVAL(req->out.body, 0x08, io->in.offset); + smb2_push_handle(req->out.body+0x10, &io->in.file.handle); + SIVAL(req->out.body, 0x20, io->in.min_count); + SIVAL(req->out.body, 0x24, io->in.channel); + SIVAL(req->out.body, 0x28, io->in.remaining); + SSVAL(req->out.body, 0x2C, io->in.channel_offset); + SSVAL(req->out.body, 0x2E, io->in.channel_length); + + smb2_transport_send(req); + + return req; +} + + +/* + recv a read reply +*/ +NTSTATUS smb2_read_recv(struct smb2_request *req, + TALLOC_CTX *mem_ctx, struct smb2_read *io) +{ + NTSTATUS status; + + if (!smb2_request_receive(req) || + !smb2_request_is_ok(req)) { + return smb2_request_destroy(req); + } + + SMB2_CHECK_PACKET_RECV(req, 0x10, true); + + status = smb2_pull_o16s32_blob(&req->in, mem_ctx, req->in.body+0x02, &io->out.data); + if (!NT_STATUS_IS_OK(status)) { + smb2_request_destroy(req); + return status; + } + + io->out.remaining = IVAL(req->in.body, 0x08); + io->out.reserved = IVAL(req->in.body, 0x0C); + + return smb2_request_destroy(req); +} + +/* + sync read request +*/ +NTSTATUS smb2_read(struct smb2_tree *tree, TALLOC_CTX *mem_ctx, struct smb2_read *io) +{ + struct smb2_request *req = smb2_read_send(tree, io); + return smb2_read_recv(req, mem_ctx, io); +} diff --git a/source4/libcli/smb2/request.c b/source4/libcli/smb2/request.c new file mode 100644 index 0000000000..64d427f889 --- /dev/null +++ b/source4/libcli/smb2/request.c @@ -0,0 +1,714 @@ +/* + Unix SMB/CIFS implementation. + + SMB2 client request handling + + Copyright (C) Andrew Tridgell 2005 + Copyright (C) Stefan Metzmacher 2005 + + 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 "libcli/raw/libcliraw.h" +#include "libcli/smb2/smb2.h" +#include "lib/util/dlinklist.h" +#include "lib/events/events.h" +#include "libcli/smb2/smb2_calls.h" +#include "param/param.h" + +/* fill in the bufinfo */ +void smb2_setup_bufinfo(struct smb2_request *req) +{ + req->in.bufinfo.mem_ctx = req; + req->in.bufinfo.flags = BUFINFO_FLAG_UNICODE | BUFINFO_FLAG_SMB2; + req->in.bufinfo.align_base = req->in.buffer; + if (req->in.dynamic) { + req->in.bufinfo.data = req->in.dynamic; + req->in.bufinfo.data_size = req->in.body_size - req->in.body_fixed; + } else { + req->in.bufinfo.data = NULL; + req->in.bufinfo.data_size = 0; + } +} + + +/* destroy a request structure */ +static int smb2_request_destructor(struct smb2_request *req) +{ + if (req->transport) { + /* remove it from the list of pending requests (a null op if + its not in the list) */ + DLIST_REMOVE(req->transport->pending_recv, req); + } + return 0; +} + +/* + initialise a smb2 request +*/ +struct smb2_request *smb2_request_init(struct smb2_transport *transport, uint16_t opcode, + uint16_t body_fixed_size, bool body_dynamic_present, + uint32_t body_dynamic_size) +{ + struct smb2_request *req; + uint64_t seqnum; + + if (body_dynamic_present) { + if (body_dynamic_size == 0) { + body_dynamic_size = 1; + } + } else { + body_dynamic_size = 0; + } + + req = talloc(transport, struct smb2_request); + if (req == NULL) return NULL; + + seqnum = transport->seqnum++; + if (seqnum == UINT64_MAX) { + seqnum = transport->seqnum++; + } + + req->state = SMB2_REQUEST_INIT; + req->transport = transport; + req->session = NULL; + req->tree = NULL; + req->seqnum = seqnum; + req->status = NT_STATUS_OK; + req->async.fn = NULL; + req->next = req->prev = NULL; + + ZERO_STRUCT(req->cancel); + ZERO_STRUCT(req->in); + + req->out.size = SMB2_HDR_BODY+NBT_HDR_SIZE+body_fixed_size; + + req->out.allocated = req->out.size + body_dynamic_size; + req->out.buffer = talloc_array(req, uint8_t, req->out.allocated); + if (req->out.buffer == NULL) { + talloc_free(req); + return NULL; + } + + req->out.hdr = req->out.buffer + NBT_HDR_SIZE; + req->out.body = req->out.hdr + SMB2_HDR_BODY; + req->out.body_fixed= body_fixed_size; + req->out.body_size = body_fixed_size; + req->out.dynamic = (body_dynamic_size ? req->out.body + body_fixed_size : NULL); + + SIVAL(req->out.hdr, 0, SMB2_MAGIC); + SSVAL(req->out.hdr, SMB2_HDR_LENGTH, SMB2_HDR_BODY); + SSVAL(req->out.hdr, SMB2_HDR_EPOCH, 0); + SIVAL(req->out.hdr, SMB2_HDR_STATUS, 0); + SSVAL(req->out.hdr, SMB2_HDR_OPCODE, opcode); + SSVAL(req->out.hdr, SMB2_HDR_CREDIT, 0); + SIVAL(req->out.hdr, SMB2_HDR_FLAGS, 0); + SIVAL(req->out.hdr, SMB2_HDR_NEXT_COMMAND, 0); + SBVAL(req->out.hdr, SMB2_HDR_MESSAGE_ID, req->seqnum); + SIVAL(req->out.hdr, SMB2_HDR_PID, 0); + SIVAL(req->out.hdr, SMB2_HDR_TID, 0); + SBVAL(req->out.hdr, SMB2_HDR_SESSION_ID, 0); + memset(req->out.hdr+SMB2_HDR_SIGNATURE, 0, 16); + + /* set the length of the fixed body part and +1 if there's a dynamic part also */ + SSVAL(req->out.body, 0, body_fixed_size + (body_dynamic_size?1:0)); + + /* + * if we have a dynamic part, make sure the first byte + * which is always be part of the packet is initialized + */ + if (body_dynamic_size) { + req->out.size += 1; + SCVAL(req->out.dynamic, 0, 0); + } + + talloc_set_destructor(req, smb2_request_destructor); + + return req; +} + +/* + initialise a smb2 request for tree operations +*/ +struct smb2_request *smb2_request_init_tree(struct smb2_tree *tree, uint16_t opcode, + uint16_t body_fixed_size, bool body_dynamic_present, + uint32_t body_dynamic_size) +{ + struct smb2_request *req = smb2_request_init(tree->session->transport, opcode, + body_fixed_size, body_dynamic_present, + body_dynamic_size); + if (req == NULL) return NULL; + + SBVAL(req->out.hdr, SMB2_HDR_SESSION_ID, tree->session->uid); + SIVAL(req->out.hdr, SMB2_HDR_TID, tree->tid); + req->session = tree->session; + req->tree = tree; + + return req; +} + +/* destroy a request structure and return final status */ +NTSTATUS smb2_request_destroy(struct smb2_request *req) +{ + NTSTATUS status; + + /* this is the error code we give the application for when a + _send() call fails completely */ + if (!req) return NT_STATUS_UNSUCCESSFUL; + + if (req->state == SMB2_REQUEST_ERROR && + NT_STATUS_IS_OK(req->status)) { + status = NT_STATUS_INTERNAL_ERROR; + } else { + status = req->status; + } + + talloc_free(req); + return status; +} + +/* + receive a response to a packet +*/ +bool smb2_request_receive(struct smb2_request *req) +{ + /* req can be NULL when a send has failed. This eliminates lots of NULL + checks in each module */ + if (!req) return false; + + /* keep receiving packets until this one is replied to */ + while (req->state <= SMB2_REQUEST_RECV) { + if (event_loop_once(req->transport->socket->event.ctx) != 0) { + return false; + } + } + + return req->state == SMB2_REQUEST_DONE; +} + +/* Return true if the last packet was in error */ +bool smb2_request_is_error(struct smb2_request *req) +{ + return NT_STATUS_IS_ERR(req->status); +} + +/* Return true if the last packet was OK */ +bool smb2_request_is_ok(struct smb2_request *req) +{ + return NT_STATUS_IS_OK(req->status); +} + +/* + check if a range in the reply body is out of bounds +*/ +bool smb2_oob(struct smb2_request_buffer *buf, const uint8_t *ptr, size_t size) +{ + if (size == 0) { + /* zero bytes is never out of range */ + return false; + } + /* be careful with wraparound! */ + if ((uintptr_t)ptr < (uintptr_t)buf->body || + (uintptr_t)ptr >= (uintptr_t)buf->body + buf->body_size || + size > buf->body_size || + (uintptr_t)ptr + size > (uintptr_t)buf->body + buf->body_size) { + return true; + } + return false; +} + +size_t smb2_padding_size(uint32_t offset, size_t n) +{ + if ((offset & (n-1)) == 0) return 0; + return n - (offset & (n-1)); +} + +static size_t smb2_padding_fix(struct smb2_request_buffer *buf) +{ + if (buf->dynamic == (buf->body + buf->body_fixed)) { + return 1; + } + return 0; +} + +/* + grow a SMB2 buffer by the specified amount +*/ +static NTSTATUS smb2_grow_buffer(struct smb2_request_buffer *buf, size_t increase) +{ + size_t dynamic_ofs; + uint8_t *buffer_ptr; + uint32_t newsize = buf->size + increase; + + /* a packet size should be limited a bit */ + if (newsize >= 0x00FFFFFF) return NT_STATUS_MARSHALL_OVERFLOW; + + if (newsize <= buf->allocated) return NT_STATUS_OK; + + dynamic_ofs = buf->dynamic - buf->buffer; + + buffer_ptr = talloc_realloc(buf, buf->buffer, uint8_t, newsize); + NT_STATUS_HAVE_NO_MEMORY(buffer_ptr); + + buf->buffer = buffer_ptr; + buf->hdr = buf->buffer + NBT_HDR_SIZE; + buf->body = buf->hdr + SMB2_HDR_BODY; + buf->dynamic = buf->buffer + dynamic_ofs; + buf->allocated = newsize; + + return NT_STATUS_OK; +} + +/* + pull a uint16_t ofs/ uint16_t length/blob triple from a data blob + the ptr points to the start of the offset/length pair +*/ +NTSTATUS smb2_pull_o16s16_blob(struct smb2_request_buffer *buf, TALLOC_CTX *mem_ctx, uint8_t *ptr, DATA_BLOB *blob) +{ + uint16_t ofs, size; + if (smb2_oob(buf, ptr, 4)) { + return NT_STATUS_BUFFER_TOO_SMALL; + } + ofs = SVAL(ptr, 0); + size = SVAL(ptr, 2); + if (ofs == 0) { + *blob = data_blob(NULL, 0); + return NT_STATUS_OK; + } + if (smb2_oob(buf, buf->hdr + ofs, size)) { + return NT_STATUS_BUFFER_TOO_SMALL; + } + *blob = data_blob_talloc(mem_ctx, buf->hdr + ofs, size); + NT_STATUS_HAVE_NO_MEMORY(blob->data); + return NT_STATUS_OK; +} + +/* + push a uint16_t ofs/ uint16_t length/blob triple into a data blob + the ofs points to the start of the offset/length pair, and is relative + to the body start +*/ +NTSTATUS smb2_push_o16s16_blob(struct smb2_request_buffer *buf, + uint16_t ofs, DATA_BLOB blob) +{ + NTSTATUS status; + size_t offset; + size_t padding_length; + size_t padding_fix; + uint8_t *ptr = buf->body+ofs; + + if (buf->dynamic == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + /* we have only 16 bit for the size */ + if (blob.length > 0xFFFF) { + return NT_STATUS_BUFFER_TOO_SMALL; + } + + /* check if there're enough room for ofs and size */ + if (smb2_oob(buf, ptr, 4)) { + return NT_STATUS_BUFFER_TOO_SMALL; + } + + if (blob.data == NULL) { + if (blob.length != 0) { + return NT_STATUS_INTERNAL_ERROR; + } + SSVAL(ptr, 0, 0); + SSVAL(ptr, 2, 0); + return NT_STATUS_OK; + } + + offset = buf->dynamic - buf->hdr; + padding_length = smb2_padding_size(offset, 2); + offset += padding_length; + padding_fix = smb2_padding_fix(buf); + + SSVAL(ptr, 0, offset); + SSVAL(ptr, 2, blob.length); + + status = smb2_grow_buffer(buf, blob.length + padding_length - padding_fix); + NT_STATUS_NOT_OK_RETURN(status); + + memset(buf->dynamic, 0, padding_length); + buf->dynamic += padding_length; + + memcpy(buf->dynamic, blob.data, blob.length); + buf->dynamic += blob.length; + + buf->size += blob.length + padding_length - padding_fix; + buf->body_size += blob.length + padding_length; + + return NT_STATUS_OK; +} + + +/* + push a uint16_t ofs/ uint32_t length/blob triple into a data blob + the ofs points to the start of the offset/length pair, and is relative + to the body start +*/ +NTSTATUS smb2_push_o16s32_blob(struct smb2_request_buffer *buf, + uint16_t ofs, DATA_BLOB blob) +{ + NTSTATUS status; + size_t offset; + size_t padding_length; + size_t padding_fix; + uint8_t *ptr = buf->body+ofs; + + if (buf->dynamic == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + /* check if there're enough room for ofs and size */ + if (smb2_oob(buf, ptr, 6)) { + return NT_STATUS_BUFFER_TOO_SMALL; + } + + if (blob.data == NULL) { + if (blob.length != 0) { + return NT_STATUS_INTERNAL_ERROR; + } + SSVAL(ptr, 0, 0); + SIVAL(ptr, 2, 0); + return NT_STATUS_OK; + } + + offset = buf->dynamic - buf->hdr; + padding_length = smb2_padding_size(offset, 2); + offset += padding_length; + padding_fix = smb2_padding_fix(buf); + + SSVAL(ptr, 0, offset); + SIVAL(ptr, 2, blob.length); + + status = smb2_grow_buffer(buf, blob.length + padding_length - padding_fix); + NT_STATUS_NOT_OK_RETURN(status); + + memset(buf->dynamic, 0, padding_length); + buf->dynamic += padding_length; + + memcpy(buf->dynamic, blob.data, blob.length); + buf->dynamic += blob.length; + + buf->size += blob.length + padding_length - padding_fix; + buf->body_size += blob.length + padding_length; + + return NT_STATUS_OK; +} + + +/* + push a uint32_t ofs/ uint32_t length/blob triple into a data blob + the ofs points to the start of the offset/length pair, and is relative + to the body start +*/ +NTSTATUS smb2_push_o32s32_blob(struct smb2_request_buffer *buf, + uint32_t ofs, DATA_BLOB blob) +{ + NTSTATUS status; + size_t offset; + size_t padding_length; + size_t padding_fix; + uint8_t *ptr = buf->body+ofs; + + if (buf->dynamic == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + /* check if there're enough room for ofs and size */ + if (smb2_oob(buf, ptr, 8)) { + return NT_STATUS_BUFFER_TOO_SMALL; + } + + if (blob.data == NULL) { + if (blob.length != 0) { + return NT_STATUS_INTERNAL_ERROR; + } + SIVAL(ptr, 0, 0); + SIVAL(ptr, 4, 0); + return NT_STATUS_OK; + } + + offset = buf->dynamic - buf->hdr; + padding_length = smb2_padding_size(offset, 8); + offset += padding_length; + padding_fix = smb2_padding_fix(buf); + + SIVAL(ptr, 0, offset); + SIVAL(ptr, 4, blob.length); + + status = smb2_grow_buffer(buf, blob.length + padding_length - padding_fix); + NT_STATUS_NOT_OK_RETURN(status); + + memset(buf->dynamic, 0, padding_length); + buf->dynamic += padding_length; + + memcpy(buf->dynamic, blob.data, blob.length); + buf->dynamic += blob.length; + + buf->size += blob.length + padding_length - padding_fix; + buf->body_size += blob.length + padding_length; + + return NT_STATUS_OK; +} + + +/* + push a uint32_t length/ uint32_t ofs/blob triple into a data blob + the ofs points to the start of the length/offset pair, and is relative + to the body start +*/ +NTSTATUS smb2_push_s32o32_blob(struct smb2_request_buffer *buf, + uint32_t ofs, DATA_BLOB blob) +{ + NTSTATUS status; + size_t offset; + size_t padding_length; + size_t padding_fix; + uint8_t *ptr = buf->body+ofs; + + if (buf->dynamic == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + /* check if there're enough room for ofs and size */ + if (smb2_oob(buf, ptr, 8)) { + return NT_STATUS_BUFFER_TOO_SMALL; + } + + if (blob.data == NULL) { + if (blob.length != 0) { + return NT_STATUS_INTERNAL_ERROR; + } + SIVAL(ptr, 0, 0); + SIVAL(ptr, 4, 0); + return NT_STATUS_OK; + } + + offset = buf->dynamic - buf->hdr; + padding_length = smb2_padding_size(offset, 8); + offset += padding_length; + padding_fix = smb2_padding_fix(buf); + + SIVAL(ptr, 0, blob.length); + SIVAL(ptr, 4, offset); + + status = smb2_grow_buffer(buf, blob.length + padding_length - padding_fix); + NT_STATUS_NOT_OK_RETURN(status); + + memset(buf->dynamic, 0, padding_length); + buf->dynamic += padding_length; + + memcpy(buf->dynamic, blob.data, blob.length); + buf->dynamic += blob.length; + + buf->size += blob.length + padding_length - padding_fix; + buf->body_size += blob.length + padding_length; + + return NT_STATUS_OK; +} + +/* + pull a uint16_t ofs/ uint32_t length/blob triple from a data blob + the ptr points to the start of the offset/length pair +*/ +NTSTATUS smb2_pull_o16s32_blob(struct smb2_request_buffer *buf, TALLOC_CTX *mem_ctx, uint8_t *ptr, DATA_BLOB *blob) +{ + uint16_t ofs; + uint32_t size; + + if (smb2_oob(buf, ptr, 6)) { + return NT_STATUS_BUFFER_TOO_SMALL; + } + ofs = SVAL(ptr, 0); + size = IVAL(ptr, 2); + if (ofs == 0) { + *blob = data_blob(NULL, 0); + return NT_STATUS_OK; + } + if (smb2_oob(buf, buf->hdr + ofs, size)) { + return NT_STATUS_BUFFER_TOO_SMALL; + } + *blob = data_blob_talloc(mem_ctx, buf->hdr + ofs, size); + NT_STATUS_HAVE_NO_MEMORY(blob->data); + return NT_STATUS_OK; +} + +/* + pull a uint32_t ofs/ uint32_t length/blob triple from a data blob + the ptr points to the start of the offset/length pair +*/ +NTSTATUS smb2_pull_o32s32_blob(struct smb2_request_buffer *buf, TALLOC_CTX *mem_ctx, uint8_t *ptr, DATA_BLOB *blob) +{ + uint32_t ofs, size; + if (smb2_oob(buf, ptr, 8)) { + return NT_STATUS_BUFFER_TOO_SMALL; + } + ofs = IVAL(ptr, 0); + size = IVAL(ptr, 4); + if (ofs == 0) { + *blob = data_blob(NULL, 0); + return NT_STATUS_OK; + } + if (smb2_oob(buf, buf->hdr + ofs, size)) { + return NT_STATUS_BUFFER_TOO_SMALL; + } + *blob = data_blob_talloc(mem_ctx, buf->hdr + ofs, size); + NT_STATUS_HAVE_NO_MEMORY(blob->data); + return NT_STATUS_OK; +} + +/* + pull a uint16_t ofs/ uint32_t length/blob triple from a data blob + the ptr points to the start of the offset/length pair + + In this varient the uint16_t is padded by an extra 2 bytes, making + the size aligned on 4 byte boundary +*/ +NTSTATUS smb2_pull_o16As32_blob(struct smb2_request_buffer *buf, TALLOC_CTX *mem_ctx, uint8_t *ptr, DATA_BLOB *blob) +{ + uint32_t ofs, size; + if (smb2_oob(buf, ptr, 8)) { + return NT_STATUS_BUFFER_TOO_SMALL; + } + ofs = SVAL(ptr, 0); + size = IVAL(ptr, 4); + if (ofs == 0) { + *blob = data_blob(NULL, 0); + return NT_STATUS_OK; + } + if (smb2_oob(buf, buf->hdr + ofs, size)) { + return NT_STATUS_BUFFER_TOO_SMALL; + } + *blob = data_blob_talloc(mem_ctx, buf->hdr + ofs, size); + NT_STATUS_HAVE_NO_MEMORY(blob->data); + return NT_STATUS_OK; +} + +/* + pull a uint32_t length/ uint32_t ofs/blob triple from a data blob + the ptr points to the start of the offset/length pair +*/ +NTSTATUS smb2_pull_s32o32_blob(struct smb2_request_buffer *buf, TALLOC_CTX *mem_ctx, uint8_t *ptr, DATA_BLOB *blob) +{ + uint32_t ofs, size; + if (smb2_oob(buf, ptr, 8)) { + return NT_STATUS_BUFFER_TOO_SMALL; + } + size = IVAL(ptr, 0); + ofs = IVAL(ptr, 4); + if (ofs == 0) { + *blob = data_blob(NULL, 0); + return NT_STATUS_OK; + } + if (smb2_oob(buf, buf->hdr + ofs, size)) { + return NT_STATUS_BUFFER_TOO_SMALL; + } + *blob = data_blob_talloc(mem_ctx, buf->hdr + ofs, size); + NT_STATUS_HAVE_NO_MEMORY(blob->data); + return NT_STATUS_OK; +} + +/* + pull a string in a uint16_t ofs/ uint16_t length/blob format + UTF-16 without termination +*/ +NTSTATUS smb2_pull_o16s16_string(struct smb2_request_buffer *buf, TALLOC_CTX *mem_ctx, + uint8_t *ptr, const char **str) +{ + DATA_BLOB blob; + NTSTATUS status; + ssize_t size; + void *vstr; + + status = smb2_pull_o16s16_blob(buf, mem_ctx, ptr, &blob); + NT_STATUS_NOT_OK_RETURN(status); + + if (blob.data == NULL) { + *str = NULL; + return NT_STATUS_OK; + } + + if (blob.length == 0) { + char *s; + s = talloc_strdup(mem_ctx, ""); + NT_STATUS_HAVE_NO_MEMORY(s); + *str = s; + return NT_STATUS_OK; + } + + size = convert_string_talloc(mem_ctx, lp_iconv_convenience(global_loadparm), CH_UTF16, CH_UNIX, + blob.data, blob.length, &vstr); + data_blob_free(&blob); + (*str) = (char *)vstr; + if (size == -1) { + return NT_STATUS_ILLEGAL_CHARACTER; + } + return NT_STATUS_OK; +} + +/* + push a string in a uint16_t ofs/ uint16_t length/blob format + UTF-16 without termination +*/ +NTSTATUS smb2_push_o16s16_string(struct smb2_request_buffer *buf, + uint16_t ofs, const char *str) +{ + DATA_BLOB blob; + NTSTATUS status; + ssize_t size; + + if (str == NULL) { + return smb2_push_o16s16_blob(buf, ofs, data_blob(NULL, 0)); + } + + if (*str == 0) { + blob.data = discard_const(str); + blob.length = 0; + return smb2_push_o16s16_blob(buf, ofs, blob); + } + + size = convert_string_talloc(buf->buffer, lp_iconv_convenience(global_loadparm), CH_UNIX, CH_UTF16, + str, strlen(str), (void **)&blob.data); + if (size == -1) { + return NT_STATUS_ILLEGAL_CHARACTER; + } + blob.length = size; + + status = smb2_push_o16s16_blob(buf, ofs, blob); + data_blob_free(&blob); + return status; +} + +/* + push a file handle into a buffer +*/ +void smb2_push_handle(uint8_t *data, struct smb2_handle *h) +{ + SBVAL(data, 0, h->data[0]); + SBVAL(data, 8, h->data[1]); +} + +/* + pull a file handle from a buffer +*/ +void smb2_pull_handle(uint8_t *ptr, struct smb2_handle *h) +{ + h->data[0] = BVAL(ptr, 0); + h->data[1] = BVAL(ptr, 8); +} diff --git a/source4/libcli/smb2/session.c b/source4/libcli/smb2/session.c new file mode 100644 index 0000000000..31b3e942e9 --- /dev/null +++ b/source4/libcli/smb2/session.c @@ -0,0 +1,275 @@ +/* + Unix SMB/CIFS implementation. + + SMB2 client session handling + + Copyright (C) Andrew Tridgell 2005 + + 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 "libcli/raw/libcliraw.h" +#include "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" +#include "libcli/composite/composite.h" +#include "auth/gensec/gensec.h" +#include "param/param.h" + +/** + initialise a smb2_session structure + */ +struct smb2_session *smb2_session_init(struct smb2_transport *transport, + struct loadparm_context *lp_ctx, + TALLOC_CTX *parent_ctx, bool primary) +{ + struct smb2_session *session; + NTSTATUS status; + + session = talloc_zero(parent_ctx, struct smb2_session); + if (!session) { + return NULL; + } + if (primary) { + session->transport = talloc_steal(session, transport); + } else { + session->transport = talloc_reference(session, transport); + } + + /* prepare a gensec context for later use */ + status = gensec_client_start(session, &session->gensec, + session->transport->socket->event.ctx, + lp_ctx); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(session); + return NULL; + } + + gensec_want_feature(session->gensec, GENSEC_FEATURE_SESSION_KEY); + + return session; +} + +/** + send a session setup request +*/ +struct smb2_request *smb2_session_setup_send(struct smb2_session *session, + struct smb2_session_setup *io) +{ + struct smb2_request *req; + NTSTATUS status; + + req = smb2_request_init(session->transport, SMB2_OP_SESSSETUP, + 0x18, true, io->in.secblob.length); + if (req == NULL) return NULL; + + SBVAL(req->out.hdr, SMB2_HDR_SESSION_ID, session->uid); + SCVAL(req->out.body, 0x02, io->in.vc_number); + SCVAL(req->out.body, 0x03, io->in.security_mode); + SIVAL(req->out.body, 0x04, io->in.capabilities); + SIVAL(req->out.body, 0x08, io->in.channel); + SBVAL(req->out.body, 0x10, io->in.previous_sessionid); + + req->session = session; + + status = smb2_push_o16s16_blob(&req->out, 0x0C, io->in.secblob); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(req); + return NULL; + } + + smb2_transport_send(req); + + return req; +} + + +/** + recv a session setup reply +*/ +NTSTATUS smb2_session_setup_recv(struct smb2_request *req, TALLOC_CTX *mem_ctx, + struct smb2_session_setup *io) +{ + NTSTATUS status; + + if (!smb2_request_receive(req) || + (smb2_request_is_error(req) && + !NT_STATUS_EQUAL(req->status, NT_STATUS_MORE_PROCESSING_REQUIRED))) { + return smb2_request_destroy(req); + } + + SMB2_CHECK_PACKET_RECV(req, 0x08, true); + + io->out.session_flags = SVAL(req->in.body, 0x02); + io->out.uid = BVAL(req->in.hdr, SMB2_HDR_SESSION_ID); + + status = smb2_pull_o16s16_blob(&req->in, mem_ctx, req->in.body+0x04, &io->out.secblob); + if (!NT_STATUS_IS_OK(status)) { + smb2_request_destroy(req); + return status; + } + + return smb2_request_destroy(req); +} + +/* + sync session setup request +*/ +NTSTATUS smb2_session_setup(struct smb2_session *session, + TALLOC_CTX *mem_ctx, struct smb2_session_setup *io) +{ + struct smb2_request *req = smb2_session_setup_send(session, io); + return smb2_session_setup_recv(req, mem_ctx, io); +} + + +struct smb2_session_state { + struct smb2_session_setup io; + struct smb2_request *req; + NTSTATUS gensec_status; +}; + +/* + handle continuations of the spnego session setup +*/ +static void session_request_handler(struct smb2_request *req) +{ + struct composite_context *c = talloc_get_type(req->async.private_data, + struct composite_context); + struct smb2_session_state *state = talloc_get_type(c->private_data, + struct smb2_session_state); + struct smb2_session *session = req->session; + + c->status = smb2_session_setup_recv(req, c, &state->io); + if (NT_STATUS_EQUAL(c->status, NT_STATUS_MORE_PROCESSING_REQUIRED) || + (NT_STATUS_IS_OK(c->status) && + NT_STATUS_EQUAL(state->gensec_status, NT_STATUS_MORE_PROCESSING_REQUIRED))) { + NTSTATUS session_key_err; + DATA_BLOB session_key; + c->status = gensec_update(session->gensec, c, + state->io.out.secblob, + &state->io.in.secblob); + state->gensec_status = c->status; + + session_key_err = gensec_session_key(session->gensec, &session_key); + if (NT_STATUS_IS_OK(session_key_err)) { + session->session_key = session_key; + } + } + + session->uid = state->io.out.uid; + + if (NT_STATUS_EQUAL(c->status, NT_STATUS_MORE_PROCESSING_REQUIRED)) { + state->req = smb2_session_setup_send(session, &state->io); + if (state->req == NULL) { + composite_error(c, NT_STATUS_NO_MEMORY); + return; + } + + state->req->async.fn = session_request_handler; + state->req->async.private_data = c; + return; + } + + if (!NT_STATUS_IS_OK(c->status)) { + composite_error(c, c->status); + return; + } + + if (session->transport->signing_required) { + if (session->session_key.length == 0) { + DEBUG(0,("Wrong session key length %u for SMB2 signing\n", + (unsigned)session->session_key.length)); + composite_error(c, NT_STATUS_ACCESS_DENIED); + return; + } + session->signing_active = true; + } + + composite_done(c); +} + +/* + a composite function that does a full SPNEGO session setup + */ +struct composite_context *smb2_session_setup_spnego_send(struct smb2_session *session, + struct cli_credentials *credentials) +{ + struct composite_context *c; + struct smb2_session_state *state; + + c = composite_create(session, session->transport->socket->event.ctx); + if (c == NULL) return NULL; + + state = talloc(c, struct smb2_session_state); + if (composite_nomem(state, c)) return c; + c->private_data = state; + + ZERO_STRUCT(state->io); + state->io.in.vc_number = 0; + if (session->transport->signing_required) { + state->io.in.security_mode = + SMB2_NEGOTIATE_SIGNING_ENABLED | SMB2_NEGOTIATE_SIGNING_REQUIRED; + } + state->io.in.capabilities = 0; + state->io.in.channel = 0; + state->io.in.previous_sessionid = 0; + + c->status = gensec_set_credentials(session->gensec, credentials); + if (!composite_is_ok(c)) return c; + + c->status = gensec_set_target_hostname(session->gensec, + session->transport->socket->hostname); + if (!composite_is_ok(c)) return c; + + c->status = gensec_set_target_service(session->gensec, "cifs"); + if (!composite_is_ok(c)) return c; + + c->status = gensec_start_mech_by_oid(session->gensec, GENSEC_OID_SPNEGO); + if (!composite_is_ok(c)) return c; + + c->status = gensec_update(session->gensec, c, + session->transport->negotiate.secblob, + &state->io.in.secblob); + if (!NT_STATUS_EQUAL(c->status, NT_STATUS_MORE_PROCESSING_REQUIRED)) { + composite_error(c, c->status); + return c; + } + state->gensec_status = c->status; + + state->req = smb2_session_setup_send(session, &state->io); + composite_continue_smb2(c, state->req, session_request_handler, c); + return c; +} + +/* + receive a composite session setup reply +*/ +NTSTATUS smb2_session_setup_spnego_recv(struct composite_context *c) +{ + NTSTATUS status; + status = composite_wait(c); + talloc_free(c); + return status; +} + +/* + sync version of smb2_session_setup_spnego +*/ +NTSTATUS smb2_session_setup_spnego(struct smb2_session *session, + struct cli_credentials *credentials) +{ + struct composite_context *c = smb2_session_setup_spnego_send(session, credentials); + return smb2_session_setup_spnego_recv(c); +} diff --git a/source4/libcli/smb2/setinfo.c b/source4/libcli/smb2/setinfo.c new file mode 100644 index 0000000000..69c0f45b63 --- /dev/null +++ b/source4/libcli/smb2/setinfo.c @@ -0,0 +1,122 @@ +/* + Unix SMB/CIFS implementation. + + SMB2 client setinfo calls + + Copyright (C) Andrew Tridgell 2005 + + 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 "libcli/raw/libcliraw.h" +#include "libcli/raw/raw_proto.h" +#include "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" + +/* + send a setinfo request +*/ +struct smb2_request *smb2_setinfo_send(struct smb2_tree *tree, struct smb2_setinfo *io) +{ + NTSTATUS status; + struct smb2_request *req; + + req = smb2_request_init_tree(tree, SMB2_OP_SETINFO, 0x20, true, io->in.blob.length); + if (req == NULL) return NULL; + + SSVAL(req->out.body, 0x02, io->in.level); + + status = smb2_push_s32o32_blob(&req->out, 0x04, io->in.blob); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(req); + return NULL; + } + + SIVAL(req->out.body, 0x0C, io->in.flags); + smb2_push_handle(req->out.body+0x10, &io->in.file.handle); + + smb2_transport_send(req); + + return req; +} + + +/* + recv a setinfo reply +*/ +NTSTATUS smb2_setinfo_recv(struct smb2_request *req) +{ + if (!smb2_request_receive(req) || + !smb2_request_is_ok(req)) { + return smb2_request_destroy(req); + } + + SMB2_CHECK_PACKET_RECV(req, 0x02, false); + + return smb2_request_destroy(req); +} + +/* + sync setinfo request +*/ +NTSTATUS smb2_setinfo(struct smb2_tree *tree, struct smb2_setinfo *io) +{ + struct smb2_request *req = smb2_setinfo_send(tree, io); + return smb2_setinfo_recv(req); +} + +/* + level specific file setinfo call - async send +*/ +struct smb2_request *smb2_setinfo_file_send(struct smb2_tree *tree, union smb_setfileinfo *io) +{ + struct smb2_setinfo b; + uint16_t smb2_level = smb2_getinfo_map_level(io->generic.level, SMB2_GETINFO_FILE); + struct smb2_request *req; + + if (smb2_level == 0) { + return NULL; + } + + ZERO_STRUCT(b); + b.in.level = smb2_level; + b.in.file.handle = io->generic.in.file.handle; + + /* change levels so the parsers know it is SMB2 */ + if (io->generic.level == RAW_SFILEINFO_RENAME_INFORMATION) { + io->generic.level = RAW_SFILEINFO_RENAME_INFORMATION_SMB2; + } + + if (!smb_raw_setfileinfo_passthru(tree, io->generic.level, io, &b.in.blob)) { + return NULL; + } + + if (io->generic.level == RAW_SFILEINFO_SEC_DESC) { + b.in.flags = io->set_secdesc.in.secinfo_flags; + } + + req = smb2_setinfo_send(tree, &b); + data_blob_free(&b.in.blob); + return req; +} + +/* + level specific file setinfo call - sync +*/ +NTSTATUS smb2_setinfo_file(struct smb2_tree *tree, union smb_setfileinfo *io) +{ + struct smb2_request *req = smb2_setinfo_file_send(tree, io); + return smb2_setinfo_recv(req); +} diff --git a/source4/libcli/smb2/signing.c b/source4/libcli/smb2/signing.c new file mode 100644 index 0000000000..de9e1e9d29 --- /dev/null +++ b/source4/libcli/smb2/signing.c @@ -0,0 +1,117 @@ +/* + Unix SMB/CIFS implementation. + + SMB2 Signing Code + + Copyright (C) Andrew Tridgell <tridge@samba.org> 2008 + + 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 "libcli/raw/libcliraw.h" +#include "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" +#include "lib/crypto/crypto.h" + +/* + sign an outgoing message + */ +NTSTATUS smb2_sign_message(struct smb2_request_buffer *buf, DATA_BLOB session_key) +{ + struct HMACSHA256Context m; + uint8_t res[32]; + uint64_t session_id; + + if (buf->size < NBT_HDR_SIZE + SMB2_HDR_SIGNATURE + 16) { + /* can't sign non-SMB2 messages */ + return NT_STATUS_OK; + } + + session_id = BVAL(buf->hdr, SMB2_HDR_SESSION_ID); + if (session_id == 0) { + /* we don't sign messages with a zero session_id. See + MS-SMB2 3.2.4.1.1 */ + return NT_STATUS_OK; + } + + if (session_key.length == 0) { + DEBUG(2,("Wrong session key length %u for SMB2 signing\n", + (unsigned)session_key.length)); + return NT_STATUS_ACCESS_DENIED; + } + + memset(buf->hdr + SMB2_HDR_SIGNATURE, 0, 16); + + SIVAL(buf->hdr, SMB2_HDR_FLAGS, IVAL(buf->hdr, SMB2_HDR_FLAGS) | SMB2_HDR_FLAG_SIGNED); + + ZERO_STRUCT(m); + hmac_sha256_init(session_key.data, MIN(session_key.length, 16), &m); + hmac_sha256_update(buf->buffer+NBT_HDR_SIZE, buf->size-NBT_HDR_SIZE, &m); + hmac_sha256_final(res, &m); + DEBUG(5,("signed SMB2 message of size %u\n", (unsigned)buf->size - NBT_HDR_SIZE)); + + memcpy(buf->hdr + SMB2_HDR_SIGNATURE, res, 16); + + return NT_STATUS_OK; +} + +/* + check an incoming signature + */ +NTSTATUS smb2_check_signature(struct smb2_request_buffer *buf, DATA_BLOB session_key) +{ + uint64_t session_id; + struct HMACSHA256Context m; + uint8_t res[SHA256_DIGEST_LENGTH]; + uint8_t sig[16]; + + if (buf->size < NBT_HDR_SIZE + SMB2_HDR_SIGNATURE + 16) { + /* can't check non-SMB2 messages */ + return NT_STATUS_OK; + } + + session_id = BVAL(buf->hdr, SMB2_HDR_SESSION_ID); + if (session_id == 0) { + /* don't sign messages with a zero session_id. See + MS-SMB2 3.2.4.1.1 */ + return NT_STATUS_OK; + } + + if (session_key.length == 0) { + /* we don't have the session key yet */ + return NT_STATUS_OK; + } + + memcpy(sig, buf->hdr+SMB2_HDR_SIGNATURE, 16); + + memset(buf->hdr + SMB2_HDR_SIGNATURE, 0, 16); + + ZERO_STRUCT(m); + hmac_sha256_init(session_key.data, MIN(session_key.length, 16), &m); + hmac_sha256_update(buf->hdr, buf->size-NBT_HDR_SIZE, &m); + hmac_sha256_final(res, &m); + + memcpy(buf->hdr+SMB2_HDR_SIGNATURE, sig, 16); + + if (memcmp(res, sig, 16) != 0) { + DEBUG(0,("Bad SMB2 signature for message of size %u\n", + (unsigned)buf->size-NBT_HDR_SIZE)); + dump_data(0, sig, 16); + dump_data(0, res, 16); + return NT_STATUS_ACCESS_DENIED; + } + + return NT_STATUS_OK; +} diff --git a/source4/libcli/smb2/smb2.h b/source4/libcli/smb2/smb2.h new file mode 100644 index 0000000000..f00107de60 --- /dev/null +++ b/source4/libcli/smb2/smb2.h @@ -0,0 +1,302 @@ +/* + Unix SMB/CIFS implementation. + + SMB2 client library header + + Copyright (C) Andrew Tridgell 2005 + + 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 __LIBCLI_SMB2_SMB2_H__ +#define __LIBCLI_SMB2_SMB2_H__ + +#include "libcli/raw/request.h" +#include "libcli/raw/libcliraw.h" + +struct smb2_handle; + +/* + information returned from the negotiate process +*/ +struct smb2_negotiate { + DATA_BLOB secblob; + NTTIME system_time; + NTTIME server_start_time; + uint16_t security_mode; +}; + +/* this is the context for the smb2 transport layer */ +struct smb2_transport { + /* socket level info */ + struct smbcli_socket *socket; + + struct smb2_negotiate negotiate; + + /* next seqnum to allocate */ + uint64_t seqnum; + + /* a list of requests that are pending for receive on this + connection */ + struct smb2_request *pending_recv; + + /* context of the stream -> packet parser */ + struct packet_context *packet; + + /* an idle function - if this is defined then it will be + called once every period microseconds while we are waiting + for a packet */ + struct { + void (*func)(struct smb2_transport *, void *); + void *private; + uint_t period; + } idle; + + struct { + /* a oplock break request handler */ + bool (*handler)(struct smb2_transport *transport, + const struct smb2_handle *handle, + uint8_t level, void *private_data); + /* private data passed to the oplock handler */ + void *private_data; + } oplock; + + struct smbcli_options options; + + bool signing_required; +}; + + +/* + SMB2 tree context +*/ +struct smb2_tree { + struct smb2_session *session; + uint32_t tid; +}; + +/* + SMB2 session context +*/ +struct smb2_session { + struct smb2_transport *transport; + struct gensec_security *gensec; + uint64_t uid; + DATA_BLOB session_key; + bool signing_active; +}; + + +struct smb2_request_buffer { + /* the raw SMB2 buffer, including the 4 byte length header */ + uint8_t *buffer; + + /* the size of the raw buffer, including 4 byte header */ + size_t size; + + /* how much has been allocated - on reply the buffer is over-allocated to + prevent too many realloc() calls + */ + size_t allocated; + + /* the start of the SMB2 header - this is always buffer+4 */ + uint8_t *hdr; + + /* the packet body */ + uint8_t *body; + size_t body_fixed; + size_t body_size; + + /* this point to the next dynamic byte that can be used + * this will be moved when some dynamic data is pushed + */ + uint8_t *dynamic; + + /* this is used to range check and align strings and buffers */ + struct request_bufinfo bufinfo; +}; + + +/* + a client request moves between the following 4 states. +*/ +enum smb2_request_state {SMB2_REQUEST_INIT, /* we are creating the request */ + SMB2_REQUEST_RECV, /* we are waiting for a matching reply */ + SMB2_REQUEST_DONE, /* the request is finished */ + SMB2_REQUEST_ERROR}; /* a packet or transport level error has occurred */ + +/* the context for a single SMB2 request */ +struct smb2_request { + /* allow a request to be part of a list of requests */ + struct smb2_request *next, *prev; + + /* each request is in one of 3 possible states */ + enum smb2_request_state state; + + struct smb2_transport *transport; + struct smb2_session *session; + struct smb2_tree *tree; + + uint64_t seqnum; + + struct { + bool do_cancel; + bool can_cancel; + uint32_t pending_id; + } cancel; + + /* the NT status for this request. Set by packet receive code + or code detecting error. */ + NTSTATUS status; + + struct smb2_request_buffer in; + struct smb2_request_buffer out; + + /* information on what to do with a reply when it is received + asyncronously. If this is not setup when a reply is received then + the reply is discarded + + The private pointer is private to the caller of the client + library (the application), not private to the library + */ + struct { + void (*fn)(struct smb2_request *); + void *private_data; + } async; +}; + + +#define SMB2_MIN_SIZE 0x42 + +/* offsets into header elements for a sync SMB2 request */ +#define SMB2_HDR_PROTOCOL_ID 0x00 +#define SMB2_HDR_LENGTH 0x04 +#define SMB2_HDR_EPOCH 0x06 +#define SMB2_HDR_STATUS 0x08 +#define SMB2_HDR_OPCODE 0x0c +#define SMB2_HDR_CREDIT 0x0e +#define SMB2_HDR_FLAGS 0x10 +#define SMB2_HDR_NEXT_COMMAND 0x14 +#define SMB2_HDR_MESSAGE_ID 0x18 +#define SMB2_HDR_PID 0x20 +#define SMB2_HDR_TID 0x24 +#define SMB2_HDR_SESSION_ID 0x28 +#define SMB2_HDR_SIGNATURE 0x30 /* 16 bytes */ +#define SMB2_HDR_BODY 0x40 + +/* header flags */ +#define SMB2_HDR_FLAG_REDIRECT 0x01 +#define SMB2_HDR_FLAG_ASYNC 0x02 +#define SMB2_HDR_FLAG_CHAINED 0x04 +#define SMB2_HDR_FLAG_SIGNED 0x08 +#define SMB2_HDR_FLAG_DFS 0x10000000 + +/* SMB2 opcodes */ +#define SMB2_OP_NEGPROT 0x00 +#define SMB2_OP_SESSSETUP 0x01 +#define SMB2_OP_LOGOFF 0x02 +#define SMB2_OP_TCON 0x03 +#define SMB2_OP_TDIS 0x04 +#define SMB2_OP_CREATE 0x05 +#define SMB2_OP_CLOSE 0x06 +#define SMB2_OP_FLUSH 0x07 +#define SMB2_OP_READ 0x08 +#define SMB2_OP_WRITE 0x09 +#define SMB2_OP_LOCK 0x0a +#define SMB2_OP_IOCTL 0x0b +#define SMB2_OP_CANCEL 0x0c +#define SMB2_OP_KEEPALIVE 0x0d +#define SMB2_OP_FIND 0x0e +#define SMB2_OP_NOTIFY 0x0f +#define SMB2_OP_GETINFO 0x10 +#define SMB2_OP_SETINFO 0x11 +#define SMB2_OP_BREAK 0x12 + +#define SMB2_MAGIC 0x424D53FE /* 0xFE 'S' 'M' 'B' */ + +/* the dialect we support */ +#define SMB2_DIALECT_REVISION 0x202 + +/* SMB2 negotiate security_mode */ +#define SMB2_NEGOTIATE_SIGNING_ENABLED 0x01 +#define SMB2_NEGOTIATE_SIGNING_REQUIRED 0x02 + +/* SMB2 capabilities - only 1 so far. I'm sure more will be added */ +#define SMB2_CAP_DFS 0x0 +/* so we can spot new caps as added */ +#define SMB2_CAP_ALL SMB2_CAP_DFS + +/* SMB2 share flags */ +#define SMB2_SHAREFLAG_MANUAL_CACHING 0x0000 +#define SMB2_SHAREFLAG_AUTO_CACHING 0x0010 +#define SMB2_SHAREFLAG_VDO_CACHING 0x0020 +#define SMB2_SHAREFLAG_NO_CACHING 0x0030 +#define SMB2_SHAREFLAG_DFS 0x0001 +#define SMB2_SHAREFLAG_DFS_ROOT 0x0002 +#define SMB2_SHAREFLAG_RESTRICT_EXCLUSIVE_OPENS 0x0100 +#define SMB2_SHAREFLAG_FORCE_SHARED_DELETE 0x0200 +#define SMB2_SHAREFLAG_ALLOW_NAMESPACE_CACHING 0x0400 +#define SMB2_SHAREFLAG_ACCESS_BASED_DIRECTORY_ENUM 0x0800 +#define SMB2_SHAREFLAG_ALL 0x0F33 + +/* SMB2 create security flags */ +#define SMB2_SECURITY_DYNAMIC_TRACKING 0x01 +#define SMB2_SECURITY_EFFECTIVE_ONLY 0x02 + +/* SMB2 requested oplock levels */ +#define SMB2_OPLOCK_LEVEL_NONE 0x00 +#define SMB2_OPLOCK_LEVEL_II 0x01 +#define SMB2_OPLOCK_LEVEL_EXCLUSIVE 0x08 +#define SMB2_OPLOCK_LEVEL_BATCH 0x09 + +/* SMB2 impersonation levels */ +#define SMB2_IMPERSONATION_ANONYMOUS 0x00 +#define SMB2_IMPERSONATION_IDENTIFICATION 0x01 +#define SMB2_IMPERSONATION_IMPERSONATION 0x02 +#define SMB2_IMPERSONATION_DELEGATE 0x03 + +/* SMB2 create tags */ +#define SMB2_CREATE_TAG_EXTA "ExtA" +#define SMB2_CREATE_TAG_MXAC "MxAc" +#define SMB2_CREATE_TAG_SECD "SecD" +#define SMB2_CREATE_TAG_DHNQ "DHnQ" +#define SMB2_CREATE_TAG_DHNC "DHnC" +#define SMB2_CREATE_TAG_ALSI "AlSi" +#define SMB2_CREATE_TAG_TWRP "TWrp" +#define SMB2_CREATE_TAG_QFID "QFid" + +/* SMB2 Create ignore some more create_options */ +#define SMB2_CREATE_OPTIONS_NOT_SUPPORTED_MASK (NTCREATEX_OPTIONS_TREE_CONNECTION | \ + NTCREATEX_OPTIONS_OPFILTER) + +/* + check that a body has the expected size +*/ +#define SMB2_CHECK_PACKET_RECV(req, size, dynamic) do { \ + size_t is_size = req->in.body_size; \ + uint16_t field_size = SVAL(req->in.body, 0); \ + uint16_t want_size = ((dynamic)?(size)+1:(size)); \ + if (is_size < (size)) { \ + DEBUG(0,("%s: buffer too small 0x%x. Expected 0x%x\n", \ + __location__, (unsigned)is_size, (unsigned)want_size)); \ + return NT_STATUS_BUFFER_TOO_SMALL; \ + }\ + if (field_size != want_size) { \ + DEBUG(0,("%s: unexpected fixed body size 0x%x. Expected 0x%x\n", \ + __location__, (unsigned)field_size, (unsigned)want_size)); \ + return NT_STATUS_INVALID_PARAMETER; \ + } \ +} while (0) + +#endif diff --git a/source4/libcli/smb2/smb2_calls.h b/source4/libcli/smb2/smb2_calls.h new file mode 100644 index 0000000000..f66236af30 --- /dev/null +++ b/source4/libcli/smb2/smb2_calls.h @@ -0,0 +1,110 @@ +/* + Unix SMB/CIFS implementation. + + SMB2 client calls + + Copyright (C) Andrew Tridgell 2005 + + 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 "libcli/raw/interfaces.h" + +struct smb2_negprot { + struct { + uint16_t dialect_count; /* size of dialects array */ + uint16_t security_mode; /* 0==signing disabled + 1==signing enabled */ + uint16_t reserved; + uint32_t capabilities; + struct GUID client_guid; + NTTIME start_time; + uint16_t *dialects; + } in; + struct { + /* static body buffer 64 (0x40) bytes */ + /* uint16_t buffer_code; 0x41 = 0x40 + 1 */ + uint16_t security_mode; /* SMB2_NEGOTIATE_SIGNING_* */ + uint16_t dialect_revision; + uint16_t reserved; + struct GUID server_guid; + uint32_t capabilities; + uint32_t max_transact_size; + uint32_t max_read_size; + uint32_t max_write_size; + NTTIME system_time; + NTTIME server_start_time; + /* uint16_t secblob_ofs */ + /* uint16_t secblob_size */ + uint32_t reserved2; + DATA_BLOB secblob; + } out; +}; + +/* getinfo classes */ +#define SMB2_GETINFO_FILE 0x01 +#define SMB2_GETINFO_FS 0x02 +#define SMB2_GETINFO_SECURITY 0x03 +#define SMB2_GETINFO_QUOTA 0x04 + +#define SMB2_GETINFO_ADD_OWNER_SECURITY 0x01 +#define SMB2_GETINFO_ADD_GROUP_SECURITY 0x02 +#define SMB2_GETINFO_ADD_DACL_SECURITY 0x04 +#define SMB2_GETINFO_ADD_SACL_SECURITY 0x08 +#define SMB2_GETINFO_ADD_LABEL_SECURITY 0x10 + +/* NOTE! the getinfo fs and file levels exactly match up with the + 'passthru' SMB levels, which are levels >= 1000. The SMB2 client + lib uses the names from the libcli/raw/ library */ + +struct smb2_getinfo { + struct { + /* static body buffer 40 (0x28) bytes */ + /* uint16_t buffer_code; 0x29 = 0x28 + 1 */ + uint8_t info_type; + uint8_t info_class; + uint32_t output_buffer_length; + /* uint32_t input_buffer_offset; */ + uint32_t reserved; + uint32_t input_buffer_length; + uint32_t additional_information; /* SMB2_GETINFO_ADD_* */ + uint32_t getinfo_flags; /* level specific */ + union smb_handle file; + DATA_BLOB blob; + } in; + + struct { + /* static body buffer 8 (0x08) bytes */ + /* uint16_t buffer_code; 0x09 = 0x08 + 1 */ + /* uint16_t blob_ofs; */ + /* uint16_t blob_size; */ + + /* dynamic body */ + DATA_BLOB blob; + } out; +}; + +struct smb2_setinfo { + struct { + uint16_t level; + uint32_t flags; + union smb_handle file; + DATA_BLOB blob; + } in; +}; + +struct cli_credentials; +struct event_context; +struct resolve_context; +#include "libcli/smb2/smb2_proto.h" diff --git a/source4/libcli/smb2/tcon.c b/source4/libcli/smb2/tcon.c new file mode 100644 index 0000000000..ec7152b264 --- /dev/null +++ b/source4/libcli/smb2/tcon.c @@ -0,0 +1,112 @@ +/* + Unix SMB/CIFS implementation. + + SMB2 client tree handling + + Copyright (C) Andrew Tridgell 2005 + + 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 "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" + +/* + initialise a smb2_session structure + */ +struct smb2_tree *smb2_tree_init(struct smb2_session *session, + TALLOC_CTX *parent_ctx, bool primary) +{ + struct smb2_tree *tree; + + tree = talloc_zero(parent_ctx, struct smb2_tree); + if (!session) { + return NULL; + } + if (primary) { + tree->session = talloc_steal(tree, session); + } else { + tree->session = talloc_reference(tree, session); + } + return tree; +} + +/* + send a tree connect +*/ +struct smb2_request *smb2_tree_connect_send(struct smb2_tree *tree, + struct smb2_tree_connect *io) +{ + struct smb2_request *req; + NTSTATUS status; + + req = smb2_request_init(tree->session->transport, SMB2_OP_TCON, + 0x08, true, 0); + if (req == NULL) return NULL; + + SBVAL(req->out.hdr, SMB2_HDR_SESSION_ID, tree->session->uid); + req->session = tree->session; + + SSVAL(req->out.body, 0x02, io->in.reserved); + status = smb2_push_o16s16_string(&req->out, 0x04, io->in.path); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(req); + return NULL; + } + + smb2_transport_send(req); + + return req; +} + + +/* + recv a tree connect reply +*/ +NTSTATUS smb2_tree_connect_recv(struct smb2_request *req, struct smb2_tree_connect *io) +{ + if (!smb2_request_receive(req) || + smb2_request_is_error(req)) { + return smb2_request_destroy(req); + } + + SMB2_CHECK_PACKET_RECV(req, 0x10, false); + + io->out.tid = IVAL(req->in.hdr, SMB2_HDR_TID); + + io->out.share_type = CVAL(req->in.body, 0x02); + io->out.reserved = CVAL(req->in.body, 0x03); + io->out.flags = IVAL(req->in.body, 0x04); + io->out.capabilities= IVAL(req->in.body, 0x08); + io->out.access_mask = IVAL(req->in.body, 0x0C); + + if (io->out.capabilities & ~SMB2_CAP_ALL) { + DEBUG(0,("Unknown capabilities mask 0x%x\n", io->out.capabilities)); + } + if (io->out.flags & ~SMB2_SHAREFLAG_ALL) { + DEBUG(0,("Unknown tcon shareflag 0x%x\n", io->out.flags)); + } + + return smb2_request_destroy(req); +} + +/* + sync tree connect request +*/ +NTSTATUS smb2_tree_connect(struct smb2_tree *tree, struct smb2_tree_connect *io) +{ + struct smb2_request *req = smb2_tree_connect_send(tree, io); + return smb2_tree_connect_recv(req, io); +} diff --git a/source4/libcli/smb2/tdis.c b/source4/libcli/smb2/tdis.c new file mode 100644 index 0000000000..5adad9dc6e --- /dev/null +++ b/source4/libcli/smb2/tdis.c @@ -0,0 +1,65 @@ +/* + Unix SMB/CIFS implementation. + + SMB2 client tdis handling + + Copyright (C) Andrew Tridgell 2005 + + 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 "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" + +/* + send a tdis request +*/ +struct smb2_request *smb2_tdis_send(struct smb2_tree *tree) +{ + struct smb2_request *req; + + req = smb2_request_init_tree(tree, SMB2_OP_TDIS, 0x04, false, 0); + if (req == NULL) return NULL; + + SSVAL(req->out.body, 0x02, 0); + + smb2_transport_send(req); + + return req; +} + + +/* + recv a tdis reply +*/ +NTSTATUS smb2_tdis_recv(struct smb2_request *req) +{ + if (!smb2_request_receive(req) || + !smb2_request_is_ok(req)) { + return smb2_request_destroy(req); + } + + SMB2_CHECK_PACKET_RECV(req, 0x04, false); + return smb2_request_destroy(req); +} + +/* + sync tdis request +*/ +NTSTATUS smb2_tdis(struct smb2_tree *tree) +{ + struct smb2_request *req = smb2_tdis_send(tree); + return smb2_tdis_recv(req); +} diff --git a/source4/libcli/smb2/transport.c b/source4/libcli/smb2/transport.c new file mode 100644 index 0000000000..b946a102c8 --- /dev/null +++ b/source4/libcli/smb2/transport.c @@ -0,0 +1,417 @@ +/* + Unix SMB/CIFS implementation. + + SMB2 client transport context management functions + + Copyright (C) Andrew Tridgell 2005 + + 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 "libcli/raw/libcliraw.h" +#include "libcli/raw/raw_proto.h" +#include "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" +#include "lib/socket/socket.h" +#include "lib/events/events.h" +#include "lib/stream/packet.h" +#include "lib/util/dlinklist.h" + + +/* + an event has happened on the socket +*/ +static void smb2_transport_event_handler(struct event_context *ev, + struct fd_event *fde, + uint16_t flags, void *private) +{ + struct smb2_transport *transport = talloc_get_type(private, + struct smb2_transport); + if (flags & EVENT_FD_READ) { + packet_recv(transport->packet); + return; + } + if (flags & EVENT_FD_WRITE) { + packet_queue_run(transport->packet); + } +} + +/* + destroy a transport + */ +static int transport_destructor(struct smb2_transport *transport) +{ + smb2_transport_dead(transport, NT_STATUS_LOCAL_DISCONNECT); + return 0; +} + + +/* + handle receive errors +*/ +static void smb2_transport_error(void *private, NTSTATUS status) +{ + struct smb2_transport *transport = talloc_get_type(private, + struct smb2_transport); + smb2_transport_dead(transport, status); +} + +static NTSTATUS smb2_transport_finish_recv(void *private, DATA_BLOB blob); + +/* + create a transport structure based on an established socket +*/ +struct smb2_transport *smb2_transport_init(struct smbcli_socket *sock, + TALLOC_CTX *parent_ctx, + struct smbcli_options *options) +{ + struct smb2_transport *transport; + + transport = talloc_zero(parent_ctx, struct smb2_transport); + if (!transport) return NULL; + + transport->socket = talloc_steal(transport, sock); + transport->options = *options; + + /* setup the stream -> packet parser */ + transport->packet = packet_init(transport); + if (transport->packet == NULL) { + talloc_free(transport); + return NULL; + } + packet_set_private(transport->packet, transport); + packet_set_socket(transport->packet, transport->socket->sock); + packet_set_callback(transport->packet, smb2_transport_finish_recv); + packet_set_full_request(transport->packet, packet_full_request_nbt); + packet_set_error_handler(transport->packet, smb2_transport_error); + packet_set_event_context(transport->packet, transport->socket->event.ctx); + packet_set_nofree(transport->packet); + + /* take over event handling from the socket layer - it only + handles events up until we are connected */ + talloc_free(transport->socket->event.fde); + transport->socket->event.fde = event_add_fd(transport->socket->event.ctx, + transport->socket, + socket_get_fd(transport->socket->sock), + EVENT_FD_READ, + smb2_transport_event_handler, + transport); + + packet_set_fde(transport->packet, transport->socket->event.fde); + packet_set_serialise(transport->packet); + + talloc_set_destructor(transport, transport_destructor); + + return transport; +} + +/* + mark the transport as dead +*/ +void smb2_transport_dead(struct smb2_transport *transport, NTSTATUS status) +{ + smbcli_sock_dead(transport->socket); + + if (NT_STATUS_EQUAL(NT_STATUS_UNSUCCESSFUL, status)) { + status = NT_STATUS_UNEXPECTED_NETWORK_ERROR; + } + + /* kill all pending receives */ + while (transport->pending_recv) { + struct smb2_request *req = transport->pending_recv; + req->state = SMB2_REQUEST_ERROR; + req->status = status; + DLIST_REMOVE(transport->pending_recv, req); + if (req->async.fn) { + req->async.fn(req); + } + } +} + +static NTSTATUS smb2_handle_oplock_break(struct smb2_transport *transport, + const DATA_BLOB *blob) +{ + uint8_t *hdr; + uint16_t opcode; + + hdr = blob->data+NBT_HDR_SIZE; + + if (blob->length < (SMB2_MIN_SIZE+0x18)) { + DEBUG(1,("Discarding smb2 oplock reply of size %u\n", + (unsigned)blob->length)); + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + + opcode = SVAL(hdr, SMB2_HDR_OPCODE); + + if (opcode != SMB2_OP_BREAK) { + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + + if (transport->oplock.handler) { + uint8_t *body = hdr+SMB2_HDR_BODY; + struct smb2_handle h; + uint8_t level; + + level = CVAL(body, 0x02); + smb2_pull_handle(body+0x08, &h); + + transport->oplock.handler(transport, &h, level, + transport->oplock.private_data); + } else { + DEBUG(5,("Got SMB2 oplock break with no handler\n")); + } + + return NT_STATUS_OK; +} + +/* + we have a full request in our receive buffer - match it to a pending request + and process + */ +static NTSTATUS smb2_transport_finish_recv(void *private, DATA_BLOB blob) +{ + struct smb2_transport *transport = talloc_get_type(private, + struct smb2_transport); + uint8_t *buffer, *hdr; + int len; + struct smb2_request *req = NULL; + uint64_t seqnum; + uint32_t flags; + uint16_t buffer_code; + uint32_t dynamic_size; + uint32_t i; + NTSTATUS status; + + buffer = blob.data; + len = blob.length; + + hdr = buffer+NBT_HDR_SIZE; + + if (len < SMB2_MIN_SIZE) { + DEBUG(1,("Discarding smb2 reply of size %d\n", len)); + goto error; + } + + flags = IVAL(hdr, SMB2_HDR_FLAGS); + seqnum = BVAL(hdr, SMB2_HDR_MESSAGE_ID); + + /* see MS-SMB2 3.2.5.19 */ + if (seqnum == UINT64_MAX) { + return smb2_handle_oplock_break(transport, &blob); + } + + /* match the incoming request against the list of pending requests */ + for (req=transport->pending_recv; req; req=req->next) { + if (req->seqnum == seqnum) break; + } + + if (!req) { + DEBUG(1,("Discarding unmatched reply with seqnum 0x%llx op %d\n", + (long long)seqnum, SVAL(hdr, SMB2_HDR_OPCODE))); + goto error; + } + + /* fill in the 'in' portion of the matching request */ + req->in.buffer = buffer; + talloc_steal(req, buffer); + req->in.size = len; + req->in.allocated = req->in.size; + + req->in.hdr = hdr; + req->in.body = hdr+SMB2_HDR_BODY; + req->in.body_size = req->in.size - (SMB2_HDR_BODY+NBT_HDR_SIZE); + req->status = NT_STATUS(IVAL(hdr, SMB2_HDR_STATUS)); + + if ((flags & SMB2_HDR_FLAG_ASYNC) && + NT_STATUS_EQUAL(req->status, STATUS_PENDING)) { + req->cancel.can_cancel = true; + req->cancel.pending_id = IVAL(hdr, SMB2_HDR_PID); + for (i=0; i< req->cancel.do_cancel; i++) { + smb2_cancel(req); + } + talloc_free(buffer); + return NT_STATUS_OK; + } + + if (req->session && req->session->signing_active) { + status = smb2_check_signature(&req->in, + req->session->session_key); + if (!NT_STATUS_IS_OK(status)) { + /* the spec says to ignore packets with a bad signature */ + talloc_free(buffer); + return status; + } + } + + buffer_code = SVAL(req->in.body, 0); + req->in.body_fixed = (buffer_code & ~1); + req->in.dynamic = NULL; + dynamic_size = req->in.body_size - req->in.body_fixed; + if (dynamic_size != 0 && (buffer_code & 1)) { + req->in.dynamic = req->in.body + req->in.body_fixed; + if (smb2_oob(&req->in, req->in.dynamic, dynamic_size)) { + DEBUG(1,("SMB2 request invalid dynamic size 0x%x\n", + dynamic_size)); + goto error; + } + } + + smb2_setup_bufinfo(req); + + DEBUG(2, ("SMB2 RECV seqnum=0x%llx\n", (long long)req->seqnum)); + dump_data(5, req->in.body, req->in.body_size); + + /* if this request has an async handler then call that to + notify that the reply has been received. This might destroy + the request so it must happen last */ + DLIST_REMOVE(transport->pending_recv, req); + req->state = SMB2_REQUEST_DONE; + if (req->async.fn) { + req->async.fn(req); + } + return NT_STATUS_OK; + +error: + dump_data(5, buffer, len); + if (req) { + DLIST_REMOVE(transport->pending_recv, req); + req->state = SMB2_REQUEST_ERROR; + if (req->async.fn) { + req->async.fn(req); + } + } else { + talloc_free(buffer); + } + return NT_STATUS_UNSUCCESSFUL; +} + +/* + handle timeouts of individual smb requests +*/ +static void smb2_timeout_handler(struct event_context *ev, struct timed_event *te, + struct timeval t, void *private) +{ + struct smb2_request *req = talloc_get_type(private, struct smb2_request); + + if (req->state == SMB2_REQUEST_RECV) { + DLIST_REMOVE(req->transport->pending_recv, req); + } + req->status = NT_STATUS_IO_TIMEOUT; + req->state = SMB2_REQUEST_ERROR; + if (req->async.fn) { + req->async.fn(req); + } +} + + +/* + destroy a request +*/ +static int smb2_request_destructor(struct smb2_request *req) +{ + if (req->state == SMB2_REQUEST_RECV) { + DLIST_REMOVE(req->transport->pending_recv, req); + } + return 0; +} + + +/* + put a request into the send queue +*/ +void smb2_transport_send(struct smb2_request *req) +{ + DATA_BLOB blob; + NTSTATUS status; + + _smb2_setlen(req->out.buffer, req->out.size - NBT_HDR_SIZE); + + DEBUG(2, ("SMB2 send seqnum=0x%llx\n", (long long)req->seqnum)); + dump_data(5, req->out.body, req->out.body_size); + + /* check if the transport is dead */ + if (req->transport->socket->sock == NULL) { + req->state = SMB2_REQUEST_ERROR; + req->status = NT_STATUS_NET_WRITE_FAULT; + return; + } + + /* possibly sign the message */ + if (req->session && req->session->signing_active) { + status = smb2_sign_message(&req->out, req->session->session_key); + if (!NT_STATUS_IS_OK(status)) { + req->state = SMB2_REQUEST_ERROR; + req->status = status; + return; + } + } + + blob = data_blob_const(req->out.buffer, req->out.size); + status = packet_send(req->transport->packet, blob); + if (!NT_STATUS_IS_OK(status)) { + req->state = SMB2_REQUEST_ERROR; + req->status = status; + return; + } + + req->state = SMB2_REQUEST_RECV; + DLIST_ADD(req->transport->pending_recv, req); + + /* add a timeout */ + if (req->transport->options.request_timeout) { + event_add_timed(req->transport->socket->event.ctx, req, + timeval_current_ofs(req->transport->options.request_timeout, 0), + smb2_timeout_handler, req); + } + + talloc_set_destructor(req, smb2_request_destructor); +} + +static void idle_handler(struct event_context *ev, + struct timed_event *te, struct timeval t, void *private) +{ + struct smb2_transport *transport = talloc_get_type(private, + struct smb2_transport); + struct timeval next = timeval_add(&t, 0, transport->idle.period); + transport->socket->event.te = event_add_timed(transport->socket->event.ctx, + transport, + next, + idle_handler, transport); + transport->idle.func(transport, transport->idle.private); +} + +/* + setup the idle handler for a transport + the period is in microseconds +*/ +void smb2_transport_idle_handler(struct smb2_transport *transport, + void (*idle_func)(struct smb2_transport *, void *), + uint64_t period, + void *private) +{ + transport->idle.func = idle_func; + transport->idle.private = private; + transport->idle.period = period; + + if (transport->socket->event.te != NULL) { + talloc_free(transport->socket->event.te); + } + + transport->socket->event.te = event_add_timed(transport->socket->event.ctx, + transport, + timeval_current_ofs(0, period), + idle_handler, transport); +} diff --git a/source4/libcli/smb2/util.c b/source4/libcli/smb2/util.c new file mode 100644 index 0000000000..311cea94a0 --- /dev/null +++ b/source4/libcli/smb2/util.c @@ -0,0 +1,210 @@ +/* + Unix SMB/CIFS implementation. + + SMB2 client utility functions + + Copyright (C) Andrew Tridgell 2005 + + 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 "libcli/raw/libcliraw.h" +#include "libcli/raw/raw_proto.h" +#include "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" +#include "libcli/smb_composite/smb_composite.h" + +/* + simple close wrapper with SMB2 +*/ +NTSTATUS smb2_util_close(struct smb2_tree *tree, struct smb2_handle h) +{ + struct smb2_close c; + + ZERO_STRUCT(c); + c.in.file.handle = h; + + return smb2_close(tree, &c); +} + +/* + unlink a file with SMB2 +*/ +NTSTATUS smb2_util_unlink(struct smb2_tree *tree, const char *fname) +{ + union smb_unlink io; + + ZERO_STRUCT(io); + io.unlink.in.pattern = fname; + + return smb2_composite_unlink(tree, &io); +} + + +/* + rmdir with SMB2 +*/ +NTSTATUS smb2_util_rmdir(struct smb2_tree *tree, const char *dname) +{ + struct smb_rmdir io; + + ZERO_STRUCT(io); + io.in.path = dname; + + return smb2_composite_rmdir(tree, &io); +} + + +/* + mkdir with SMB2 +*/ +NTSTATUS smb2_util_mkdir(struct smb2_tree *tree, const char *dname) +{ + union smb_mkdir io; + + ZERO_STRUCT(io); + io.mkdir.level = RAW_MKDIR_MKDIR; + io.mkdir.in.path = dname; + + return smb2_composite_mkdir(tree, &io); +} + + +/* + set file attribute with SMB2 +*/ +NTSTATUS smb2_util_setatr(struct smb2_tree *tree, const char *name, uint32_t attrib) +{ + union smb_setfileinfo io; + + ZERO_STRUCT(io); + io.basic_info.level = RAW_SFILEINFO_BASIC_INFORMATION; + io.basic_info.in.file.path = name; + io.basic_info.in.attrib = attrib; + + return smb2_composite_setpathinfo(tree, &io); +} + + + + +/* + recursively descend a tree deleting all files + returns the number of files deleted, or -1 on error +*/ +int smb2_deltree(struct smb2_tree *tree, const char *dname) +{ + NTSTATUS status; + uint32_t total_deleted = 0; + uint_t count, i; + union smb_search_data *list; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + struct smb2_find f; + struct smb2_create create_parm; + + /* it might be a file */ + status = smb2_util_unlink(tree, dname); + if (NT_STATUS_IS_OK(status)) { + talloc_free(tmp_ctx); + return 1; + } + if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND) || + NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_PATH_NOT_FOUND) || + NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_FILE)) { + talloc_free(tmp_ctx); + return 0; + } + + if (NT_STATUS_EQUAL(status, NT_STATUS_CANNOT_DELETE)) { + /* it could be read-only */ + status = smb2_util_setatr(tree, dname, FILE_ATTRIBUTE_NORMAL); + status = smb2_util_unlink(tree, dname); + } + if (NT_STATUS_IS_OK(status)) { + talloc_free(tmp_ctx); + return 1; + } + + ZERO_STRUCT(create_parm); + create_parm.in.desired_access = SEC_FLAG_MAXIMUM_ALLOWED; + create_parm.in.share_access = + NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE; + create_parm.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + create_parm.in.create_disposition = NTCREATEX_DISP_OPEN; + create_parm.in.fname = dname; + + status = smb2_create(tree, tmp_ctx, &create_parm); + if (NT_STATUS_IS_ERR(status)) { + DEBUG(2,("Failed to open %s - %s\n", dname, nt_errstr(status))); + talloc_free(tmp_ctx); + return -1; + } + + + ZERO_STRUCT(f); + f.in.file.handle = create_parm.out.file.handle; + f.in.max_response_size = 0x10000; + f.in.level = SMB2_FIND_NAME_INFO; + f.in.pattern = "*"; + + status = smb2_find_level(tree, tmp_ctx, &f, &count, &list); + if (NT_STATUS_IS_ERR(status)) { + DEBUG(2,("Failed to list %s - %s\n", + dname, nt_errstr(status))); + smb2_util_close(tree, create_parm.out.file.handle); + talloc_free(tmp_ctx); + return -1; + } + + for (i=0;i<count;i++) { + char *name; + if (strcmp(".", list[i].name_info.name.s) == 0 || + strcmp("..", list[i].name_info.name.s) == 0) { + continue; + } + name = talloc_asprintf(tmp_ctx, "%s\\%s", dname, list[i].name_info.name.s); + status = smb2_util_unlink(tree, name); + if (NT_STATUS_EQUAL(status, NT_STATUS_CANNOT_DELETE)) { + /* it could be read-only */ + status = smb2_util_setatr(tree, name, FILE_ATTRIBUTE_NORMAL); + status = smb2_util_unlink(tree, name); + } + + if (NT_STATUS_EQUAL(status, NT_STATUS_FILE_IS_A_DIRECTORY)) { + int ret; + ret = smb2_deltree(tree, name); + if (ret > 0) total_deleted += ret; + } + talloc_free(name); + if (NT_STATUS_IS_OK(status)) { + total_deleted++; + } + } + + smb2_util_close(tree, create_parm.out.file.handle); + + status = smb2_util_rmdir(tree, dname); + if (NT_STATUS_IS_ERR(status)) { + DEBUG(2,("Failed to delete %s - %s\n", + dname, nt_errstr(status))); + talloc_free(tmp_ctx); + return -1; + } + + talloc_free(tmp_ctx); + + return total_deleted; +} diff --git a/source4/libcli/smb2/write.c b/source4/libcli/smb2/write.c new file mode 100644 index 0000000000..bc283370d7 --- /dev/null +++ b/source4/libcli/smb2/write.c @@ -0,0 +1,81 @@ +/* + Unix SMB/CIFS implementation. + + SMB2 client write call + + Copyright (C) Andrew Tridgell 2005 + + 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 "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" + +/* + send a write request +*/ +struct smb2_request *smb2_write_send(struct smb2_tree *tree, struct smb2_write *io) +{ + NTSTATUS status; + struct smb2_request *req; + + req = smb2_request_init_tree(tree, SMB2_OP_WRITE, 0x30, true, io->in.data.length); + if (req == NULL) return NULL; + + status = smb2_push_o16s32_blob(&req->out, 0x02, io->in.data); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(req); + return NULL; + } + + SBVAL(req->out.body, 0x08, io->in.offset); + smb2_push_handle(req->out.body+0x10, &io->in.file.handle); + + SBVAL(req->out.body, 0x20, io->in.unknown1); + SBVAL(req->out.body, 0x28, io->in.unknown2); + + smb2_transport_send(req); + + return req; +} + + +/* + recv a write reply +*/ +NTSTATUS smb2_write_recv(struct smb2_request *req, struct smb2_write *io) +{ + if (!smb2_request_receive(req) || + smb2_request_is_error(req)) { + return smb2_request_destroy(req); + } + + SMB2_CHECK_PACKET_RECV(req, 0x10, true); + + io->out._pad = SVAL(req->in.body, 0x02); + io->out.nwritten = IVAL(req->in.body, 0x04); + io->out.unknown1 = BVAL(req->in.body, 0x08); + + return smb2_request_destroy(req); +} + +/* + sync write request +*/ +NTSTATUS smb2_write(struct smb2_tree *tree, struct smb2_write *io) +{ + struct smb2_request *req = smb2_write_send(tree, io); + return smb2_write_recv(req, io); +} |