libcli/smb: copy smb2cli_req_* code to smbXcli_base.c
authorStefan Metzmacher <metze@samba.org>
Thu, 15 Sep 2011 09:47:11 +0000 (11:47 +0200)
committerStefan Metzmacher <metze@samba.org>
Thu, 24 Nov 2011 18:02:29 +0000 (19:02 +0100)
metze

libcli/smb/smbXcli_base.c
libcli/smb/smbXcli_base.h

index 2755218eb5ea68f93389cf8f39e4f1ae8b013f6e..57094bd9571badee146522d3d2899253d074647e 100644 (file)
@@ -1558,3 +1558,582 @@ bool smbXcli_conn_has_async_calls(struct smbXcli_conn *conn)
        return ((tevent_queue_length(conn->outgoing) != 0)
                || (talloc_array_length(conn->pending) != 0));
 }
+
+struct tevent_req *smb2cli_req_create(TALLOC_CTX *mem_ctx,
+                                     struct tevent_context *ev,
+                                     struct cli_state *cli,
+                                     uint16_t cmd,
+                                     uint32_t additional_flags,
+                                     uint32_t clear_flags,
+                                     unsigned int timeout,
+                                     uint32_t pid,
+                                     uint32_t tid,
+                                     uint64_t uid,
+                                     const uint8_t *fixed,
+                                     uint16_t fixed_len,
+                                     const uint8_t *dyn,
+                                     uint32_t dyn_len)
+{
+       struct tevent_req *req;
+       struct smb2cli_req_state *state;
+       uint32_t flags = 0;
+
+       req = tevent_req_create(mem_ctx, &state,
+                               struct smb2cli_req_state);
+       if (req == NULL) {
+               return NULL;
+       }
+       state->ev = ev;
+       state->cli = cli;
+
+       state->recv_iov = talloc_zero_array(state, struct iovec, 3);
+       if (state->recv_iov == NULL) {
+               TALLOC_FREE(req);
+               return NULL;
+       }
+
+       flags |= additional_flags;
+       flags &= ~clear_flags;
+
+       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,         pid);
+       SIVAL(state->hdr, SMB2_HDR_TID,         tid);
+       SBVAL(state->hdr, SMB2_HDR_SESSION_ID,  uid);
+
+       if (timeout > 0) {
+               struct timeval endtime;
+
+               endtime = timeval_current_ofs_msec(timeout);
+               if (!tevent_req_set_endtime(req, ev, endtime)) {
+                       return req;
+               }
+       }
+
+       return req;
+}
+
+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;
+               uint64_t mid;
+
+               if (!tevent_req_is_in_progress(reqs[i])) {
+                       return NT_STATUS_INTERNAL_ERROR;
+               }
+
+               state = tevent_req_data(reqs[i], struct smb2cli_req_state);
+
+               if (!cli_state_is_connected(state->cli)) {
+                       return NT_STATUS_CONNECTION_DISCONNECTED;
+               }
+
+               if (state->cli->smb2.mid == UINT64_MAX) {
+                       return NT_STATUS_CONNECTION_ABORTED;
+               }
+
+               mid = state->cli->smb2.mid;
+               state->cli->smb2.mid += 1;
+
+               SBVAL(state->hdr, SMB2_HDR_MESSAGE_ID, 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_tcp(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->conn.outgoing,
+                            state->cli->conn.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 additional_flags,
+                                   uint32_t clear_flags,
+                                   unsigned int timeout,
+                                   uint32_t pid,
+                                   uint32_t tid,
+                                   uint64_t uid,
+                                   const uint8_t *fixed,
+                                   uint16_t fixed_len,
+                                   const uint8_t *dyn,
+                                   uint32_t dyn_len)
+{
+       struct tevent_req *req;
+       NTSTATUS status;
+
+       req = smb2cli_req_create(mem_ctx, ev, cli, cmd,
+                                additional_flags, clear_flags,
+                                timeout,
+                                pid, tid, uid,
+                                fixed, fixed_len, dyn, dyn_len);
+       if (req == NULL) {
+               return NULL;
+       }
+       if (!tevent_req_is_in_progress(req)) {
+               return tevent_req_post(req, ev);
+       }
+       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) {
+               /* here, we need to notify all pending requests */
+               NTSTATUS status = map_nt_error_from_unix_common(err);
+               smb2cli_notify_pending(state->cli, status);
+               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;
+       uint8_t *first_hdr;
+
+       num_iov = 0;
+
+       iov = talloc_array(mem_ctx, struct iovec, num_iov);
+       if (iov == NULL) {
+               return NT_STATUS_NO_MEMORY;
+       }
+
+       buflen = smb_len_tcp(buf);
+       taken = 0;
+       first_hdr = buf + NBT_HDR_SIZE;
+
+       while (taken < buflen) {
+               size_t len = buflen - taken;
+               uint8_t *hdr = first_hdr + 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->conn.pending);
+       int i;
+
+       for (i=0; i<num_pending; i++) {
+               struct tevent_req *req = cli->conn.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);
+       TALLOC_CTX *frame = talloc_stackframe();
+       struct tevent_req *req;
+       struct smb2cli_req_state *state = NULL;
+       struct iovec *iov;
+       int i, num_iov;
+       NTSTATUS status;
+       uint8_t *inbuf;
+       ssize_t received;
+       int err;
+       size_t num_pending;
+       bool defer = true;
+
+       received = read_smb_recv(subreq, frame, &inbuf, &err);
+       TALLOC_FREE(subreq);
+       if (received == -1) {
+               /*
+                * We need to close the connection and notify
+                * all pending requests.
+                */
+               status = map_nt_error_from_unix_common(err);
+               smb2cli_notify_pending(cli, status);
+               TALLOC_FREE(frame);
+               return;
+       }
+
+       status = smb2cli_inbuf_parse_compound(inbuf, frame,
+                                             &iov, &num_iov);
+       if (!NT_STATUS_IS_OK(status)) {
+               /*
+                * if we cannot parse the incoming pdu,
+                * the connection becomes unusable.
+                *
+                * We need to close the connection and notify
+                * all pending requests.
+                */
+               smb2cli_notify_pending(cli, status);
+               TALLOC_FREE(frame);
+               return;
+       }
+
+       for (i=0; i<num_iov; i+=3) {
+               uint8_t *inbuf_ref = NULL;
+               struct iovec *cur = &iov[i];
+               uint8_t *inhdr = (uint8_t *)cur[0].iov_base;
+               uint16_t opcode = SVAL(inhdr, SMB2_HDR_OPCODE);
+               uint32_t flags = IVAL(inhdr, SMB2_HDR_FLAGS);
+               uint64_t mid = BVAL(inhdr, SMB2_HDR_MESSAGE_ID);
+               uint16_t req_opcode;
+
+               req = cli_smb2_find_pending(cli, mid);
+               if (req == NULL) {
+                       /*
+                        * TODO: handle oplock breaks and async responses
+                        */
+
+                       /*
+                        * We need to close the connection and notify
+                        * all pending requests.
+                        */
+                       status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+                       smb2cli_notify_pending(cli, status);
+                       TALLOC_FREE(frame);
+                       return;
+               }
+               state = tevent_req_data(req, struct smb2cli_req_state);
+
+               req_opcode = SVAL(state->hdr, SMB2_HDR_OPCODE);
+               if (opcode != req_opcode) {
+                       status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+                       smb2cli_notify_pending(cli, status);
+                       TALLOC_FREE(frame);
+                       return;
+               }
+
+               if (!(flags & SMB2_HDR_FLAG_REDIRECT)) {
+                       status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+                       smb2cli_notify_pending(cli, status);
+                       TALLOC_FREE(frame);
+                       return;
+               }
+
+               status = NT_STATUS(IVAL(inhdr, SMB2_HDR_STATUS));
+               if ((flags & SMB2_HDR_FLAG_ASYNC) &&
+                   NT_STATUS_EQUAL(status, STATUS_PENDING)) {
+                       uint32_t req_flags = IVAL(state->hdr, SMB2_HDR_FLAGS);
+                       uint64_t async_id = BVAL(inhdr, SMB2_HDR_ASYNC_ID);
+
+                       req_flags |= SMB2_HDR_FLAG_ASYNC;
+                       SBVAL(state->hdr, SMB2_HDR_FLAGS, req_flags);
+                       SBVAL(state->hdr, SMB2_HDR_ASYNC_ID, async_id);
+                       continue;
+               }
+
+               smb2cli_req_unset_pending(req);
+
+               /*
+                * There might be more than one response
+                * we need to defer the notifications
+                */
+               if ((num_iov == 4) && (talloc_array_length(cli->conn.pending) == 0)) {
+                       defer = false;
+               }
+
+               if (defer) {
+                       tevent_req_defer_callback(req, state->ev);
+               }
+
+               /*
+                * Note: here we use talloc_reference() in a way
+                *       that does not expose it to the caller.
+                */
+               inbuf_ref = talloc_reference(state->recv_iov, inbuf);
+               if (tevent_req_nomem(inbuf_ref, req)) {
+                       continue;
+               }
+
+               /* copy the related buffers */
+               state->recv_iov[0] = cur[0];
+               state->recv_iov[1] = cur[1];
+               state->recv_iov[2] = cur[2];
+
+               tevent_req_done(req);
+       }
+
+       TALLOC_FREE(frame);
+
+       if (!defer) {
+               return;
+       }
+
+       num_pending = talloc_array_length(cli->conn.pending);
+       if (num_pending == 0) {
+               if (state->cli->smb2.mid < UINT64_MAX) {
+                       /* no more pending requests, so we are done for now */
+                       return;
+               }
+
+               /*
+                * If there are no more requests possible,
+                * because we are out of message ids,
+                * we need to disconnect.
+                */
+               smb2cli_notify_pending(cli, NT_STATUS_CONNECTION_ABORTED);
+               return;
+       }
+       req = cli->conn.pending[0];
+       state = tevent_req_data(req, struct smb2cli_req_state);
+
+       /*
+        * add the read_smb request that waits for the
+        * next answer from the server
+        */
+       subreq = read_smb_send(cli->conn.pending, state->ev, cli->conn.fd);
+       if (subreq == NULL) {
+               smb2cli_notify_pending(cli, NT_STATUS_NO_MEMORY);
+               return;
+       }
+       tevent_req_set_callback(subreq, smb2cli_inbuf_received, cli);
+}
+
+NTSTATUS smb2cli_req_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+                         struct iovec **piov,
+                         const struct smb2cli_req_expected_response *expected,
+                         size_t num_expected)
+{
+       struct smb2cli_req_state *state =
+               tevent_req_data(req,
+               struct smb2cli_req_state);
+       NTSTATUS status;
+       size_t body_size;
+       bool found_status = false;
+       bool found_size = false;
+       size_t i;
+
+       if (piov != NULL) {
+               *piov = NULL;
+       }
+
+       if (tevent_req_is_nterror(req, &status)) {
+               for (i=0; i < num_expected; i++) {
+                       if (NT_STATUS_EQUAL(status, expected[i].status)) {
+                               found_status = true;
+                               break;
+                       }
+               }
+
+               if (found_status) {
+                       return NT_STATUS_UNEXPECTED_NETWORK_ERROR;
+               }
+
+               return status;
+       }
+
+       if (num_expected == 0) {
+               found_status = true;
+               found_size = true;
+       }
+
+       status = NT_STATUS(IVAL(state->recv_iov[0].iov_base, SMB2_HDR_STATUS));
+       body_size = SVAL(state->recv_iov[1].iov_base, 0);
+
+       for (i=0; i < num_expected; i++) {
+               if (!NT_STATUS_EQUAL(status, expected[i].status)) {
+                       continue;
+               }
+
+               found_status = true;
+               if (expected[i].body_size == 0) {
+                       found_size = true;
+                       break;
+               }
+
+               if (expected[i].body_size == body_size) {
+                       found_size = true;
+                       break;
+               }
+       }
+
+       if (!found_status) {
+               return status;
+       }
+
+       if (!found_size) {
+               return NT_STATUS_INVALID_NETWORK_RESPONSE;
+       }
+
+       if (piov != NULL) {
+               *piov = talloc_move(mem_ctx, &state->recv_iov);
+       }
+
+       return status;
+}
index 3f39be8c2dc58badc43db2f860b45f33cb8836e6..6054a1a3ae10e2b6aa1e841649f07da81097ffda 100644 (file)
@@ -103,4 +103,45 @@ NTSTATUS smb1cli_req_recv(struct tevent_req *req,
                          uint8_t min_wct, uint8_t *pwct, uint16_t **pvwv,
                          uint32_t *pnum_bytes, uint8_t **pbytes);
 
+struct tevent_req *smb2cli_req_create(TALLOC_CTX *mem_ctx,
+                                     struct tevent_context *ev,
+                                     struct cli_state *cli,
+                                     uint16_t cmd,
+                                     uint32_t additional_flags,
+                                     uint32_t clear_flags,
+                                     unsigned int timeout,
+                                     uint32_t pid,
+                                     uint32_t tid,
+                                     uint64_t uid,
+                                     const uint8_t *fixed,
+                                     uint16_t fixed_len,
+                                     const uint8_t *dyn,
+                                     uint32_t dyn_len);
+NTSTATUS smb2cli_req_compound_submit(struct tevent_req **reqs,
+                                    int num_reqs);
+
+struct smb2cli_req_expected_response {
+       NTSTATUS status;
+       uint16_t body_size;
+};
+
+struct tevent_req *smb2cli_req_send(TALLOC_CTX *mem_ctx,
+                                   struct tevent_context *ev,
+                                   struct cli_state *cli,
+                                   uint16_t cmd,
+                                   uint32_t additional_flags,
+                                   uint32_t clear_flags,
+                                   unsigned int timeout,
+                                   uint32_t pid,
+                                   uint32_t tid,
+                                   uint64_t uid,
+                                   const uint8_t *fixed,
+                                   uint16_t fixed_len,
+                                   const uint8_t *dyn,
+                                   uint32_t dyn_len);
+NTSTATUS smb2cli_req_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+                         struct iovec **piov,
+                         const struct smb2cli_req_expected_response *expected,
+                         size_t num_expected);
+
 #endif /* _SMBXCLI_BASE_H_ */