summaryrefslogtreecommitdiff
path: root/libcli/smb/smbXcli_base.c
diff options
context:
space:
mode:
authorStefan Metzmacher <metze@samba.org>2011-09-15 11:47:11 +0200
committerStefan Metzmacher <metze@samba.org>2011-11-24 19:02:29 +0100
commit21b5f1c18573d8447d6e3fbda3018b72f65a4494 (patch)
treedab3ba3ba9bcc00d9e4b2e076983a18fbe307fe2 /libcli/smb/smbXcli_base.c
parent26892a9783756a13c2d86b642bb83768dbcca926 (diff)
downloadsamba-21b5f1c18573d8447d6e3fbda3018b72f65a4494.tar.gz
samba-21b5f1c18573d8447d6e3fbda3018b72f65a4494.tar.bz2
samba-21b5f1c18573d8447d6e3fbda3018b72f65a4494.zip
libcli/smb: copy smb2cli_req_* code to smbXcli_base.c
metze
Diffstat (limited to 'libcli/smb/smbXcli_base.c')
-rw-r--r--libcli/smb/smbXcli_base.c579
1 files changed, 579 insertions, 0 deletions
diff --git a/libcli/smb/smbXcli_base.c b/libcli/smb/smbXcli_base.c
index 2755218eb5..57094bd957 100644
--- a/libcli/smb/smbXcli_base.c
+++ b/libcli/smb/smbXcli_base.c
@@ -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;
+}