summaryrefslogtreecommitdiff
path: root/source3/libsmb/smb2cli_base.c
diff options
context:
space:
mode:
Diffstat (limited to 'source3/libsmb/smb2cli_base.c')
-rw-r--r--source3/libsmb/smb2cli_base.c531
1 files changed, 531 insertions, 0 deletions
diff --git a/source3/libsmb/smb2cli_base.c b/source3/libsmb/smb2cli_base.c
new file mode 100644
index 0000000000..318f1a250b
--- /dev/null
+++ b/source3/libsmb/smb2cli_base.c
@@ -0,0 +1,531 @@
+/*
+ Unix SMB/CIFS implementation.
+ smb2 lib
+ Copyright (C) Volker Lendecke 2011
+
+ 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 "client.h"
+#include "read_smb.h"
+#include "smb2cli_base.h"
+#include "lib/async_req/async_sock.h"
+#include "lib/util/tevent_ntstatus.h"
+
+struct smb2cli_req_state {
+ struct tevent_context *ev;
+ struct cli_state *cli;
+
+ const uint8_t *fixed;
+ uint16_t fixed_len;
+ const uint8_t *dyn;
+ uint16_t dyn_len;
+
+ uint8_t nbt[4];
+ uint8_t hdr[64];
+ uint8_t pad[7]; /* padding space for compounding */
+
+ uint8_t *inbuf;
+ struct iovec *recv_iov;
+};
+
+static void smb2cli_req_unset_pending(struct tevent_req *req)
+{
+ struct smb2cli_req_state *state =
+ tevent_req_data(req,
+ struct smb2cli_req_state);
+ struct cli_state *cli = state->cli;
+ int num_pending = talloc_array_length(cli->pending);
+ int i;
+
+ if (num_pending == 1) {
+ /*
+ * The pending read_smb tevent_req is a child of
+ * cli->pending. So if nothing is pending anymore, we need to
+ * delete the socket read fde.
+ */
+ TALLOC_FREE(cli->pending);
+ return;
+ }
+
+ for (i=0; i<num_pending; i++) {
+ if (req == cli->pending[i]) {
+ break;
+ }
+ }
+ if (i == num_pending) {
+ /*
+ * Something's seriously broken. Just returning here is the
+ * right thing nevertheless, the point of this routine is to
+ * remove ourselves from cli->pending.
+ */
+ return;
+ }
+
+ /*
+ * Remove ourselves from the cli->pending array
+ */
+ if (num_pending > 1) {
+ cli->pending[i] = cli->pending[num_pending-1];
+ }
+
+ /*
+ * No NULL check here, we're shrinking by sizeof(void *), and
+ * talloc_realloc just adjusts the size for this.
+ */
+ cli->pending = talloc_realloc(NULL, cli->pending, struct tevent_req *,
+ num_pending - 1);
+ return;
+}
+
+static int smb2cli_req_destructor(struct tevent_req *req)
+{
+ smb2cli_req_unset_pending(req);
+ return 0;
+}
+
+static void smb2cli_inbuf_received(struct tevent_req *subreq);
+
+static bool smb2cli_req_set_pending(struct tevent_req *req)
+{
+ struct smb2cli_req_state *state =
+ tevent_req_data(req,
+ struct smb2cli_req_state);
+ struct cli_state *cli;
+ struct tevent_req **pending;
+ int num_pending;
+ struct tevent_req *subreq;
+
+ cli = state->cli;
+ num_pending = talloc_array_length(cli->pending);
+
+ pending = talloc_realloc(cli, cli->pending, struct tevent_req *,
+ num_pending+1);
+ if (pending == NULL) {
+ return false;
+ }
+ pending[num_pending] = req;
+ cli->pending = pending;
+ talloc_set_destructor(req, smb2cli_req_destructor);
+
+ if (num_pending > 0) {
+ return true;
+ }
+
+ /*
+ * We're the first ones, add the read_smb request that waits for the
+ * answer from the server
+ */
+ subreq = read_smb_send(cli->pending, state->ev, cli->fd);
+ if (subreq == NULL) {
+ smb2cli_req_unset_pending(req);
+ return false;
+ }
+ tevent_req_set_callback(subreq, smb2cli_inbuf_received, cli);
+ return true;
+}
+
+struct tevent_req *smb2cli_req_create(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t cmd,
+ uint32_t flags,
+ const uint8_t *fixed,
+ uint16_t fixed_len,
+ const uint8_t *dyn,
+ uint16_t dyn_len)
+{
+ struct tevent_req *result;
+ struct smb2cli_req_state *state;
+
+ result = tevent_req_create(mem_ctx, &state,
+ struct smb2cli_req_state);
+ if (result == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->cli = cli;
+
+ state->fixed = fixed;
+ state->fixed_len = fixed_len;
+ state->dyn = dyn;
+ state->dyn_len = dyn_len;
+
+ SIVAL(state->hdr, SMB2_HDR_PROTOCOL_ID, SMB2_MAGIC);
+ SSVAL(state->hdr, SMB2_HDR_LENGTH, SMB2_HDR_BODY);
+ SSVAL(state->hdr, SMB2_HDR_EPOCH, 1);
+ SIVAL(state->hdr, SMB2_HDR_STATUS, NT_STATUS_V(NT_STATUS_OK));
+ SSVAL(state->hdr, SMB2_HDR_OPCODE, cmd);
+ SSVAL(state->hdr, SMB2_HDR_CREDIT, 31);
+ SIVAL(state->hdr, SMB2_HDR_FLAGS, flags);
+ SIVAL(state->hdr, SMB2_HDR_PID, cli->smb2.pid);
+ SIVAL(state->hdr, SMB2_HDR_TID, cli->smb2.tid);
+ SBVAL(state->hdr, SMB2_HDR_SESSION_ID, cli->smb2.uid);
+
+ return result;
+}
+
+static void smb2cli_writev_done(struct tevent_req *subreq);
+
+NTSTATUS smb2cli_req_compound_submit(struct tevent_req **reqs,
+ int num_reqs)
+{
+ struct smb2cli_req_state *state;
+ struct tevent_req *subreq;
+ struct iovec *iov;
+ int i, num_iov, nbt_len;
+
+ /*
+ * 1 for the nbt length
+ * per request: HDR, fixed, dyn, padding
+ * -1 because the last one does not need padding
+ */
+
+ iov = talloc_array(reqs[0], struct iovec, 1 + 4*num_reqs - 1);
+ if (iov == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ num_iov = 1;
+ nbt_len = 0;
+
+ for (i=0; i<num_reqs; i++) {
+ size_t reqlen;
+ bool ret;
+
+ state = tevent_req_data(reqs[i], struct smb2cli_req_state);
+
+ SBVAL(state->hdr, SMB2_HDR_MESSAGE_ID, state->cli->smb2.mid++);
+
+ iov[num_iov].iov_base = state->hdr;
+ iov[num_iov].iov_len = sizeof(state->hdr);
+ num_iov += 1;
+
+ iov[num_iov].iov_base = discard_const(state->fixed);
+ iov[num_iov].iov_len = state->fixed_len;
+ num_iov += 1;
+
+ if (state->dyn != NULL) {
+ iov[num_iov].iov_base = discard_const(state->dyn);
+ iov[num_iov].iov_len = state->dyn_len;
+ num_iov += 1;
+ }
+
+ reqlen = sizeof(state->hdr) + state->fixed_len +
+ state->dyn_len;
+
+ if (i < num_reqs-1) {
+ if ((reqlen % 8) > 0) {
+ uint8_t pad = 8 - (reqlen % 8);
+ iov[num_iov].iov_base = state->pad;
+ iov[num_iov].iov_len = pad;
+ num_iov += 1;
+ reqlen += pad;
+ }
+ SIVAL(state->hdr, SMB2_HDR_NEXT_COMMAND, reqlen);
+ }
+ nbt_len += reqlen;
+
+ ret = smb2cli_req_set_pending(reqs[i]);
+ if (!ret) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ /*
+ * TODO: Do signing here
+ */
+
+ state = tevent_req_data(reqs[0], struct smb2cli_req_state);
+ _smb_setlen_large(state->nbt, nbt_len);
+ iov[0].iov_base = state->nbt;
+ iov[0].iov_len = sizeof(state->nbt);
+
+ subreq = writev_send(state, state->ev, state->cli->outgoing,
+ state->cli->fd, false, iov, num_iov);
+ if (subreq == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ tevent_req_set_callback(subreq, smb2cli_writev_done, reqs[0]);
+ return NT_STATUS_OK;
+}
+
+struct tevent_req *smb2cli_req_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t cmd,
+ uint32_t flags,
+ const uint8_t *fixed,
+ uint16_t fixed_len,
+ const uint8_t *dyn,
+ uint16_t dyn_len)
+{
+ struct tevent_req *req;
+ NTSTATUS status;
+
+ req = smb2cli_req_create(mem_ctx, ev, cli, cmd, flags,
+ fixed, fixed_len, dyn, dyn_len);
+ if (req == NULL) {
+ return NULL;
+ }
+ if (!tevent_req_is_in_progress(req)) {
+ return req;
+ }
+ status = smb2cli_req_compound_submit(&req, 1);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+ return req;
+}
+
+static void smb2cli_writev_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct smb2cli_req_state *state =
+ tevent_req_data(req,
+ struct smb2cli_req_state);
+ ssize_t nwritten;
+ int err;
+
+ nwritten = writev_recv(subreq, &err);
+ TALLOC_FREE(subreq);
+ if (nwritten == -1) {
+ if (state->cli->fd != -1) {
+ close(state->cli->fd);
+ state->cli->fd = -1;
+ }
+ tevent_req_nterror(req, map_nt_error_from_unix(err));
+ return;
+ }
+}
+
+static NTSTATUS smb2cli_inbuf_parse_compound(uint8_t *buf, TALLOC_CTX *mem_ctx,
+ struct iovec **piov, int *pnum_iov)
+{
+ struct iovec *iov;
+ int num_iov;
+ size_t buflen;
+ size_t taken;
+
+ num_iov = 1;
+
+ iov = talloc_array(mem_ctx, struct iovec, num_iov);
+ if (iov == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ iov[0].iov_base = buf;
+ iov[0].iov_len = 4;
+
+ buflen = smb_len_large(buf) + 4;
+ taken = 4;
+
+ while (taken < buflen) {
+ size_t len = buflen - taken;
+ uint8_t *hdr = buf + taken;
+ struct iovec *cur;
+ size_t full_size;
+ size_t next_command_ofs;
+ uint16_t body_size;
+ struct iovec *iov_tmp;
+
+ /*
+ * We need the header plus the body length field
+ */
+
+ if (len < SMB2_HDR_BODY + 2) {
+ DEBUG(10, ("%d bytes left, expected at least %d\n",
+ (int)len, SMB2_HDR_BODY));
+ goto inval;
+ }
+ if (IVAL(hdr, 0) != SMB2_MAGIC) {
+ DEBUG(10, ("Got non-SMB2 PDU: %x\n",
+ IVAL(hdr, 0)));
+ goto inval;
+ }
+ if (SVAL(hdr, 4) != SMB2_HDR_BODY) {
+ DEBUG(10, ("Got HDR len %d, expected %d\n",
+ SVAL(hdr, 4), SMB2_HDR_BODY));
+ goto inval;
+ }
+
+ full_size = len;
+ next_command_ofs = IVAL(hdr, SMB2_HDR_NEXT_COMMAND);
+ body_size = SVAL(hdr, SMB2_HDR_BODY);
+
+ if (next_command_ofs != 0) {
+ if (next_command_ofs < (SMB2_HDR_BODY + 2)) {
+ goto inval;
+ }
+ if (next_command_ofs > full_size) {
+ goto inval;
+ }
+ full_size = next_command_ofs;
+ }
+ if (body_size < 2) {
+ goto inval;
+ }
+ body_size &= 0xfffe;
+
+ if (body_size > (full_size - SMB2_HDR_BODY)) {
+ goto inval;
+ }
+
+ iov_tmp = talloc_realloc(mem_ctx, iov, struct iovec,
+ num_iov + 3);
+ if (iov_tmp == NULL) {
+ TALLOC_FREE(iov);
+ return NT_STATUS_NO_MEMORY;
+ }
+ iov = iov_tmp;
+ cur = &iov[num_iov];
+ num_iov += 3;
+
+ cur[0].iov_base = hdr;
+ cur[0].iov_len = SMB2_HDR_BODY;
+ cur[1].iov_base = hdr + SMB2_HDR_BODY;
+ cur[1].iov_len = body_size;
+ cur[2].iov_base = hdr + SMB2_HDR_BODY + body_size;
+ cur[2].iov_len = full_size - (SMB2_HDR_BODY + body_size);
+
+ taken += full_size;
+ }
+
+ *piov = iov;
+ *pnum_iov = num_iov;
+ return NT_STATUS_OK;
+
+inval:
+ TALLOC_FREE(iov);
+ return NT_STATUS_INVALID_NETWORK_RESPONSE;
+}
+
+static struct tevent_req *cli_smb2_find_pending(struct cli_state *cli,
+ uint64_t mid)
+{
+ int num_pending = talloc_array_length(cli->pending);
+ int i;
+
+ for (i=0; i<num_pending; i++) {
+ struct tevent_req *req = cli->pending[i];
+ struct smb2cli_req_state *state =
+ tevent_req_data(req,
+ struct smb2cli_req_state);
+
+ if (mid == BVAL(state->hdr, SMB2_HDR_MESSAGE_ID)) {
+ return req;
+ }
+ }
+ return NULL;
+}
+
+static void smb2cli_inbuf_received(struct tevent_req *subreq)
+{
+ struct cli_state *cli =
+ tevent_req_callback_data(subreq,
+ struct cli_state);
+ struct tevent_req *req;
+ struct iovec *iov;
+ int i, num_iov;
+ NTSTATUS status;
+ uint8_t *inbuf;
+ ssize_t received;
+ int err;
+
+ received = read_smb_recv(subreq, talloc_tos(), &inbuf, &err);
+ TALLOC_FREE(subreq);
+ if (received == -1) {
+ if (cli->fd != -1) {
+ close(cli->fd);
+ cli->fd = -1;
+ }
+ status = map_nt_error_from_unix(err);
+ goto fail;
+ }
+
+ status = smb2cli_inbuf_parse_compound(inbuf, talloc_tos(),
+ &iov, &num_iov);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+ for (i=1; i<num_iov; i+=3) {
+ struct iovec *cur = &iov[i];
+ uint8_t *inhdr = (uint8_t *)cur[0].iov_base;
+ struct smb2cli_req_state *state;
+
+ req = cli_smb2_find_pending(
+ cli, BVAL(inhdr, SMB2_HDR_MESSAGE_ID));
+ if (req == NULL) {
+ /*
+ * oplock breaks ??
+ */
+ goto fail;
+ }
+ state = tevent_req_data(req, struct smb2cli_req_state);
+ if (i+3 >= num_iov) {
+ /* last in chain */
+ state->inbuf = inbuf;
+ }
+ state->recv_iov = cur;
+ tevent_req_done(req);
+ }
+ return;
+ fail:
+ /*
+ * Cancel all pending requests. We don't do a for-loop walking
+ * cli->pending because that array changes in
+ * cli_smb_req_destructor().
+ */
+ while (talloc_array_length(cli->pending) > 0) {
+ req = cli->pending[0];
+ talloc_set_destructor(req, NULL);
+ smb2cli_req_destructor(req);
+ tevent_req_nterror(req, status);
+ }
+}
+
+NTSTATUS smb2cli_req_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ struct iovec **piov, int body_size)
+{
+ struct smb2cli_req_state *state =
+ tevent_req_data(req,
+ struct smb2cli_req_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ if (body_size != 0) {
+ if (body_size != SVAL(state->recv_iov[1].iov_base, 0)) {
+ return NT_STATUS_INVALID_NETWORK_RESPONSE;
+ }
+ }
+ talloc_steal(req, state->inbuf);
+ if (piov != NULL) {
+ *piov = state->recv_iov;
+ }
+
+ return NT_STATUS(IVAL(state->recv_iov[0].iov_base, SMB2_HDR_STATUS));
+}
+
+uint8_t *smb2cli_req_inbuf(struct tevent_req *req)
+{
+ struct smb2cli_req_state *state = tevent_req_data(
+ req, struct smb2cli_req_state);
+
+ return state->inbuf;
+}