updated comment based on MS-SMB2 docs
[kai/samba.git] / source / libcli / smb2 / create.c
index cca83a040c5a2cb931be396df3429bbf4b27def1..342a5193760d007766a9de2d4e673963c68059f4 100644 (file)
 
 #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
 */
-NTSTATUS smb2_create_blob_add(TALLOC_CTX *mem_ctx, DATA_BLOB *blob, 
-                             const char *tag,
-                             DATA_BLOB add, bool last)
+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 = blob->length;
-       size_t tag_length = strlen(tag);
-       uint8_t pad = smb2_padding_size(add.length+tag_length, 8);
-       if (!data_blob_realloc(mem_ctx, blob, 
-                              blob->length + 0x14 + tag_length + add.length + pad))
+       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(blob->data, ofs+0x00, 0);
+               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 {
-               SIVAL(blob->data, ofs+0x00, 0x14 + tag_length + add.length + pad);
+               b->blobs[b->num_blobs].data = data_blob(NULL, 0);
        }
-       SSVAL(blob->data, ofs+0x04, 0x10); /* offset of tag */
-       SIVAL(blob->data, ofs+0x06, tag_length); /* tag length */
-       SSVAL(blob->data, ofs+0x0A, 0x14 + tag_length); /* offset of data */
-       SIVAL(blob->data, ofs+0x0C, add.length);
-       memcpy(blob->data+ofs+0x10, tag, tag_length);
-       SIVAL(blob->data, ofs+0x10+tag_length, 0); /* pad? */
-       memcpy(blob->data+ofs+0x14+tag_length, add.data, add.length);
-       memset(blob->data+ofs+0x14+tag_length+add.length, 0, pad);
+
+       b->num_blobs += 1;
 
        return NT_STATUS_OK;
 }
@@ -62,7 +190,11 @@ struct smb2_request *smb2_create_send(struct smb2_tree *tree, struct smb2_create
 {
        struct smb2_request *req;
        NTSTATUS status;
-       DATA_BLOB blob = data_blob(NULL, 0);
+       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;
@@ -84,11 +216,13 @@ struct smb2_request *smb2_create_send(struct smb2_tree *tree, struct smb2_create
                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));
-               ea_put_list_chained(b.data, io->in.eas.num_eas, io->in.eas.eas);
-               status = smb2_create_blob_add(req, &blob, SMB2_CREATE_TAG_EXTA, b, false);
+                                              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;
@@ -98,19 +232,112 @@ struct smb2_request *smb2_create_send(struct smb2_tree *tree, struct smb2_create
 
        /* an empty MxAc tag seems to be used to ask the server to
           return the maximum access mask allowed on the file */
-       status = smb2_create_blob_add(req, &blob, SMB2_CREATE_TAG_MXAC, 
-                                     data_blob(NULL, 0), true);
+       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;
@@ -123,6 +350,8 @@ struct smb2_request *smb2_create_send(struct smb2_tree *tree, struct smb2_create
 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)) {
@@ -130,7 +359,7 @@ NTSTATUS smb2_create_recv(struct smb2_request *req, TALLOC_CTX *mem_ctx, struct
        }
 
        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);
@@ -143,12 +372,40 @@ NTSTATUS smb2_create_recv(struct smb2_request *req, TALLOC_CTX *mem_ctx, struct
        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, &io->out.blob);
+       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, 0);
+               }
+               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);
 }