s3:libsmb: we need to include "includes.h" as first header to let code build on all...
[samba.git] / source3 / libsmb / async_smb.c
index fc1c7f2f62d2bf9d246d387fec4ce885e09c1c2c..6edfe514b806b5c763e7a27d5580597ea80d3985 100644 (file)
 
 #include "includes.h"
 
+/*
+ * Read an smb packet asynchronously, discard keepalives
+ */
+
+struct read_smb_state {
+       struct tevent_context *ev;
+       int fd;
+       uint8_t *buf;
+};
+
+static ssize_t read_smb_more(uint8_t *buf, size_t buflen, void *private_data);
+static void read_smb_done(struct tevent_req *subreq);
+
+static struct tevent_req *read_smb_send(TALLOC_CTX *mem_ctx,
+                                       struct tevent_context *ev,
+                                       int fd)
+{
+       struct tevent_req *result, *subreq;
+       struct read_smb_state *state;
+
+       result = tevent_req_create(mem_ctx, &state, struct read_smb_state);
+       if (result == NULL) {
+               return NULL;
+       }
+       state->ev = ev;
+       state->fd = fd;
+
+       subreq = read_packet_send(state, ev, fd, 4, read_smb_more, NULL);
+       if (subreq == NULL) {
+               goto fail;
+       }
+       tevent_req_set_callback(subreq, read_smb_done, result);
+       return result;
+ fail:
+       TALLOC_FREE(result);
+       return NULL;
+}
+
+static ssize_t read_smb_more(uint8_t *buf, size_t buflen, void *private_data)
+{
+       if (buflen > 4) {
+               return 0;       /* We've been here, we're done */
+       }
+       return smb_len_large(buf);
+}
+
+static void read_smb_done(struct tevent_req *subreq)
+{
+       struct tevent_req *req = tevent_req_callback_data(
+               subreq, struct tevent_req);
+       struct read_smb_state *state = tevent_req_data(
+               req, struct read_smb_state);
+       ssize_t len;
+       int err;
+
+       len = read_packet_recv(subreq, state, &state->buf, &err);
+       TALLOC_FREE(subreq);
+       if (len == -1) {
+               tevent_req_error(req, err);
+               return;
+       }
+
+       if (CVAL(state->buf, 0) == SMBkeepalive) {
+               subreq = read_packet_send(state, state->ev, state->fd, 4,
+                                         read_smb_more, NULL);
+               if (tevent_req_nomem(subreq, req)) {
+                       return;
+               }
+               tevent_req_set_callback(subreq, read_smb_done, req);
+               return;
+       }
+       tevent_req_done(req);
+}
+
+static ssize_t read_smb_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+                            uint8_t **pbuf, int *perrno)
+{
+       struct read_smb_state *state = tevent_req_data(
+               req, struct read_smb_state);
+
+       if (tevent_req_is_unix_error(req, perrno)) {
+               return -1;
+       }
+       *pbuf = talloc_move(mem_ctx, &state->buf);
+       return talloc_get_size(*pbuf);
+}
+
 /**
  * Fetch an error out of a NBT packet
  * @param[in] buf      The SMB packet
@@ -300,7 +387,7 @@ static uint16_t cli_alloc_mid(struct cli_state *cli)
                int i;
 
                result = cli->mid++;
-               if (result == 0) {
+               if ((result == 0) || (result == 0xffff)) {
                        continue;
                }
 
@@ -496,11 +583,11 @@ struct tevent_req *cli_smb_req_create(TALLOC_CTX *mem_ctx,
 
        SSVAL(state->bytecount_buf, 0, iov_len(bytes_iov, iov_count));
 
-       state->iov[0].iov_base = state->header;
+       state->iov[0].iov_base = (void *)state->header;
        state->iov[0].iov_len  = sizeof(state->header);
-       state->iov[1].iov_base = state->vwv;
+       state->iov[1].iov_base = (void *)state->vwv;
        state->iov[1].iov_len  = wct * sizeof(uint16_t);
-       state->iov[2].iov_base = state->bytecount_buf;
+       state->iov[2].iov_base = (void *)state->bytecount_buf;
        state->iov[2].iov_len  = sizeof(uint16_t);
 
        if (iov_count != 0) {
@@ -512,8 +599,8 @@ struct tevent_req *cli_smb_req_create(TALLOC_CTX *mem_ctx,
        return result;
 }
 
-static bool cli_signv(struct cli_state *cli, struct iovec *iov, int count,
-                     uint32_t *seqnum)
+static NTSTATUS cli_signv(struct cli_state *cli, struct iovec *iov, int count,
+                         uint32_t *seqnum)
 {
        uint8_t *buf;
 
@@ -523,31 +610,36 @@ static bool cli_signv(struct cli_state *cli, struct iovec *iov, int count,
         */
 
        if ((count <= 0) || (iov[0].iov_len < smb_wct)) {
-               return false;
+               return NT_STATUS_INVALID_PARAMETER;
        }
 
        buf = iov_concat(talloc_tos(), iov, count);
        if (buf == NULL) {
-               return false;
+               return NT_STATUS_NO_MEMORY;
        }
 
        cli_calculate_sign_mac(cli, (char *)buf, seqnum);
        memcpy(iov[0].iov_base, buf, iov[0].iov_len);
 
        TALLOC_FREE(buf);
-       return true;
+       return NT_STATUS_OK;
 }
 
 static void cli_smb_sent(struct tevent_req *subreq);
 
-static bool cli_smb_req_iov_send(struct tevent_req *req,
-                                struct cli_smb_state *state,
-                                struct iovec *iov, int iov_count)
+static NTSTATUS cli_smb_req_iov_send(struct tevent_req *req,
+                                    struct cli_smb_state *state,
+                                    struct iovec *iov, int iov_count)
 {
        struct tevent_req *subreq;
+       NTSTATUS status;
+
+       if (state->cli->fd == -1) {
+               return NT_STATUS_CONNECTION_INVALID;
+       }
 
        if (iov[0].iov_len < smb_wct) {
-               return false;
+               return NT_STATUS_INVALID_PARAMETER;
        }
 
        if (state->mid != 0) {
@@ -558,17 +650,18 @@ static bool cli_smb_req_iov_send(struct tevent_req *req,
 
        smb_setlen((char *)iov[0].iov_base, iov_len(iov, iov_count) - 4);
 
-       if (!cli_signv(state->cli, iov, iov_count, &state->seqnum)) {
-               return false;
+       status = cli_signv(state->cli, iov, iov_count, &state->seqnum);
+
+       if (!NT_STATUS_IS_OK(status)) {
+               return status;
        }
 
        if (cli_encryption_on(state->cli)) {
-               NTSTATUS status;
                char *buf, *enc_buf;
 
                buf = (char *)iov_concat(talloc_tos(), iov, iov_count);
                if (buf == NULL) {
-                       return false;
+                       return NT_STATUS_NO_MEMORY;
                }
                status = cli_encrypt_message(state->cli, (char *)buf,
                                             &enc_buf);
@@ -576,30 +669,30 @@ static bool cli_smb_req_iov_send(struct tevent_req *req,
                if (!NT_STATUS_IS_OK(status)) {
                        DEBUG(0, ("Error in encrypting client message: %s\n",
                                  nt_errstr(status)));
-                       return false;
+                       return status;
                }
                buf = (char *)talloc_memdup(state, enc_buf,
                                            smb_len(enc_buf)+4);
                SAFE_FREE(enc_buf);
                if (buf == NULL) {
-                       return false;
+                       return NT_STATUS_NO_MEMORY;
                }
-               iov[0].iov_base = buf;
+               iov[0].iov_base = (void *)buf;
                iov[0].iov_len = talloc_get_size(buf);
                subreq = writev_send(state, state->ev, state->cli->outgoing,
-                                    state->cli->fd, iov, 1);
+                                    state->cli->fd, false, iov, 1);
        } else {
                subreq = writev_send(state, state->ev, state->cli->outgoing,
-                                    state->cli->fd, iov, iov_count);
+                                    state->cli->fd, false, iov, iov_count);
        }
        if (subreq == NULL) {
-               return false;
+               return NT_STATUS_NO_MEMORY;
        }
        tevent_req_set_callback(subreq, cli_smb_sent, req);
-       return true;
+       return NT_STATUS_OK;
 }
 
-bool cli_smb_req_send(struct tevent_req *req)
+NTSTATUS cli_smb_req_send(struct tevent_req *req)
 {
        struct cli_smb_state *state = tevent_req_data(
                req, struct cli_smb_state);
@@ -618,8 +711,9 @@ struct tevent_req *cli_smb_send(TALLOC_CTX *mem_ctx,
 {
        struct tevent_req *req;
        struct iovec iov;
+       NTSTATUS status;
 
-       iov.iov_base = CONST_DISCARD(char *, bytes);
+       iov.iov_base = CONST_DISCARD(void *, bytes);
        iov.iov_len = num_bytes;
 
        req = cli_smb_req_create(mem_ctx, ev, cli, smb_command,
@@ -627,8 +721,11 @@ struct tevent_req *cli_smb_send(TALLOC_CTX *mem_ctx,
        if (req == NULL) {
                return NULL;
        }
-       if (!cli_smb_req_send(req)) {
-               TALLOC_FREE(req);
+
+       status = cli_smb_req_send(req);
+       if (!NT_STATUS_IS_OK(status)) {
+               tevent_req_nterror(req, status);
+               return tevent_req_post(req, ev);
        }
        return req;
 }
@@ -645,6 +742,10 @@ static void cli_smb_sent(struct tevent_req *subreq)
        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;
        }
@@ -685,10 +786,15 @@ static void cli_smb_received(struct tevent_req *subreq)
        int num_pending;
        int i, err;
        uint16_t mid;
+       bool oplock_break;
 
        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;
        }
@@ -741,11 +847,31 @@ static void cli_smb_received(struct tevent_req *subreq)
                goto done;
        }
 
+       oplock_break = false;
+
+       if (mid == 0xffff) {
+               /*
+                * Paranoia checks that this is really an oplock break request.
+                */
+               oplock_break = (smb_len(inbuf) == 51); /* hdr + 8 words */
+               oplock_break &= ((CVAL(inbuf, smb_flg) & FLAG_REPLY) == 0);
+               oplock_break &= (CVAL(inbuf, smb_com) == SMBlockingX);
+               oplock_break &= (SVAL(inbuf, smb_vwv6) == 0);
+               oplock_break &= (SVAL(inbuf, smb_vwv7) == 0);
+
+               if (!oplock_break) {
+                       /* Dump unexpected reply */
+                       TALLOC_FREE(inbuf);
+                       goto done;
+               }
+       }
+
        req = cli->pending[i];
        state = tevent_req_data(req, struct cli_smb_state);
        ev = state->ev;
 
-       if (!cli_check_sign_mac(cli, (char *)inbuf, state->seqnum+1)) {
+       if (!oplock_break /* oplock breaks are not signed */
+           && !cli_check_sign_mac(cli, (char *)inbuf, state->seqnum+1)) {
                DEBUG(10, ("cli_check_sign_mac failed\n"));
                TALLOC_FREE(inbuf);
                status = NT_STATUS_ACCESS_DENIED;
@@ -920,7 +1046,7 @@ size_t cli_smb_wct_ofs(struct tevent_req **reqs, int num_reqs)
        return wct_ofs;
 }
 
-bool cli_smb_chain_send(struct tevent_req **reqs, int num_reqs)
+NTSTATUS cli_smb_chain_send(struct tevent_req **reqs, int num_reqs)
 {
        struct cli_smb_state *first_state = tevent_req_data(
                reqs[0], struct cli_smb_state);
@@ -932,6 +1058,7 @@ bool cli_smb_chain_send(struct tevent_req **reqs, int num_reqs)
        int i, iovlen;
        struct iovec *iov = NULL;
        struct iovec *this_iov;
+       NTSTATUS status;
 
        iovlen = 0;
        for (i=0; i<num_reqs; i++) {
@@ -941,13 +1068,14 @@ bool cli_smb_chain_send(struct tevent_req **reqs, int num_reqs)
 
        iov = talloc_array(last_state, struct iovec, iovlen);
        if (iov == NULL) {
-               goto fail;
+               return NT_STATUS_NO_MEMORY;
        }
 
        first_state->chained_requests = (struct tevent_req **)talloc_memdup(
                last_state, reqs, sizeof(*reqs) * num_reqs);
        if (first_state->chained_requests == NULL) {
-               goto fail;
+               TALLOC_FREE(iov);
+               return NT_STATUS_NO_MEMORY;
        }
 
        wct_offset = smb_wct - 4;
@@ -962,7 +1090,9 @@ bool cli_smb_chain_send(struct tevent_req **reqs, int num_reqs)
                if (i < num_reqs-1) {
                        if (!is_andx_req(CVAL(state->header, smb_com))
                            || CVAL(state->header, smb_wct) < 2) {
-                               goto fail;
+                               TALLOC_FREE(iov);
+                               TALLOC_FREE(first_state->chained_requests);
+                               return NT_STATUS_INVALID_PARAMETER;
                        }
                }
 
@@ -998,7 +1128,7 @@ bool cli_smb_chain_send(struct tevent_req **reqs, int num_reqs)
                         * last byte.
                         */
                        this_iov[0].iov_len = chain_padding+1;
-                       this_iov[0].iov_base = &state->header[
+                       this_iov[0].iov_base = (void *)&state->header[
                                sizeof(state->header) - this_iov[0].iov_len];
                        memset(this_iov[0].iov_base, 0, this_iov[0].iov_len-1);
                }
@@ -1008,13 +1138,14 @@ bool cli_smb_chain_send(struct tevent_req **reqs, int num_reqs)
                chain_padding = next_padding;
        }
 
-       if (!cli_smb_req_iov_send(reqs[0], last_state, iov, iovlen)) {
-               goto fail;
+       status = cli_smb_req_iov_send(reqs[0], last_state, iov, iovlen);
+       if (!NT_STATUS_IS_OK(status)) {
+               TALLOC_FREE(iov);
+               TALLOC_FREE(first_state->chained_requests);
+               return status;
        }
-       return true;
- fail:
-       TALLOC_FREE(iov);
-       return false;
+
+       return NT_STATUS_OK;
 }
 
 uint8_t *cli_smb_inbuf(struct tevent_req *req)
@@ -1029,3 +1160,82 @@ bool cli_has_async_calls(struct cli_state *cli)
        return ((tevent_queue_length(cli->outgoing) != 0)
                || (talloc_array_length(cli->pending) != 0));
 }
+
+struct cli_smb_oplock_break_waiter_state {
+       uint16_t fnum;
+       uint8_t level;
+};
+
+static void cli_smb_oplock_break_waiter_done(struct tevent_req *subreq);
+
+struct tevent_req *cli_smb_oplock_break_waiter_send(TALLOC_CTX *mem_ctx,
+                                                   struct event_context *ev,
+                                                   struct cli_state *cli)
+{
+       struct tevent_req *req, *subreq;
+       struct cli_smb_oplock_break_waiter_state *state;
+       struct cli_smb_state *smb_state;
+
+       req = tevent_req_create(mem_ctx, &state,
+                               struct cli_smb_oplock_break_waiter_state);
+       if (req == NULL) {
+               return NULL;
+       }
+
+       /*
+        * Create a fake SMB request that we will never send out. This is only
+        * used to be set into the pending queue with the right mid.
+        */
+       subreq = cli_smb_req_create(mem_ctx, ev, cli, 0, 0, 0, NULL, 0, NULL);
+       if (tevent_req_nomem(subreq, req)) {
+               return tevent_req_post(req, ev);
+       }
+       smb_state = tevent_req_data(subreq, struct cli_smb_state);
+       SSVAL(smb_state->header, smb_mid, 0xffff);
+
+       if (!cli_smb_req_set_pending(subreq)) {
+               tevent_req_nterror(req, NT_STATUS_NO_MEMORY);
+               return tevent_req_post(req, ev);
+       }
+       tevent_req_set_callback(subreq, cli_smb_oplock_break_waiter_done, req);
+       return req;
+}
+
+static void cli_smb_oplock_break_waiter_done(struct tevent_req *subreq)
+{
+       struct tevent_req *req = tevent_req_callback_data(
+               subreq, struct tevent_req);
+       struct cli_smb_oplock_break_waiter_state *state = tevent_req_data(
+               req, struct cli_smb_oplock_break_waiter_state);
+       uint8_t wct;
+       uint16_t *vwv;
+       uint32_t num_bytes;
+       uint8_t *bytes;
+       NTSTATUS status;
+
+       status = cli_smb_recv(subreq, 8, &wct, &vwv, &num_bytes, &bytes);
+       if (!NT_STATUS_IS_OK(status)) {
+               TALLOC_FREE(subreq);
+               tevent_req_nterror(req, status);
+               return;
+       }
+       state->fnum = SVAL(vwv+2, 0);
+       state->level = CVAL(vwv+3, 1);
+       tevent_req_done(req);
+}
+
+NTSTATUS cli_smb_oplock_break_waiter_recv(struct tevent_req *req,
+                                         uint16_t *pfnum,
+                                         uint8_t *plevel)
+{
+       struct cli_smb_oplock_break_waiter_state *state = tevent_req_data(
+               req, struct cli_smb_oplock_break_waiter_state);
+       NTSTATUS status;
+
+       if (tevent_req_is_nterror(req, &status)) {
+               return status;
+       }
+       *pfnum = state->fnum;
+       *plevel = state->level;
+       return NT_STATUS_OK;
+}