s3:libsmb: allow store_cldap_reply() to work with a ipv6 response
[samba.git] / source3 / libsmb / cli_smb2_fnum.c
index e5d6e6b7fbda0e01cbfe019e4c781ee5608f0c26..08d95cf35798bc0647613efa6ebd3adfeed5ed1a 100644 (file)
@@ -42,6 +42,9 @@
 #include "librpc/gen_ndr/ndr_ioctl.h"
 #include "ntioctl.h"
 #include "librpc/gen_ndr/ndr_quota.h"
+#include "librpc/gen_ndr/ndr_smb3posix.h"
+#include "lib/util/string_wrappers.h"
+#include "lib/util/idtree.h"
 
 struct smb2_hnd {
        uint64_t fid_persistent;
@@ -140,11 +143,11 @@ static NTSTATUS delete_smb2_handle_mapping(struct cli_state *cli,
  Oplock mapping code.
 ***************************************************************/
 
-static uint8_t flags_to_smb2_oplock(uint32_t create_flags)
+static uint8_t flags_to_smb2_oplock(struct cli_smb2_create_flags create_flags)
 {
-       if (create_flags & REQUEST_BATCH_OPLOCK) {
+       if (create_flags.batch_oplock) {
                return SMB2_OPLOCK_LEVEL_BATCH;
-       } else if (create_flags & REQUEST_OPLOCK) {
+       } else if (create_flags.exclusive_oplock) {
                return SMB2_OPLOCK_LEVEL_EXCLUSIVE;
        }
 
@@ -152,6 +155,42 @@ static uint8_t flags_to_smb2_oplock(uint32_t create_flags)
        return SMB2_OPLOCK_LEVEL_NONE;
 }
 
+/***************************************************************
+ If we're on a DFS share, ensure we convert to a full DFS path
+ if this hasn't already been done.
+***************************************************************/
+
+static char *smb2_dfs_share_path(TALLOC_CTX *ctx,
+                                struct cli_state *cli,
+                                char *path)
+{
+       bool is_dfs = smbXcli_conn_dfs_supported(cli->conn) &&
+                       smbXcli_tcon_is_dfs_share(cli->smb2.tcon);
+       bool is_already_dfs_path = false;
+
+       if (!is_dfs) {
+               return path;
+       }
+       is_already_dfs_path = cli_dfs_is_already_full_path(cli, path);
+       if (is_already_dfs_path) {
+               return path;
+       }
+       if (path[0] == '\0') {
+               return talloc_asprintf(ctx,
+                                      "%s\\%s",
+                                       smbXcli_conn_remote_name(cli->conn),
+                                       cli->share);
+       }
+       while (*path == '\\') {
+               path++;
+       }
+       return talloc_asprintf(ctx,
+                              "%s\\%s\\%s",
+                              smbXcli_conn_remote_name(cli->conn),
+                              cli->share,
+                              path);
+}
+
 /***************************************************************
  Small wrapper that allows SMB2 create to return a uint16_t fnum.
 ***************************************************************/
@@ -161,6 +200,7 @@ struct cli_smb2_create_fnum_state {
        struct smb2_create_blobs in_cblobs;
        struct smb2_create_blobs out_cblobs;
        struct smb_create_returns cr;
+       struct symlink_reparse_struct *symlink;
        uint16_t fnum;
        struct tevent_req *subreq;
 };
@@ -172,8 +212,8 @@ struct tevent_req *cli_smb2_create_fnum_send(
        TALLOC_CTX *mem_ctx,
        struct tevent_context *ev,
        struct cli_state *cli,
-       const char *fname,
-       uint32_t create_flags,
+       const char *fname_in,
+       struct cli_smb2_create_flags create_flags,
        uint32_t impersonation_level,
        uint32_t desired_access,
        uint32_t file_attributes,
@@ -184,10 +224,10 @@ struct tevent_req *cli_smb2_create_fnum_send(
 {
        struct tevent_req *req, *subreq;
        struct cli_smb2_create_fnum_state *state;
+       char *fname = NULL;
        size_t fname_len = 0;
-       const char *startp = NULL;
-       const char *endp = NULL;
-       time_t tstamp = (time_t)0;
+       bool have_twrp;
+       NTTIME ntt;
        NTSTATUS status;
 
        req = tevent_req_create(mem_ctx, &state,
@@ -197,8 +237,8 @@ struct tevent_req *cli_smb2_create_fnum_send(
        }
        state->cli = cli;
 
-       if (smbXcli_conn_protocol(cli->conn) < PROTOCOL_SMB2_02) {
-               tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+       fname = talloc_strdup(state, fname_in);
+       if (tevent_req_nomem(fname, req)) {
                return tevent_req_post(req, ev);
        }
 
@@ -206,36 +246,36 @@ struct tevent_req *cli_smb2_create_fnum_send(
                create_options |= FILE_OPEN_FOR_BACKUP_INTENT;
        }
 
-       /* Check for @GMT- paths. Remove the @GMT and turn into TWrp if so. */
-       fname_len = strlen(fname);
-       if (clistr_is_previous_version_path(fname, &startp, &endp, &tstamp)) {
-               size_t len_before_gmt = startp - fname;
-               size_t len_after_gmt = fname + fname_len - endp;
-               DATA_BLOB twrp_blob;
-               NTTIME ntt;
-
-               char *new_fname = talloc_array(state, char,
-                               len_before_gmt + len_after_gmt + 1);
+       if (cli->smb2.client_smb311_posix) {
+               uint8_t modebuf[4] = {
+                       0,
+               };
 
-               if (tevent_req_nomem(new_fname, req)) {
+               status =
+                       smb2_create_blob_add(state,
+                                            &state->in_cblobs,
+                                            SMB2_CREATE_TAG_POSIX,
+                                            (DATA_BLOB){
+                                                    .data = modebuf,
+                                                    .length = sizeof(modebuf),
+                                            });
+               if (tevent_req_nterror(req, status)) {
                        return tevent_req_post(req, ev);
                }
+       }
 
-               memcpy(new_fname, fname, len_before_gmt);
-               memcpy(new_fname + len_before_gmt, endp, len_after_gmt + 1);
-               fname = new_fname;
-               fname_len = len_before_gmt + len_after_gmt;
-
-               unix_to_nt_time(&ntt, tstamp);
-               twrp_blob = data_blob_const((const void *)&ntt, 8);
-
+       /* Check for @GMT- paths. Remove the @GMT and turn into TWrp if so. */
+       have_twrp = clistr_smb2_extract_snapshot_token(fname, &ntt);
+       if (have_twrp) {
                status = smb2_create_blob_add(
                        state,
                        &state->in_cblobs,
                        SMB2_CREATE_TAG_TWRP,
-                       twrp_blob);
-               if (!NT_STATUS_IS_OK(status)) {
-                       tevent_req_nterror(req, status);
+                       (DATA_BLOB) {
+                               .data = (uint8_t *)&ntt,
+                               .length = sizeof(ntt),
+                       });
+               if (tevent_req_nterror(req, status)) {
                        return tevent_req_post(req, ev);
                }
        }
@@ -253,6 +293,12 @@ struct tevent_req *cli_smb2_create_fnum_send(
                }
        }
 
+       fname = smb2_dfs_share_path(state, cli, fname);
+       if (tevent_req_nomem(fname, req)) {
+               return tevent_req_post(req, ev);
+       }
+       fname_len = strlen(fname);
+
        /* SMB2 is pickier about pathnames. Ensure it doesn't
           start in a '\' */
        if (*fname == '\\') {
@@ -262,12 +308,7 @@ struct tevent_req *cli_smb2_create_fnum_send(
 
        /* Or end in a '\' */
        if (fname_len > 0 && fname[fname_len-1] == '\\') {
-               char *new_fname = talloc_strdup(state, fname);
-               if (tevent_req_nomem(new_fname, req)) {
-                       return tevent_req_post(req, ev);
-               }
-               new_fname[fname_len-1] = '\0';
-               fname = new_fname;
+               fname[fname_len-1] = '\0';
        }
 
        subreq = smb2cli_create_send(state, ev,
@@ -309,7 +350,8 @@ static void cli_smb2_create_fnum_done(struct tevent_req *subreq)
                &h.fid_persistent,
                &h.fid_volatile, &state->cr,
                state,
-               &state->out_cblobs);
+               &state->out_cblobs,
+               &state->symlink);
        TALLOC_FREE(subreq);
        if (tevent_req_nterror(req, status)) {
                return;
@@ -334,13 +376,18 @@ NTSTATUS cli_smb2_create_fnum_recv(
        uint16_t *pfnum,
        struct smb_create_returns *cr,
        TALLOC_CTX *mem_ctx,
-       struct smb2_create_blobs *out_cblobs)
+       struct smb2_create_blobs *out_cblobs,
+       struct symlink_reparse_struct **symlink)
 {
        struct cli_smb2_create_fnum_state *state = tevent_req_data(
                req, struct cli_smb2_create_fnum_state);
        NTSTATUS status;
 
        if (tevent_req_is_nterror(req, &status)) {
+               if (NT_STATUS_EQUAL(status, NT_STATUS_STOPPED_ON_SYMLINK) &&
+                   (symlink != NULL)) {
+                       *symlink = talloc_move(mem_ctx, &state->symlink);
+               }
                state->cli->raw_status = status;
                return status;
        }
@@ -364,7 +411,7 @@ NTSTATUS cli_smb2_create_fnum_recv(
 NTSTATUS cli_smb2_create_fnum(
        struct cli_state *cli,
        const char *fname,
-       uint32_t create_flags,
+       struct cli_smb2_create_flags create_flags,
        uint32_t impersonation_level,
        uint32_t desired_access,
        uint32_t file_attributes,
@@ -412,7 +459,8 @@ NTSTATUS cli_smb2_create_fnum(
        if (!tevent_req_poll_ntstatus(req, ev, &status)) {
                goto fail;
        }
-       status = cli_smb2_create_fnum_recv(req, pfid, cr, mem_ctx, out_cblobs);
+       status = cli_smb2_create_fnum_recv(
+               req, pfid, cr, mem_ctx, out_cblobs, NULL);
  fail:
        TALLOC_FREE(frame);
        return status;
@@ -433,7 +481,8 @@ static void cli_smb2_close_fnum_done(struct tevent_req *subreq);
 struct tevent_req *cli_smb2_close_fnum_send(TALLOC_CTX *mem_ctx,
                                            struct tevent_context *ev,
                                            struct cli_state *cli,
-                                           uint16_t fnum)
+                                           uint16_t fnum,
+                                           uint16_t flags)
 {
        struct tevent_req *req, *subreq;
        struct cli_smb2_close_fnum_state *state;
@@ -447,19 +496,19 @@ struct tevent_req *cli_smb2_close_fnum_send(TALLOC_CTX *mem_ctx,
        state->cli = cli;
        state->fnum = fnum;
 
-       if (smbXcli_conn_protocol(cli->conn) < PROTOCOL_SMB2_02) {
-               tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
-               return tevent_req_post(req, ev);
-       }
-
        status = map_fnum_to_smb2_handle(cli, fnum, &state->ph);
        if (tevent_req_nterror(req, status)) {
                return tevent_req_post(req, ev);
        }
 
-       subreq = smb2cli_close_send(state, ev, cli->conn, cli->timeout,
-                                   cli->smb2.session, cli->smb2.tcon,
-                                   0, state->ph->fid_persistent,
+       subreq = smb2cli_close_send(state,
+                                   ev,
+                                   cli->conn,
+                                   cli->timeout,
+                                   cli->smb2.session,
+                                   cli->smb2.tcon,
+                                   flags,
+                                   state->ph->fid_persistent,
                                    state->ph->fid_volatile);
        if (tevent_req_nomem(subreq, req)) {
                return tevent_req_post(req, ev);
@@ -521,7 +570,7 @@ NTSTATUS cli_smb2_close_fnum(struct cli_state *cli, uint16_t fnum)
        if (ev == NULL) {
                goto fail;
        }
-       req = cli_smb2_close_fnum_send(frame, ev, cli, fnum);
+       req = cli_smb2_close_fnum_send(frame, ev, cli, fnum, 0);
        if (req == NULL) {
                goto fail;
        }
@@ -671,11 +720,6 @@ struct tevent_req *cli_smb2_delete_on_close_send(TALLOC_CTX *mem_ctx,
        }
        state->cli = cli;
 
-       if (smbXcli_conn_protocol(cli->conn) < PROTOCOL_SMB2_02) {
-               tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
-               return tevent_req_post(req, ev);
-       }
-
        /*
         * setinfo on the handle with info_type SMB2_SETINFO_FILE (1),
         * level 13 (SMB_FILE_DISPOSITION_INFORMATION - 1000).
@@ -791,7 +835,7 @@ struct tevent_req *cli_smb2_mkdir_send(
                ev,                                /* ev */
                cli,                               /* cli */
                dname,                             /* fname */
-               0,                                 /* create_flags */
+               (struct cli_smb2_create_flags){0}, /* create_flags */
                SMB2_IMPERSONATION_IMPERSONATION,  /* impersonation_level */
                FILE_READ_ATTRIBUTES,              /* desired_access */
                FILE_ATTRIBUTE_DIRECTORY,          /* file_attributes */
@@ -814,15 +858,17 @@ static void cli_smb2_mkdir_opened(struct tevent_req *subreq)
        struct cli_smb2_mkdir_state *state = tevent_req_data(
                req, struct cli_smb2_mkdir_state);
        NTSTATUS status;
-       uint16_t fnum;
+       uint16_t fnum = 0xffff;
 
-       status = cli_smb2_create_fnum_recv(subreq, &fnum, NULL, NULL, NULL);
+       status = cli_smb2_create_fnum_recv(
+               subreq, &fnum, NULL, NULL, NULL, NULL);
        TALLOC_FREE(subreq);
        if (tevent_req_nterror(req, status)) {
                return;
        }
 
-       subreq = cli_smb2_close_fnum_send(state, state->ev, state->cli, fnum);
+       subreq =
+               cli_smb2_close_fnum_send(state, state->ev, state->cli, fnum, 0);
        if (tevent_req_nomem(subreq, req)) {
                return;
        }
@@ -873,17 +919,12 @@ struct tevent_req *cli_smb2_rmdir_send(
        state->dname = dname;
        state->in_cblobs = in_cblobs;
 
-       if (smbXcli_conn_protocol(cli->conn) < PROTOCOL_SMB2_02) {
-               tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
-               return tevent_req_post(req, ev);
-       }
-
        subreq = cli_smb2_create_fnum_send(
                state,
                state->ev,
                state->cli,
                state->dname,
-               0,                      /* create_flags */
+               (struct cli_smb2_create_flags){0},
                SMB2_IMPERSONATION_IMPERSONATION,
                DELETE_ACCESS,          /* desired_access */
                FILE_ATTRIBUTE_DIRECTORY, /* file attributes */
@@ -907,7 +948,7 @@ static void cli_smb2_rmdir_opened1(struct tevent_req *subreq)
        NTSTATUS status;
 
        status = cli_smb2_create_fnum_recv(
-               subreq, &state->fnum, NULL, NULL, NULL);
+               subreq, &state->fnum, NULL, NULL, NULL, NULL);
        TALLOC_FREE(subreq);
 
        if (NT_STATUS_EQUAL(status, NT_STATUS_STOPPED_ON_SYMLINK)) {
@@ -922,7 +963,7 @@ static void cli_smb2_rmdir_opened1(struct tevent_req *subreq)
                        state->ev,
                        state->cli,
                        state->dname,
-                       0,                      /* create_flags */
+                       (struct cli_smb2_create_flags){0},
                        SMB2_IMPERSONATION_IMPERSONATION,
                        DELETE_ACCESS,          /* desired_access */
                        FILE_ATTRIBUTE_DIRECTORY, /* file attributes */
@@ -960,7 +1001,7 @@ static void cli_smb2_rmdir_opened2(struct tevent_req *subreq)
        NTSTATUS status;
 
        status = cli_smb2_create_fnum_recv(
-               subreq, &state->fnum, NULL, NULL, NULL);
+               subreq, &state->fnum, NULL, NULL, NULL, NULL);
        TALLOC_FREE(subreq);
        if (tevent_req_nterror(req, status)) {
                return;
@@ -988,8 +1029,11 @@ static void cli_smb2_rmdir_disp_set(struct tevent_req *subreq)
         * Close the fd even if the set_disp failed
         */
 
-       subreq = cli_smb2_close_fnum_send(
-               state, state->ev, state->cli, state->fnum);
+       subreq = cli_smb2_close_fnum_send(state,
+                                         state->ev,
+                                         state->cli,
+                                         state->fnum,
+                                         0);
        if (tevent_req_nomem(subreq, req)) {
                return;
        }
@@ -1048,17 +1092,12 @@ struct tevent_req *cli_smb2_unlink_send(
        state->fname = fname;
        state->in_cblobs = in_cblobs;
 
-       if (smbXcli_conn_protocol(cli->conn) < PROTOCOL_SMB2_02) {
-               tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
-               return tevent_req_post(req, ev);
-       }
-
        subreq = cli_smb2_create_fnum_send(
                state,          /* mem_ctx */
                state->ev,      /* tevent_context */
                state->cli,     /* cli_struct */
                state->fname,   /* filename */
-               0,                      /* create_flags */
+               (struct cli_smb2_create_flags){0},
                SMB2_IMPERSONATION_IMPERSONATION,
                DELETE_ACCESS,          /* desired_access */
                FILE_ATTRIBUTE_NORMAL, /* file attributes */
@@ -1081,13 +1120,15 @@ static void cli_smb2_unlink_opened1(struct tevent_req *subreq)
                subreq, struct tevent_req);
        struct cli_smb2_unlink_state *state = tevent_req_data(
                req, struct cli_smb2_unlink_state);
-       uint16_t fnum;
+       uint16_t fnum = 0xffff;
        NTSTATUS status;
 
-       status = cli_smb2_create_fnum_recv(subreq, &fnum, NULL, NULL, NULL);
+       status = cli_smb2_create_fnum_recv(
+               subreq, &fnum, NULL, NULL, NULL, NULL);
        TALLOC_FREE(subreq);
 
-       if (NT_STATUS_EQUAL(status, NT_STATUS_STOPPED_ON_SYMLINK)) {
+       if (NT_STATUS_EQUAL(status, NT_STATUS_STOPPED_ON_SYMLINK) ||
+           NT_STATUS_EQUAL(status, NT_STATUS_IO_REPARSE_TAG_NOT_HANDLED)) {
                /*
                 * Naive option to match our SMB1 code. Assume the
                 * symlink path that tripped us up was the last
@@ -1099,7 +1140,7 @@ static void cli_smb2_unlink_opened1(struct tevent_req *subreq)
                        state->ev,      /* tevent_context */
                        state->cli,     /* cli_struct */
                        state->fname,   /* filename */
-                       0,                      /* create_flags */
+                       (struct cli_smb2_create_flags){0},
                        SMB2_IMPERSONATION_IMPERSONATION,
                        DELETE_ACCESS,          /* desired_access */
                        FILE_ATTRIBUTE_NORMAL, /* file attributes */
@@ -1121,7 +1162,8 @@ static void cli_smb2_unlink_opened1(struct tevent_req *subreq)
                return;
        }
 
-       subreq = cli_smb2_close_fnum_send(state, state->ev, state->cli, fnum);
+       subreq =
+               cli_smb2_close_fnum_send(state, state->ev, state->cli, fnum, 0);
        if (tevent_req_nomem(subreq, req)) {
                return;
        }
@@ -1134,16 +1176,18 @@ static void cli_smb2_unlink_opened2(struct tevent_req *subreq)
                subreq, struct tevent_req);
        struct cli_smb2_unlink_state *state = tevent_req_data(
                req, struct cli_smb2_unlink_state);
-       uint16_t fnum;
+       uint16_t fnum = 0xffff;
        NTSTATUS status;
 
-       status = cli_smb2_create_fnum_recv(subreq, &fnum, NULL, NULL, NULL);
+       status = cli_smb2_create_fnum_recv(
+               subreq, &fnum, NULL, NULL, NULL, NULL);
        TALLOC_FREE(subreq);
        if (tevent_req_nterror(req, status)) {
                return;
        }
 
-       subreq = cli_smb2_close_fnum_send(state, state->ev, state->cli, fnum);
+       subreq =
+               cli_smb2_close_fnum_send(state, state->ev, state->cli, fnum, 0);
        if (tevent_req_nomem(subreq, req)) {
                return;
        }
@@ -1161,6 +1205,109 @@ NTSTATUS cli_smb2_unlink_recv(struct tevent_req *req)
        return tevent_req_simple_recv_ntstatus(req);
 }
 
+/***************************************************************
+ Utility function to parse a SMB2_FIND_POSIX_INFORMATION reply.
+***************************************************************/
+
+static NTSTATUS parse_finfo_posix_info(const uint8_t *dir_data,
+                                      uint32_t dir_data_length,
+                                      struct file_info *finfo,
+                                      uint32_t *next_offset)
+{
+       struct smb3_file_posix_information info = {};
+       size_t consumed;
+       enum ndr_err_code ndr_err;
+       size_t namelen = 0;
+       size_t ret = 0;
+       uint32_t _next_offset = 0;
+
+       if (dir_data_length < 4) {
+               return NT_STATUS_INFO_LENGTH_MISMATCH;
+       }
+
+       _next_offset = IVAL(dir_data, 0);
+
+       if (_next_offset > dir_data_length) {
+               return NT_STATUS_INFO_LENGTH_MISMATCH;
+       }
+
+       if (_next_offset != 0) {
+               /* Ensure we only read what in this record. */
+               dir_data_length = _next_offset;
+       }
+
+       /*
+        * Skip NextEntryOffset and FileIndex
+        */
+       if (dir_data_length < 8) {
+               return NT_STATUS_INFO_LENGTH_MISMATCH;
+       }
+       dir_data += 8;
+       dir_data_length -= 8;
+
+       ndr_err = ndr_pull_struct_blob_noalloc(
+               dir_data,
+               dir_data_length,
+               &info,
+               (ndr_pull_flags_fn_t)ndr_pull_smb3_file_posix_information,
+               &consumed);
+       if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+               return ndr_map_error2ntstatus(ndr_err);
+       }
+       if (consumed > dir_data_length) {
+               return NT_STATUS_INFO_LENGTH_MISMATCH;
+       }
+       dir_data += consumed;
+       dir_data_length -= consumed;
+
+       finfo->btime_ts = interpret_long_date(info.creation_time);
+       finfo->atime_ts = interpret_long_date(info.last_access_time);
+       finfo->mtime_ts = interpret_long_date(info.last_write_time);
+       finfo->ctime_ts = interpret_long_date(info.change_time);
+       finfo->allocated_size = info.allocation_size;
+       finfo->size = info.end_of_file;
+       finfo->attr = info.file_attributes;
+       finfo->ino = info.inode;
+       finfo->st_ex_dev = info.device;
+       finfo->st_ex_nlink = info.cc.nlinks;
+       finfo->reparse_tag = info.cc.reparse_tag;
+       finfo->st_ex_mode = wire_perms_to_unix(info.cc.posix_perms);
+       sid_copy(&finfo->owner_sid, &info.cc.owner);
+       sid_copy(&finfo->group_sid, &info.cc.group);
+
+       if (dir_data_length < 4) {
+               return NT_STATUS_INFO_LENGTH_MISMATCH;
+       }
+       namelen = PULL_LE_U32(dir_data, 0);
+
+       dir_data += 4;
+       dir_data_length -= 4;
+
+       if (namelen > dir_data_length) {
+               return NT_STATUS_INFO_LENGTH_MISMATCH;
+       }
+
+       ret = pull_string_talloc(finfo,
+                                dir_data,
+                                FLAGS2_UNICODE_STRINGS,
+                                &finfo->name,
+                                dir_data,
+                                namelen,
+                                STR_UNICODE);
+       if (ret == (size_t)-1) {
+               /* Bad conversion. */
+               return NT_STATUS_INVALID_NETWORK_RESPONSE;
+       }
+
+       if (finfo->name == NULL) {
+               /* Bad conversion. */
+               return NT_STATUS_INVALID_NETWORK_RESPONSE;
+       }
+
+       *next_offset = _next_offset;
+       return NT_STATUS_OK;
+}
+
 /***************************************************************
  Utility function to parse a SMB2_FIND_ID_BOTH_DIRECTORY_INFO reply.
 ***************************************************************/
@@ -1193,10 +1340,10 @@ static NTSTATUS parse_finfo_id_both_directory_info(const uint8_t *dir_data,
                return NT_STATUS_INFO_LENGTH_MISMATCH;
        }
 
-       finfo->btime_ts = interpret_long_date((const char *)dir_data + 8);
-       finfo->atime_ts = interpret_long_date((const char *)dir_data + 16);
-       finfo->mtime_ts = interpret_long_date((const char *)dir_data + 24);
-       finfo->ctime_ts = interpret_long_date((const char *)dir_data + 32);
+       finfo->btime_ts = interpret_long_date(BVAL(dir_data, 8));
+       finfo->atime_ts = interpret_long_date(BVAL(dir_data, 16));
+       finfo->mtime_ts = interpret_long_date(BVAL(dir_data, 24));
+       finfo->ctime_ts = interpret_long_date(BVAL(dir_data, 32));
        finfo->size = IVAL2_TO_SMB_BIG_UINT(dir_data + 40, 0);
        finfo->allocated_size = IVAL2_TO_SMB_BIG_UINT(dir_data + 48, 0);
        finfo->attr = IVAL(dir_data + 56, 0);
@@ -1205,6 +1352,7 @@ static NTSTATUS parse_finfo_id_both_directory_info(const uint8_t *dir_data,
        if (namelen > (dir_data_length - 104)) {
                return NT_STATUS_INFO_LENGTH_MISMATCH;
        }
+       finfo->reparse_tag = IVAL(dir_data + 64, 0);
        slen = CVAL(dir_data + 68, 0);
        if (slen > 24) {
                return NT_STATUS_INFO_LENGTH_MISMATCH;
@@ -1278,213 +1426,298 @@ static bool windows_parent_dirname(TALLOC_CTX *mem_ctx,
        return true;
 }
 
-/***************************************************************
- Wrapper that allows SMB2 to list a directory.
- Synchronous only.
-***************************************************************/
+struct cli_smb2_list_dir_data {
+       uint8_t *data;
+       uint32_t length;
+};
+
+struct cli_smb2_list_state {
+       struct tevent_context *ev;
+       struct cli_state *cli;
+       const char *mask;
+
+       uint16_t fnum;
 
-NTSTATUS cli_smb2_list(struct cli_state *cli,
-                       const char *pathname,
-                       uint32_t attribute,
-                       NTSTATUS (*fn)(const char *,
-                               struct file_info *,
-                               const char *,
-                               void *),
-                       void *state)
-{
        NTSTATUS status;
-       uint16_t fnum = 0xffff;
-       char *parent_dir = NULL;
-       const char *mask = NULL;
-       struct smb2_hnd *ph = NULL;
-       bool processed_file = false;
-       TALLOC_CTX *frame = talloc_stackframe();
-       TALLOC_CTX *subframe = NULL;
-       bool mask_has_wild;
-       uint32_t max_trans;
-       uint32_t max_avail_len;
+       struct cli_smb2_list_dir_data *response;
+       uint32_t offset;
+       unsigned int info_level;
+};
+
+static void cli_smb2_list_opened(struct tevent_req *subreq);
+static void cli_smb2_list_done(struct tevent_req *subreq);
+static void cli_smb2_list_closed(struct tevent_req *subreq);
+
+struct tevent_req *cli_smb2_list_send(
+       TALLOC_CTX *mem_ctx,
+       struct tevent_context *ev,
+       struct cli_state *cli,
+       const char *pathname,
+       unsigned int info_level)
+{
+       struct tevent_req *req = NULL, *subreq = NULL;
+       struct cli_smb2_list_state *state = NULL;
+       char *parent = NULL;
        bool ok;
+       struct smb2_create_blobs *in_cblobs = NULL;
 
-       if (smbXcli_conn_has_async_calls(cli->conn)) {
-               /*
-                * Can't use sync call while an async call is in flight
-                */
-               status = NT_STATUS_INVALID_PARAMETER;
-               goto fail;
+       req = tevent_req_create(mem_ctx, &state, struct cli_smb2_list_state);
+       if (req == NULL) {
+               return NULL;
        }
+       state->ev = ev;
+       state->cli = cli;
+       state->status = NT_STATUS_OK;
+       state->info_level = info_level;
 
-       if (smbXcli_conn_protocol(cli->conn) < PROTOCOL_SMB2_02) {
-               status = NT_STATUS_INVALID_PARAMETER;
-               goto fail;
+       ok = windows_parent_dirname(state, pathname, &parent, &state->mask);
+       if (!ok) {
+               tevent_req_oom(req);
+               return tevent_req_post(req, ev);
        }
 
-       /* Get the directory name. */
-       if (!windows_parent_dirname(frame,
-                               pathname,
-                               &parent_dir,
-                               &mask)) {
-                status = NT_STATUS_NO_MEMORY;
-               goto fail;
-        }
+       if (smbXcli_conn_have_posix(cli->conn) &&
+               info_level == SMB2_FIND_POSIX_INFORMATION)
+       {
+               NTSTATUS status;
 
-       mask_has_wild = ms_has_wild(mask);
+               /* The mode MUST be 0 when opening an existing file/dir, and
+                * will be ignored by the server.
+                */
+               uint8_t linear_mode[4] = { 0 };
+               DATA_BLOB blob = { .data=linear_mode,
+                                  .length=sizeof(linear_mode) };
 
-       status = cli_smb2_create_fnum(cli,
-                       parent_dir,
-                       0,                      /* create_flags */
-                       SMB2_IMPERSONATION_IMPERSONATION,
-                       SEC_DIR_LIST|SEC_DIR_READ_ATTRIBUTE,/* desired_access */
-                       FILE_ATTRIBUTE_DIRECTORY, /* file attributes */
-                       FILE_SHARE_READ|FILE_SHARE_WRITE, /* share_access */
-                       FILE_OPEN,              /* create_disposition */
-                       FILE_DIRECTORY_FILE,    /* create_options */
-                       NULL,
-                       &fnum,
-                       NULL,
-                       NULL,
-                       NULL);
+               in_cblobs = talloc_zero(mem_ctx, struct smb2_create_blobs);
+               if (in_cblobs == NULL) {
+                       return NULL;
+               }
 
-       if (!NT_STATUS_IS_OK(status)) {
-               goto fail;
+               status = smb2_create_blob_add(in_cblobs, in_cblobs,
+                                             SMB2_CREATE_TAG_POSIX, blob);
+               if (tevent_req_nterror(req, status)) {
+                       tevent_req_nterror(req, status);
+                       return tevent_req_post(req, ev);
+               }
        }
 
-       status = map_fnum_to_smb2_handle(cli,
-                                       fnum,
-                                       &ph);
-       if (!NT_STATUS_IS_OK(status)) {
-               goto fail;
+       subreq = cli_smb2_create_fnum_send(
+               state,                                  /* mem_ctx */
+               ev,                                     /* ev */
+               cli,                                    /* cli */
+               parent,                                 /* fname */
+               (struct cli_smb2_create_flags){0},      /* create_flags */
+               SMB2_IMPERSONATION_IMPERSONATION,       /* impersonation_level */
+               SEC_DIR_LIST|SEC_DIR_READ_ATTRIBUTE,    /* desired_access */
+               FILE_ATTRIBUTE_DIRECTORY,               /* file_attributes */
+               FILE_SHARE_READ|FILE_SHARE_WRITE,       /* share_access */
+               FILE_OPEN,                              /* create_disposition */
+               FILE_DIRECTORY_FILE,                    /* create_options */
+               in_cblobs);                             /* in_cblobs */
+       TALLOC_FREE(in_cblobs);
+       if (tevent_req_nomem(subreq, req)) {
+               return tevent_req_post(req, ev);
+       }
+       tevent_req_set_callback(subreq, cli_smb2_list_opened, req);
+       return req;
+}
+
+static void cli_smb2_list_opened(struct tevent_req *subreq)
+{
+       struct tevent_req *req = tevent_req_callback_data(
+               subreq, struct tevent_req);
+       struct cli_smb2_list_state *state = tevent_req_data(
+               req, struct cli_smb2_list_state);
+       NTSTATUS status;
+
+       status = cli_smb2_create_fnum_recv(
+               subreq, &state->fnum, NULL, NULL, NULL, NULL);
+       TALLOC_FREE(subreq);
+       if (tevent_req_nterror(req, status)) {
+               return;
        }
 
        /*
-        * ideally, use the max transaction size, but don't send a request
-        * bigger than we have credits available for
+        * Make our caller get back to us via cli_smb2_list_recv(),
+        * triggering the smb2_query_directory_send()
         */
-       max_trans = smb2cli_conn_max_trans_size(cli->conn);
-       ok = smb2cli_conn_req_possible(cli->conn, &max_avail_len);
-       if (ok) {
-               max_trans = MIN(max_trans, max_avail_len);
-       }
-
-       do {
-               uint8_t *dir_data = NULL;
-               uint32_t dir_data_length = 0;
-               uint32_t next_offset = 0;
-               subframe = talloc_stackframe();
-
-               status = smb2cli_query_directory(cli->conn,
-                                       cli->timeout,
-                                       cli->smb2.session,
-                                       cli->smb2.tcon,
-                                       SMB2_FIND_ID_BOTH_DIRECTORY_INFO,
-                                       0,      /* flags */
-                                       0,      /* file_index */
-                                       ph->fid_persistent,
-                                       ph->fid_volatile,
-                                       mask,
-                                       max_trans,
-                                       subframe,
-                                       &dir_data,
-                                       &dir_data_length);
+       tevent_req_defer_callback(req, state->ev);
+       tevent_req_notify_callback(req);
+}
 
-               if (!NT_STATUS_IS_OK(status)) {
-                       if (NT_STATUS_EQUAL(status, STATUS_NO_MORE_FILES)) {
-                               break;
-                       }
-                       goto fail;
-               }
+static void cli_smb2_list_done(struct tevent_req *subreq)
+{
+       struct tevent_req *req = tevent_req_callback_data(
+               subreq, struct tevent_req);
+       struct cli_smb2_list_state *state = tevent_req_data(
+               req, struct cli_smb2_list_state);
+       struct cli_smb2_list_dir_data *response = NULL;
 
-               do {
-                       struct file_info *finfo = talloc_zero(subframe,
-                                                       struct file_info);
+       response = talloc(state, struct cli_smb2_list_dir_data);
+       if (tevent_req_nomem(response, req)) {
+               return;
+       }
 
-                       if (finfo == NULL) {
-                               status = NT_STATUS_NO_MEMORY;
-                               goto fail;
-                       }
+       state->status = smb2cli_query_directory_recv(
+               subreq, response, &response->data, &response->length);
+       TALLOC_FREE(subreq);
 
-                       status = parse_finfo_id_both_directory_info(dir_data,
-                                               dir_data_length,
-                                               finfo,
-                                               &next_offset);
+       if (NT_STATUS_IS_OK(state->status)) {
+               state->response = response;
+               state->offset = 0;
 
-                       if (!NT_STATUS_IS_OK(status)) {
-                               goto fail;
-                       }
+               tevent_req_defer_callback(req, state->ev);
+               tevent_req_notify_callback(req);
+               return;
+       }
 
-                       /* Protect against server attack. */
-                       status = is_bad_finfo_name(cli, finfo);
-                       if (!NT_STATUS_IS_OK(status)) {
-                               smbXcli_conn_disconnect(cli->conn, status);
-                               goto fail;
-                       }
+       TALLOC_FREE(response);
 
-                       if (dir_check_ftype(finfo->attr, attribute)) {
-                               /*
-                                * Only process if attributes match.
-                                * On SMB1 server does this, so on
-                                * SMB2 we need to emulate in the
-                                * client.
-                                *
-                                * https://bugzilla.samba.org/show_bug.cgi?id=10260
-                                */
-                               processed_file = true;
-
-                               status = fn(cli->dfs_mountpoint,
-                                       finfo,
-                                       pathname,
-                                       state);
-
-                               if (!NT_STATUS_IS_OK(status)) {
-                                       break;
-                               }
-                       }
+       subreq = cli_smb2_close_fnum_send(state,
+                                         state->ev,
+                                         state->cli,
+                                         state->fnum,
+                                         0);
+       if (tevent_req_nomem(subreq, req)) {
+               return;
+       }
+       tevent_req_set_callback(subreq, cli_smb2_list_closed, req);
+}
 
-                       TALLOC_FREE(finfo);
+static void cli_smb2_list_closed(struct tevent_req *subreq)
+{
+       NTSTATUS status = cli_smb2_close_fnum_recv(subreq);
+       tevent_req_simple_finish_ntstatus(subreq, status);
+}
 
-                       /* Move to next entry. */
-                       if (next_offset) {
-                               dir_data += next_offset;
-                               dir_data_length -= next_offset;
-                       }
-               } while (next_offset != 0);
-
-               TALLOC_FREE(subframe);
-
-               if (!mask_has_wild) {
-                       /*
-                        * MacOSX 10 doesn't set STATUS_NO_MORE_FILES
-                        * when handed a non-wildcard path. Do it
-                        * for the server (with a non-wildcard path
-                        * there should only ever be one file returned.
-                        */
-                       status = STATUS_NO_MORE_FILES;
-                       break;
+/*
+ * Return the next finfo directory.
+ *
+ * This parses the blob returned from QUERY_DIRECTORY step by step. If
+ * the blob ends, this triggers a fresh QUERY_DIRECTORY and returns
+ * NT_STATUS_RETRY, which will then trigger the caller again when the
+ * QUERY_DIRECTORY has returned with another buffer. This way we
+ * guarantee that no asynchronous request is open after this call
+ * returns an entry, so that other synchronous requests can be issued
+ * on the same connection while the directory listing proceeds.
+ */
+NTSTATUS cli_smb2_list_recv(
+       struct tevent_req *req,
+       TALLOC_CTX *mem_ctx,
+       struct file_info **pfinfo)
+{
+       struct cli_smb2_list_state *state = tevent_req_data(
+               req, struct cli_smb2_list_state);
+       struct cli_smb2_list_dir_data *response = NULL;
+       struct file_info *finfo = NULL;
+       NTSTATUS status;
+       uint32_t next_offset = 0;
+       bool in_progress;
+
+       in_progress = tevent_req_is_in_progress(req);
+
+       if (!in_progress) {
+               if (!tevent_req_is_nterror(req, &status)) {
+                       status = NT_STATUS_NO_MORE_FILES;
+               }
+               goto fail;
+       }
+
+       response = state->response;
+       if (response == NULL) {
+               struct tevent_req *subreq = NULL;
+               struct cli_state *cli = state->cli;
+               struct smb2_hnd *ph = NULL;
+               uint32_t max_trans, max_avail_len;
+               bool ok;
+
+               if (!NT_STATUS_IS_OK(state->status)) {
+                       status = state->status;
+                       goto fail;
+               }
+
+               status = map_fnum_to_smb2_handle(cli, state->fnum, &ph);
+               if (!NT_STATUS_IS_OK(status)) {
+                       goto fail;
                }
 
-       } while (NT_STATUS_IS_OK(status));
+               max_trans = smb2cli_conn_max_trans_size(cli->conn);
+               ok = smb2cli_conn_req_possible(cli->conn, &max_avail_len);
+               if (ok) {
+                       max_trans = MIN(max_trans, max_avail_len);
+               }
 
-       if (NT_STATUS_EQUAL(status, STATUS_NO_MORE_FILES)) {
-               status = NT_STATUS_OK;
+               subreq = smb2cli_query_directory_send(
+                       state,                          /* mem_ctx */
+                       state->ev,                      /* ev */
+                       cli->conn,                      /* conn */
+                       cli->timeout,                   /* timeout_msec */
+                       cli->smb2.session,              /* session */
+                       cli->smb2.tcon,                 /* tcon */
+                       state->info_level,              /* level */
+                       0,                              /* flags */
+                       0,                              /* file_index */
+                       ph->fid_persistent,             /* fid_persistent */
+                       ph->fid_volatile,               /* fid_volatile */
+                       state->mask,                    /* mask */
+                       max_trans);                     /* outbuf_len */
+               if (subreq == NULL) {
+                       status = NT_STATUS_NO_MEMORY;
+                       goto fail;
+               }
+               tevent_req_set_callback(subreq, cli_smb2_list_done, req);
+               return NT_STATUS_RETRY;
        }
 
-       if (NT_STATUS_IS_OK(status) && !processed_file) {
-               /*
-                * In SMB1 findfirst returns NT_STATUS_NO_SUCH_FILE
-                * if no files match. Emulate this in the client.
-                */
-               status = NT_STATUS_NO_SUCH_FILE;
+       SMB_ASSERT(response->length > state->offset);
+
+       finfo = talloc_zero(mem_ctx, struct file_info);
+       if (finfo == NULL) {
+               status = NT_STATUS_NO_MEMORY;
+               goto fail;
        }
 
-  fail:
+       if (state->info_level == SMB2_FIND_POSIX_INFORMATION) {
+               status = parse_finfo_posix_info(
+                       response->data + state->offset,
+                       response->length - state->offset,
+                       finfo,
+                       &next_offset);
+       } else {
+               status = parse_finfo_id_both_directory_info(
+                       response->data + state->offset,
+                       response->length - state->offset,
+                       finfo,
+                       &next_offset);
+       }
+       if (!NT_STATUS_IS_OK(status)) {
+               goto fail;
+       }
 
-       if (fnum != 0xffff) {
-               cli_smb2_close_fnum(cli, fnum);
+       status = is_bad_finfo_name(state->cli, finfo);
+       if (!NT_STATUS_IS_OK(status)) {
+               goto fail;
        }
 
-       cli->raw_status = status;
+       /*
+        * parse_finfo_id_both_directory_info() checks for overflow,
+        * no need to check again here.
+        */
+       state->offset += next_offset;
 
-       TALLOC_FREE(subframe);
-       TALLOC_FREE(frame);
+       if (next_offset == 0) {
+               TALLOC_FREE(state->response);
+       }
+
+       tevent_req_defer_callback(req, state->ev);
+       tevent_req_notify_callback(req);
+
+       *pfinfo = finfo;
+       return NT_STATUS_OK;
+
+fail:
+       TALLOC_FREE(finfo);
+       tevent_req_received(req);
        return status;
 }
 
@@ -1510,10 +1743,6 @@ NTSTATUS cli_smb2_qpathinfo_basic(struct cli_state *cli,
                return NT_STATUS_INVALID_PARAMETER;
        }
 
-       if (smbXcli_conn_protocol(cli->conn) < PROTOCOL_SMB2_02) {
-               return NT_STATUS_INVALID_PARAMETER;
-       }
-
        /* SMB2 is pickier about pathnames. Ensure it doesn't
           end in a '\' */
        if (namelen > 0 && name[namelen-1] == '\\') {
@@ -1529,7 +1758,7 @@ NTSTATUS cli_smb2_qpathinfo_basic(struct cli_state *cli,
 
        status = cli_smb2_create_fnum(cli,
                        name,
-                       0,                      /* create_flags */
+                       (struct cli_smb2_create_flags){0},
                        SMB2_IMPERSONATION_IMPERSONATION,
                        FILE_READ_ATTRIBUTES,   /* desired_access */
                        FILE_ATTRIBUTE_DIRECTORY, /* file attributes */
@@ -1546,7 +1775,7 @@ NTSTATUS cli_smb2_qpathinfo_basic(struct cli_state *cli,
                /* Maybe a file ? */
                status = cli_smb2_create_fnum(cli,
                        name,
-                       0,                      /* create_flags */
+                       (struct cli_smb2_create_flags){0},
                        SMB2_IMPERSONATION_IMPERSONATION,
                        FILE_READ_ATTRIBUTES,           /* desired_access */
                        0, /* file attributes */
@@ -1560,6 +1789,24 @@ NTSTATUS cli_smb2_qpathinfo_basic(struct cli_state *cli,
                        NULL);
        }
 
+       if (NT_STATUS_EQUAL(status, NT_STATUS_STOPPED_ON_SYMLINK)) {
+               /* Maybe a reparse point ? */
+               status = cli_smb2_create_fnum(cli,
+                       name,
+                       (struct cli_smb2_create_flags){0},
+                       SMB2_IMPERSONATION_IMPERSONATION,
+                       FILE_READ_ATTRIBUTES,           /* desired_access */
+                       0, /* file attributes */
+                       FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, /* share_access */
+                       FILE_OPEN,              /* create_disposition */
+                       FILE_OPEN_REPARSE_POINT, /* create_options */
+                       NULL,
+                       &fnum,
+                       &cr,
+                       NULL,
+                       NULL);
+       }
+
        if (!NT_STATUS_IS_OK(status)) {
                return status;
        }
@@ -1577,87 +1824,6 @@ NTSTATUS cli_smb2_qpathinfo_basic(struct cli_state *cli,
        return status;
 }
 
-struct cli_smb2_chkpath_state {
-       struct tevent_context *ev;
-       struct cli_state *cli;
-};
-
-static void cli_smb2_chkpath_opened(struct tevent_req *subreq);
-static void cli_smb2_chkpath_closed(struct tevent_req *subreq);
-
-struct tevent_req *cli_smb2_chkpath_send(
-       TALLOC_CTX *mem_ctx,
-       struct tevent_context *ev,
-       struct cli_state *cli,
-       const char *name)
-{
-       struct tevent_req *req = NULL, *subreq = NULL;
-       struct cli_smb2_chkpath_state *state = NULL;
-
-       req = tevent_req_create(
-               mem_ctx, &state, struct cli_smb2_chkpath_state);
-       if (req == NULL) {
-               return NULL;
-       }
-       state->ev = ev;
-       state->cli = cli;
-
-       /* Ensure this is a directory. */
-       subreq = cli_smb2_create_fnum_send(
-               state,                             /* mem_ctx */
-               ev,                                /* ev */
-               cli,                               /* cli */
-               name,                              /* fname */
-               0,                                 /* create_flags */
-               SMB2_IMPERSONATION_IMPERSONATION,  /* impersonation_level */
-               FILE_READ_ATTRIBUTES,              /* desired_access */
-               FILE_ATTRIBUTE_DIRECTORY,          /* file_attributes */
-               FILE_SHARE_READ|
-               FILE_SHARE_WRITE|
-               FILE_SHARE_DELETE,                 /* share_access */
-               FILE_OPEN,                         /* create_disposition */
-               FILE_DIRECTORY_FILE,               /* create_options */
-               NULL);                             /* in_cblobs */
-       if (tevent_req_nomem(subreq, req)) {
-               return tevent_req_post(req, ev);
-       }
-       tevent_req_set_callback(subreq, cli_smb2_chkpath_opened, req);
-       return req;
-}
-
-static void cli_smb2_chkpath_opened(struct tevent_req *subreq)
-{
-       struct tevent_req *req = tevent_req_callback_data(
-               subreq, struct tevent_req);
-       struct cli_smb2_chkpath_state *state = tevent_req_data(
-               req, struct cli_smb2_chkpath_state);
-       NTSTATUS status;
-       uint16_t fnum;
-
-       status = cli_smb2_create_fnum_recv(subreq, &fnum, NULL, NULL, NULL);
-       TALLOC_FREE(subreq);
-       if (tevent_req_nterror(req, status)) {
-               return;
-       }
-
-       subreq = cli_smb2_close_fnum_send(state, state->ev, state->cli, fnum);
-       if (tevent_req_nomem(subreq, req)) {
-               return;
-       }
-       tevent_req_set_callback(subreq, cli_smb2_chkpath_closed, req);
-}
-
-static void cli_smb2_chkpath_closed(struct tevent_req *subreq)
-{
-       NTSTATUS status = cli_smb2_close_fnum_recv(subreq);
-       tevent_req_simple_finish_ntstatus(subreq, status);
-}
-
-NTSTATUS cli_smb2_chkpath_recv(struct tevent_req *req)
-{
-       return tevent_req_simple_recv_ntstatus(req);
-}
-
 struct cli_smb2_query_info_fnum_state {
        DATA_BLOB outbuf;
 };
@@ -1817,416 +1983,352 @@ fail:
  Helper function for pathname operations.
 ***************************************************************/
 
-static NTSTATUS get_fnum_from_path(struct cli_state *cli,
-                               const char *name,
-                               uint32_t desired_access,
-                               uint16_t *pfnum)
+struct get_fnum_from_path_state {
+       struct tevent_context *ev;
+       struct cli_state *cli;
+       const char *name;
+       uint32_t desired_access;
+       uint16_t fnum;
+};
+
+static void get_fnum_from_path_opened_file(struct tevent_req *subreq);
+static void get_fnum_from_path_opened_reparse(struct tevent_req *subreq);
+static void get_fnum_from_path_opened_dir(struct tevent_req *subreq);
+
+static struct tevent_req *get_fnum_from_path_send(
+       TALLOC_CTX *mem_ctx,
+       struct tevent_context *ev,
+       struct cli_state *cli,
+       const char *name,
+       uint32_t desired_access)
 {
-       NTSTATUS status;
+       struct tevent_req *req = NULL, *subreq = NULL;
+       struct get_fnum_from_path_state *state = NULL;
        size_t namelen = strlen(name);
-       TALLOC_CTX *frame = talloc_stackframe();
-       uint32_t create_options = 0;
 
-       /* SMB2 is pickier about pathnames. Ensure it doesn't
-          end in a '\' */
+       req = tevent_req_create(
+               mem_ctx, &state, struct get_fnum_from_path_state);
+       if (req == NULL) {
+               return NULL;
+       }
+       state->ev = ev;
+       state->cli = cli;
+       state->name = name;
+       state->desired_access = desired_access;
+
+       /*
+        * SMB2 is pickier about pathnames. Ensure it doesn't end in a
+        * '\'
+        */
        if (namelen > 0 && name[namelen-1] == '\\') {
-               char *modname = talloc_strdup(frame, name);
-               if (modname == NULL) {
-                       status = NT_STATUS_NO_MEMORY;
-                       goto fail;
+               state->name = talloc_strndup(state, name, namelen-1);
+               if (tevent_req_nomem(state->name, req)) {
+                       return tevent_req_post(req, ev);
                }
-               modname[namelen-1] = '\0';
-               name = modname;
        }
 
-       /* Try to open a file handle first. */
-       status = cli_smb2_create_fnum(cli,
-                       name,
-                       0,                      /* create_flags */
-                       SMB2_IMPERSONATION_IMPERSONATION,
-                       desired_access,
-                       0, /* file attributes */
-                       FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, /* share_access */
-                       FILE_OPEN,              /* create_disposition */
-                       create_options,
-                       NULL,
-                       pfnum,
-                       NULL,
-                       NULL,
-                       NULL);
+       subreq = cli_smb2_create_fnum_send(
+               state,          /* mem_ctx, */
+               ev,             /* ev */
+               cli,            /* cli */
+               state->name,    /* fname */
+               (struct cli_smb2_create_flags){0}, /* create_flags */
+               SMB2_IMPERSONATION_IMPERSONATION, /* impersonation_level */
+               desired_access, /* desired_access */
+               0,              /* file_attributes */
+               FILE_SHARE_READ|
+               FILE_SHARE_WRITE|
+               FILE_SHARE_DELETE, /* share_access */
+               FILE_OPEN,      /* create_disposition */
+               0,              /* create_options */
+               NULL);          /* in_cblobs */
+       if (tevent_req_nomem(subreq, req)) {
+               return tevent_req_post(req, ev);
+       }
+       tevent_req_set_callback(subreq, get_fnum_from_path_opened_file, req);
+       return req;
+}
 
-       if (NT_STATUS_EQUAL(status, NT_STATUS_STOPPED_ON_SYMLINK)) {
+static void get_fnum_from_path_opened_file(struct tevent_req *subreq)
+{
+       struct tevent_req *req = tevent_req_callback_data(
+               subreq, struct tevent_req);
+       struct get_fnum_from_path_state *state = tevent_req_data(
+               req, struct get_fnum_from_path_state);
+       NTSTATUS status;
+
+       status = cli_smb2_create_fnum_recv(
+               subreq, &state->fnum, NULL, NULL, NULL, NULL);
+       TALLOC_FREE(subreq);
+
+       if (NT_STATUS_EQUAL(status, NT_STATUS_STOPPED_ON_SYMLINK) ||
+           NT_STATUS_EQUAL(status, NT_STATUS_IO_REPARSE_TAG_NOT_HANDLED)) {
                /*
                 * Naive option to match our SMB1 code. Assume the
                 * symlink path that tripped us up was the last
                 * component and try again. Eventually we will have to
                 * deal with the returned path unprocessed component. JRA.
                 */
-               create_options |= FILE_OPEN_REPARSE_POINT;
-               status = cli_smb2_create_fnum(cli,
-                       name,
-                       0,                      /* create_flags */
-                       SMB2_IMPERSONATION_IMPERSONATION,
-                       desired_access,
-                       0, /* file attributes */
-                       FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, /* share_access */
-                       FILE_OPEN,              /* create_disposition */
-                       create_options,
-                       NULL,
-                       pfnum,
-                       NULL,
-                       NULL,
-                       NULL);
+               subreq = cli_smb2_create_fnum_send(
+                       state,          /* mem_ctx, */
+                       state->ev,      /* ev */
+                       state->cli,     /* cli */
+                       state->name,    /* fname */
+                       (struct cli_smb2_create_flags){0}, /* create_flags */
+                       SMB2_IMPERSONATION_IMPERSONATION, /* impersonation */
+                       state->desired_access, /* desired_access */
+                       0,              /* file_attributes */
+                       FILE_SHARE_READ|
+                       FILE_SHARE_WRITE|
+                       FILE_SHARE_DELETE, /* share_access */
+                       FILE_OPEN,      /* create_disposition */
+                       FILE_OPEN_REPARSE_POINT, /* create_options */
+                       NULL);          /* in_cblobs */
+               if (tevent_req_nomem(subreq, req)) {
+                       return;
+               }
+               tevent_req_set_callback(
+                       subreq, get_fnum_from_path_opened_reparse, req);
+               return;
        }
 
        if (NT_STATUS_EQUAL(status, NT_STATUS_FILE_IS_A_DIRECTORY)) {
-               create_options |= FILE_DIRECTORY_FILE;
-               status = cli_smb2_create_fnum(cli,
-                       name,
-                       0,                      /* create_flags */
-                       SMB2_IMPERSONATION_IMPERSONATION,
-                       desired_access,
-                       FILE_ATTRIBUTE_DIRECTORY, /* file attributes */
-                       FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, /* share_access */
-                       FILE_OPEN,              /* create_disposition */
-                       create_options,         /* create_options */
-                       NULL,
-                       pfnum,
-                       NULL,
-                       NULL,
-                       NULL);
+               subreq = cli_smb2_create_fnum_send(
+                       state,          /* mem_ctx, */
+                       state->ev,      /* ev */
+                       state->cli,     /* cli */
+                       state->name,    /* fname */
+                       (struct cli_smb2_create_flags){0}, /* create_flags */
+                       SMB2_IMPERSONATION_IMPERSONATION, /* impersonation */
+                       state->desired_access, /* desired_access */
+                       0,              /* file_attributes */
+                       FILE_SHARE_READ|
+                       FILE_SHARE_WRITE|
+                       FILE_SHARE_DELETE, /* share_access */
+                       FILE_OPEN,      /* create_disposition */
+                       FILE_DIRECTORY_FILE, /* create_options */
+                       NULL);          /* in_cblobs */
+               if (tevent_req_nomem(subreq, req)) {
+                       return;
+               }
+               tevent_req_set_callback(
+                       subreq, get_fnum_from_path_opened_dir, req);
+               return;
        }
 
-  fail:
-
-       TALLOC_FREE(frame);
-       return status;
+       if (tevent_req_nterror(req, status)) {
+               return;
+       }
+       tevent_req_done(req);
 }
 
-/***************************************************************
- Wrapper that allows SMB2 to query a path info (ALTNAME level).
- Synchronous only.
-***************************************************************/
-
-NTSTATUS cli_smb2_qpathinfo_alt_name(struct cli_state *cli,
-                               const char *name,
-                               fstring alt_name)
+static void get_fnum_from_path_opened_reparse(struct tevent_req *subreq)
 {
-       NTSTATUS status;
-       DATA_BLOB outbuf = data_blob_null;
-       uint16_t fnum = 0xffff;
-       uint32_t altnamelen = 0;
-       TALLOC_CTX *frame = talloc_stackframe();
-
-       if (smbXcli_conn_has_async_calls(cli->conn)) {
-               /*
-                * Can't use sync call while an async call is in flight
-                */
-               status = NT_STATUS_INVALID_PARAMETER;
-               goto fail;
-       }
-
-       if (smbXcli_conn_protocol(cli->conn) < PROTOCOL_SMB2_02) {
-               status = NT_STATUS_INVALID_PARAMETER;
-               goto fail;
-       }
-
-       status = get_fnum_from_path(cli,
-                               name,
-                               FILE_READ_ATTRIBUTES,
-                               &fnum);
-
-       if (!NT_STATUS_IS_OK(status)) {
-               goto fail;
-       }
-
-       status = cli_smb2_query_info_fnum(
-               cli,
-               fnum,
-               1, /* in_info_type */
-               (SMB_FILE_ALTERNATE_NAME_INFORMATION - 1000), /* in_file_info_class */
-               0xFFFF, /* in_max_output_length */
-               NULL, /* in_input_buffer */
-               0, /* in_additional_info */
-               0, /* in_flags */
-               frame,
-               &outbuf);
-
-       if (!NT_STATUS_IS_OK(status)) {
-               goto fail;
-       }
-
-       /* Parse the reply. */
-       if (outbuf.length < 4) {
-               status = NT_STATUS_INVALID_NETWORK_RESPONSE;
-               goto fail;
-       }
-
-       altnamelen = IVAL(outbuf.data, 0);
-       if (altnamelen > outbuf.length - 4) {
-               status = NT_STATUS_INVALID_NETWORK_RESPONSE;
-               goto fail;
-       }
-
-       if (altnamelen > 0) {
-               size_t ret = 0;
-               char *short_name = NULL;
-               ret = pull_string_talloc(frame,
-                               outbuf.data,
-                               FLAGS2_UNICODE_STRINGS,
-                               &short_name,
-                               outbuf.data + 4,
-                               altnamelen,
-                               STR_UNICODE);
-               if (ret == (size_t)-1) {
-                       /* Bad conversion. */
-                       status = NT_STATUS_INVALID_NETWORK_RESPONSE;
-                       goto fail;
-               }
-
-               fstrcpy(alt_name, short_name);
-       } else {
-               alt_name[0] = '\0';
-       }
+       struct tevent_req *req = tevent_req_callback_data(
+               subreq, struct tevent_req);
+       struct get_fnum_from_path_state *state = tevent_req_data(
+               req, struct get_fnum_from_path_state);
+       NTSTATUS status = cli_smb2_create_fnum_recv(
+               subreq, &state->fnum, NULL, NULL, NULL, NULL);
+       tevent_req_simple_finish_ntstatus(subreq, status);
+}
 
-       status = NT_STATUS_OK;
+static void get_fnum_from_path_opened_dir(struct tevent_req *subreq)
+{
+       /* Abstraction violation, but these two are just the same... */
+       get_fnum_from_path_opened_reparse(subreq);
+}
 
-  fail:
+static NTSTATUS get_fnum_from_path_recv(
+       struct tevent_req *req, uint16_t *pfnum)
+{
+       struct get_fnum_from_path_state *state = tevent_req_data(
+               req, struct get_fnum_from_path_state);
+       NTSTATUS status = NT_STATUS_OK;
 
-       if (fnum != 0xffff) {
-               cli_smb2_close_fnum(cli, fnum);
+       if (!tevent_req_is_nterror(req, &status)) {
+               *pfnum = state->fnum;
        }
-
-       cli->raw_status = status;
-
-       TALLOC_FREE(frame);
+       tevent_req_received(req);
        return status;
 }
 
-/***************************************************************
- Wrapper that allows SMB2 to get pathname attributes.
- Synchronous only.
-***************************************************************/
-
-NTSTATUS cli_smb2_getatr(struct cli_state *cli,
-                       const char *name,
-                       uint32_t *pattr,
-                       off_t *size,
-                       time_t *write_time)
+static NTSTATUS get_fnum_from_path(struct cli_state *cli,
+                               const char *name,
+                               uint32_t desired_access,
+                               uint16_t *pfnum)
 {
-       NTSTATUS status;
-       uint16_t fnum = 0xffff;
-       struct smb2_hnd *ph = NULL;
-       struct timespec write_time_ts;
        TALLOC_CTX *frame = talloc_stackframe();
+       struct tevent_context *ev = NULL;
+       struct tevent_req *req = NULL;
+       NTSTATUS status = NT_STATUS_NO_MEMORY;
 
        if (smbXcli_conn_has_async_calls(cli->conn)) {
-               /*
-                * Can't use sync call while an async call is in flight
-                */
                status = NT_STATUS_INVALID_PARAMETER;
                goto fail;
        }
-
-       if (smbXcli_conn_protocol(cli->conn) < PROTOCOL_SMB2_02) {
-               status = NT_STATUS_INVALID_PARAMETER;
-               goto fail;
-       }
-
-       status = get_fnum_from_path(cli,
-                               name,
-                               FILE_READ_ATTRIBUTES,
-                               &fnum);
-
-       if (!NT_STATUS_IS_OK(status)) {
+       ev = samba_tevent_context_init(frame);
+       if (ev == NULL) {
                goto fail;
        }
-
-       status = map_fnum_to_smb2_handle(cli,
-                                       fnum,
-                                       &ph);
-       if (!NT_STATUS_IS_OK(status)) {
+       req = get_fnum_from_path_send(frame, ev, cli, name, desired_access);
+       if (req == NULL) {
                goto fail;
        }
-       status = cli_qfileinfo_basic(
-               cli,
-               fnum,
-               pattr,
-               size,
-               NULL,           /* create_time */
-               NULL,           /* access_time */
-               &write_time_ts,
-               NULL,           /* change_time */
-               NULL);          /* ino */
-       if (!NT_STATUS_IS_OK(status)) {
+       if (!tevent_req_poll_ntstatus(req, ev, &status)) {
                goto fail;
        }
-       if (write_time != NULL) {
-               *write_time = write_time_ts.tv_sec;
-       }
-
-  fail:
-
-       if (fnum != 0xffff) {
-               cli_smb2_close_fnum(cli, fnum);
-       }
-
-       cli->raw_status = status;
-
+       status = get_fnum_from_path_recv(req, pfnum);
+ fail:
        TALLOC_FREE(frame);
        return status;
 }
 
-/***************************************************************
- Wrapper that allows SMB2 to query a pathname info (basic level).
- Implement on top of cli_qfileinfo_basic().
- Synchronous only.
-***************************************************************/
+struct cli_smb2_qpathinfo_state {
+       struct tevent_context *ev;
+       struct cli_state *cli;
+       const char *fname;
+       uint16_t fnum;
+       uint16_t level;
+       uint32_t min_rdata;
+       uint32_t max_rdata;
 
-NTSTATUS cli_smb2_qpathinfo2(struct cli_state *cli,
-                       const char *name,
-                       struct timespec *create_time,
-                       struct timespec *access_time,
-                       struct timespec *write_time,
-                       struct timespec *change_time,
-                       off_t *size,
-                       uint32_t *pattr,
-                       SMB_INO_T *ino)
-{
        NTSTATUS status;
-       struct smb2_hnd *ph = NULL;
-       uint16_t fnum = 0xffff;
-       TALLOC_CTX *frame = talloc_stackframe();
-
-       if (smbXcli_conn_has_async_calls(cli->conn)) {
-               /*
-                * Can't use sync call while an async call is in flight
-                */
-               status = NT_STATUS_INVALID_PARAMETER;
-               goto fail;
-       }
-
-       if (smbXcli_conn_protocol(cli->conn) < PROTOCOL_SMB2_02) {
-               status = NT_STATUS_INVALID_PARAMETER;
-               goto fail;
-       }
-
-       status = get_fnum_from_path(cli,
-                                       name,
-                                       FILE_READ_ATTRIBUTES,
-                                       &fnum);
+       DATA_BLOB out;
+};
 
-       if (!NT_STATUS_IS_OK(status)) {
-               goto fail;
-       }
+static void cli_smb2_qpathinfo_opened(struct tevent_req *subreq);
+static void cli_smb2_qpathinfo_done(struct tevent_req *subreq);
+static void cli_smb2_qpathinfo_closed(struct tevent_req *subreq);
+
+struct tevent_req *cli_smb2_qpathinfo_send(TALLOC_CTX *mem_ctx,
+                                          struct tevent_context *ev,
+                                          struct cli_state *cli,
+                                          const char *fname,
+                                          uint16_t level,
+                                          uint32_t min_rdata,
+                                          uint32_t max_rdata)
+{
+       struct tevent_req *req = NULL, *subreq = NULL;
+       struct cli_smb2_qpathinfo_state *state = NULL;
 
-       status = map_fnum_to_smb2_handle(cli,
-                                       fnum,
-                                       &ph);
-       if (!NT_STATUS_IS_OK(status)) {
-               goto fail;
+       req = tevent_req_create(mem_ctx,
+                               &state,
+                               struct cli_smb2_qpathinfo_state);
+       if (req == NULL) {
+               return NULL;
        }
-
-       status = cli_qfileinfo_basic(
-               cli,
-               fnum,
-               pattr,
-               size,
-               create_time,
-               access_time,
-               write_time,
-               change_time,
-               ino);
-
-  fail:
-
-       if (fnum != 0xffff) {
-               cli_smb2_close_fnum(cli, fnum);
+       state->ev = ev;
+       state->cli = cli;
+       state->level = level;
+       state->min_rdata = min_rdata;
+       state->max_rdata = max_rdata;
+
+       subreq = get_fnum_from_path_send(state,
+                                        ev,
+                                        cli,
+                                        fname,
+                                        FILE_READ_ATTRIBUTES);
+       if (tevent_req_nomem(subreq, req)) {
+               return tevent_req_post(req, ev);
        }
-
-       cli->raw_status = status;
-
-       TALLOC_FREE(frame);
-       return status;
+       tevent_req_set_callback(subreq, cli_smb2_qpathinfo_opened, req);
+       return req;
 }
 
-/***************************************************************
- Wrapper that allows SMB2 to query pathname streams.
- Synchronous only.
-***************************************************************/
-
-NTSTATUS cli_smb2_qpathinfo_streams(struct cli_state *cli,
-                               const char *name,
-                               TALLOC_CTX *mem_ctx,
-                               unsigned int *pnum_streams,
-                               struct stream_struct **pstreams)
+static void cli_smb2_qpathinfo_opened(struct tevent_req *subreq)
 {
+       struct tevent_req *req =
+               tevent_req_callback_data(subreq, struct tevent_req);
+       struct cli_smb2_qpathinfo_state *state =
+               tevent_req_data(req, struct cli_smb2_qpathinfo_state);
        NTSTATUS status;
-       uint16_t fnum = 0xffff;
-       DATA_BLOB outbuf = data_blob_null;
-       TALLOC_CTX *frame = talloc_stackframe();
 
-       if (smbXcli_conn_has_async_calls(cli->conn)) {
-               /*
-                * Can't use sync call while an async call is in flight
-                */
-               status = NT_STATUS_INVALID_PARAMETER;
-               goto fail;
-       }
-
-       if (smbXcli_conn_protocol(cli->conn) < PROTOCOL_SMB2_02) {
-               status = NT_STATUS_INVALID_PARAMETER;
-               goto fail;
+       status = get_fnum_from_path_recv(subreq, &state->fnum);
+       TALLOC_FREE(subreq);
+       if (tevent_req_nterror(req, status)) {
+               return;
        }
 
-       status = get_fnum_from_path(cli,
-                               name,
-                               FILE_READ_ATTRIBUTES,
-                               &fnum);
-
-       if (!NT_STATUS_IS_OK(status)) {
-               goto fail;
+       subreq = cli_smb2_query_info_fnum_send(state,
+                                              state->ev,
+                                              state->cli,
+                                              state->fnum,
+                                              1, /* in_info_type */
+                                              state->level,
+                                              state->max_rdata,
+                                              NULL, /* in_input_buffer */
+                                              0,    /* in_additional_info */
+                                              0);   /* in_flags */
+       if (tevent_req_nomem(subreq, req)) {
+               return;
        }
+       tevent_req_set_callback(subreq, cli_smb2_qpathinfo_done, req);
+}
 
-       /* getinfo on the handle with info_type SMB2_GETINFO_FILE (1),
-          level 22 (SMB2_FILE_STREAM_INFORMATION). */
+static void cli_smb2_qpathinfo_done(struct tevent_req *subreq)
+{
+       struct tevent_req *req =
+               tevent_req_callback_data(subreq, struct tevent_req);
+       struct cli_smb2_qpathinfo_state *state =
+               tevent_req_data(req, struct cli_smb2_qpathinfo_state);
 
-       status = cli_smb2_query_info_fnum(
-               cli,
-               fnum,
-               1, /* in_info_type */
-               (SMB_FILE_STREAM_INFORMATION - 1000), /* in_file_info_class */
-               0xFFFF, /* in_max_output_length */
-               NULL, /* in_input_buffer */
-               0, /* in_additional_info */
-               0, /* in_flags */
-               frame,
-               &outbuf);
+       state->status =
+               cli_smb2_query_info_fnum_recv(subreq, state, &state->out);
+       TALLOC_FREE(subreq);
 
-       if (!NT_STATUS_IS_OK(status)) {
-               goto fail;
+       if (NT_STATUS_IS_OK(state->status) &&
+           (state->out.length < state->min_rdata)) {
+               state->status = NT_STATUS_INVALID_NETWORK_RESPONSE;
        }
 
-       /* Parse the reply. */
-       if (!parse_streams_blob(mem_ctx,
-                               outbuf.data,
-                               outbuf.length,
-                               pnum_streams,
-                               pstreams)) {
-               status = NT_STATUS_INVALID_NETWORK_RESPONSE;
-               goto fail;
+       subreq = cli_smb2_close_fnum_send(state,
+                                         state->ev,
+                                         state->cli,
+                                         state->fnum,
+                                         0);
+       if (tevent_req_nomem(subreq, req)) {
+               return;
        }
+       tevent_req_set_callback(subreq, cli_smb2_qpathinfo_closed, req);
+}
 
-  fail:
+static void cli_smb2_qpathinfo_closed(struct tevent_req *subreq)
+{
+       struct tevent_req *req =
+               tevent_req_callback_data(subreq, struct tevent_req);
+       struct cli_smb2_qpathinfo_state *state =
+               tevent_req_data(req, struct cli_smb2_qpathinfo_state);
+       NTSTATUS status;
 
-       if (fnum != 0xffff) {
-               cli_smb2_close_fnum(cli, fnum);
+       status = cli_smb2_close_fnum_recv(subreq);
+       TALLOC_FREE(subreq);
+       if (tevent_req_nterror(req, status)) {
+               return;
        }
+       if (tevent_req_nterror(req, state->status)) {
+               return;
+       }
+       tevent_req_done(req);
+}
+
+NTSTATUS cli_smb2_qpathinfo_recv(struct tevent_req *req,
+                                TALLOC_CTX *mem_ctx,
+                                uint8_t **rdata,
+                                uint32_t *num_rdata)
+{
+       struct cli_smb2_qpathinfo_state *state =
+               tevent_req_data(req, struct cli_smb2_qpathinfo_state);
+       NTSTATUS status;
 
-       cli->raw_status = status;
+       if (tevent_req_is_nterror(req, &status)) {
+               return status;
+       }
 
-       TALLOC_FREE(frame);
-       return status;
+       *rdata = talloc_move(mem_ctx, &state->out.data);
+       *num_rdata = state->out.length;
+       tevent_req_received(req);
+       return NT_STATUS_OK;
 }
 
 /***************************************************************
@@ -2253,11 +2355,6 @@ NTSTATUS cli_smb2_setpathinfo(struct cli_state *cli,
                goto fail;
        }
 
-       if (smbXcli_conn_protocol(cli->conn) < PROTOCOL_SMB2_02) {
-               status = NT_STATUS_INVALID_PARAMETER;
-               goto fail;
-       }
-
        status = get_fnum_from_path(cli,
                                name,
                                FILE_WRITE_ATTRIBUTES,
@@ -2361,6 +2458,7 @@ NTSTATUS cli_smb2_setattrE(struct cli_state *cli,
 {
        uint8_t inbuf_store[40];
        DATA_BLOB inbuf = data_blob_null;
+       NTSTATUS status;
 
        if (smbXcli_conn_has_async_calls(cli->conn)) {
                /*
@@ -2369,10 +2467,6 @@ NTSTATUS cli_smb2_setattrE(struct cli_state *cli,
                return NT_STATUS_INVALID_PARAMETER;
        }
 
-       if (smbXcli_conn_protocol(cli->conn) < PROTOCOL_SMB2_02) {
-               return NT_STATUS_INVALID_PARAMETER;
-       }
-
        /* setinfo on the handle with info_type SMB2_SETINFO_FILE (1),
           level 4 (SMB_FILE_BASIC_INFORMATION - 1000). */
 
@@ -2391,15 +2485,15 @@ NTSTATUS cli_smb2_setattrE(struct cli_state *cli,
                put_long_date((char *)inbuf.data + 16, write_time);
        }
 
-       cli->raw_status = cli_smb2_set_info_fnum(
-               cli,
-               fnum,
-               1,              /* in_info_type */
-               SMB_FILE_BASIC_INFORMATION - 1000, /* in_file_info_class */
-               &inbuf,            /* in_input_buffer */
-               0);                /* in_additional_info */
-
-       return cli->raw_status;
+       status = cli_smb2_set_info_fnum(cli,
+                                       fnum,
+                                       1, /* in_info_type */
+                                       SMB_FILE_BASIC_INFORMATION -
+                                               1000, /* in_file_info_class */
+                                       &inbuf,       /* in_input_buffer */
+                                       0);           /* in_additional_info */
+       cli->raw_status = status;
+       return status;
 }
 
 /***************************************************************
@@ -2427,15 +2521,10 @@ NTSTATUS cli_smb2_dskattr(struct cli_state *cli, const char *path,
                goto fail;
        }
 
-       if (smbXcli_conn_protocol(cli->conn) < PROTOCOL_SMB2_02) {
-               status = NT_STATUS_INVALID_PARAMETER;
-               goto fail;
-       }
-
        /* First open the top level directory. */
        status = cli_smb2_create_fnum(cli,
                        path,
-                       0,                      /* create_flags */
+                       (struct cli_smb2_create_flags){0},
                        SMB2_IMPERSONATION_IMPERSONATION,
                        FILE_READ_ATTRIBUTES,   /* desired_access */
                        FILE_ATTRIBUTE_DIRECTORY, /* file attributes */
@@ -2530,14 +2619,10 @@ NTSTATUS cli_smb2_get_fs_full_size_info(struct cli_state *cli,
                goto fail;
        }
 
-       if (smbXcli_conn_protocol(cli->conn) < PROTOCOL_SMB2_02) {
-               status = NT_STATUS_INVALID_PARAMETER;
-               goto fail;
-       }
-
        /* First open the top level directory. */
        status =
-           cli_smb2_create_fnum(cli, "", 0,               /* create_flags */
+           cli_smb2_create_fnum(cli, "",
+                                (struct cli_smb2_create_flags){0},
                                 SMB2_IMPERSONATION_IMPERSONATION,
                                 FILE_READ_ATTRIBUTES,     /* desired_access */
                                 FILE_ATTRIBUTE_DIRECTORY, /* file attributes */
@@ -2616,14 +2701,10 @@ NTSTATUS cli_smb2_get_fs_attr_info(struct cli_state *cli, uint32_t *fs_attr)
                goto fail;
        }
 
-       if (smbXcli_conn_protocol(cli->conn) < PROTOCOL_SMB2_02) {
-               status = NT_STATUS_INVALID_PARAMETER;
-               goto fail;
-       }
-
        /* First open the top level directory. */
        status =
-           cli_smb2_create_fnum(cli, "", 0,               /* create_flags */
+           cli_smb2_create_fnum(cli, "",
+                                (struct cli_smb2_create_flags){0},
                                 SMB2_IMPERSONATION_IMPERSONATION,
                                 FILE_READ_ATTRIBUTES,     /* desired_access */
                                 FILE_ATTRIBUTE_DIRECTORY, /* file attributes */
@@ -2701,14 +2782,10 @@ NTSTATUS cli_smb2_get_fs_volume_info(struct cli_state *cli,
                goto fail;
        }
 
-       if (smbXcli_conn_protocol(cli->conn) < PROTOCOL_SMB2_02) {
-               status = NT_STATUS_INVALID_PARAMETER;
-               goto fail;
-       }
-
        /* First open the top level directory. */
        status =
-           cli_smb2_create_fnum(cli, "", 0,               /* create_flags */
+           cli_smb2_create_fnum(cli, "",
+                                (struct cli_smb2_create_flags){0},
                                 SMB2_IMPERSONATION_IMPERSONATION,
                                 FILE_READ_ATTRIBUTES,     /* desired_access */
                                 FILE_ATTRIBUTE_DIRECTORY, /* file attributes */
@@ -2752,7 +2829,7 @@ NTSTATUS cli_smb2_get_fs_volume_info(struct cli_state *cli,
 
        if (pdate) {
                struct timespec ts;
-               ts = interpret_long_date((char *)outbuf.data);
+               ts = interpret_long_date(BVAL(outbuf.data, 0));
                *pdate = ts.tv_sec;
        }
        if (pserial_number) {
@@ -2799,138 +2876,6 @@ fail:
        return status;
 }
 
-
-/***************************************************************
- Wrapper that allows SMB2 to query a security descriptor.
- Synchronous only.
-***************************************************************/
-
-NTSTATUS cli_smb2_query_security_descriptor(struct cli_state *cli,
-                                       uint16_t fnum,
-                                       uint32_t sec_info,
-                                       TALLOC_CTX *mem_ctx,
-                                       struct security_descriptor **ppsd)
-{
-       NTSTATUS status;
-       DATA_BLOB outbuf = data_blob_null;
-       struct security_descriptor *lsd = NULL;
-       TALLOC_CTX *frame = talloc_stackframe();
-
-       if (smbXcli_conn_has_async_calls(cli->conn)) {
-               /*
-                * Can't use sync call while an async call is in flight
-                */
-               status = NT_STATUS_INVALID_PARAMETER;
-               goto fail;
-       }
-
-       if (smbXcli_conn_protocol(cli->conn) < PROTOCOL_SMB2_02) {
-               status = NT_STATUS_INVALID_PARAMETER;
-               goto fail;
-       }
-
-       /* getinfo on the returned handle with info_type SMB2_GETINFO_SEC (3) */
-
-       status = cli_smb2_query_info_fnum(
-               cli,
-               fnum,
-               3, /* in_info_type */
-               0, /* in_file_info_class */
-               0xFFFF, /* in_max_output_length */
-               NULL, /* in_input_buffer */
-               sec_info, /* in_additional_info */
-               0, /* in_flags */
-               frame,
-               &outbuf);
-
-       if (!NT_STATUS_IS_OK(status)) {
-               goto fail;
-       }
-
-       /* Parse the reply. */
-       status = unmarshall_sec_desc(mem_ctx,
-                               outbuf.data,
-                               outbuf.length,
-                               &lsd);
-
-       if (!NT_STATUS_IS_OK(status)) {
-               goto fail;
-       }
-
-       if (ppsd != NULL) {
-               *ppsd = lsd;
-       } else {
-               TALLOC_FREE(lsd);
-       }
-
-  fail:
-
-       cli->raw_status = status;
-
-       TALLOC_FREE(frame);
-       return status;
-}
-
-/***************************************************************
- Wrapper that allows SMB2 to set a security descriptor.
- Synchronous only.
-***************************************************************/
-
-NTSTATUS cli_smb2_set_security_descriptor(struct cli_state *cli,
-                                       uint16_t fnum,
-                                       uint32_t sec_info,
-                                       const struct security_descriptor *sd)
-{
-       NTSTATUS status;
-       DATA_BLOB inbuf = data_blob_null;
-       TALLOC_CTX *frame = talloc_stackframe();
-
-       if (smbXcli_conn_has_async_calls(cli->conn)) {
-               /*
-                * Can't use sync call while an async call is in flight
-                */
-               status = NT_STATUS_INVALID_PARAMETER;
-               goto fail;
-       }
-
-       if (smbXcli_conn_protocol(cli->conn) < PROTOCOL_SMB2_02) {
-               status = NT_STATUS_INVALID_PARAMETER;
-               goto fail;
-       }
-
-       status = marshall_sec_desc(frame,
-                               sd,
-                               &inbuf.data,
-                               &inbuf.length);
-
-        if (!NT_STATUS_IS_OK(status)) {
-               goto fail;
-        }
-
-       /* setinfo on the returned handle with info_type SMB2_SETINFO_SEC (3) */
-
-       status = cli_smb2_set_info_fnum(
-               cli,
-               fnum,
-               3,                        /* in_info_type */
-               0,                        /* in_file_info_class */
-               &inbuf,                   /* in_input_buffer */
-               sec_info);                /* in_additional_info */
-
-  fail:
-
-       cli->raw_status = status;
-
-       TALLOC_FREE(frame);
-       return status;
-}
-
-/***************************************************************
- Wrapper that allows SMB2 to query a security descriptor.
- Synchronous only.
-
-***************************************************************/
-
 struct cli_smb2_mxac_state {
        struct tevent_context *ev;
        struct cli_state *cli;
@@ -2963,11 +2908,6 @@ struct tevent_req *cli_smb2_query_mxac_send(TALLOC_CTX *mem_ctx,
                .fname = fname,
        };
 
-       if (smbXcli_conn_protocol(cli->conn) < PROTOCOL_SMB2_02) {
-               tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
-               return tevent_req_post(req, ev);
-       }
-
        status = smb2_create_blob_add(state,
                                      &state->in_cblobs,
                                      SMB2_CREATE_TAG_MXAC,
@@ -2981,7 +2921,7 @@ struct tevent_req *cli_smb2_query_mxac_send(TALLOC_CTX *mem_ctx,
                state->ev,
                state->cli,
                state->fname,
-               0,                      /* create_flags */
+               (struct cli_smb2_create_flags){0},
                SMB2_IMPERSONATION_IMPERSONATION,
                FILE_READ_ATTRIBUTES,
                0,                      /* file attributes */
@@ -3007,7 +2947,7 @@ static void cli_smb2_mxac_opened(struct tevent_req *subreq)
        NTSTATUS status;
 
        status = cli_smb2_create_fnum_recv(
-               subreq, &state->fnum, NULL, state, &out_cblobs);
+               subreq, &state->fnum, NULL, state, &out_cblobs, NULL);
        TALLOC_FREE(subreq);
 
        if (tevent_req_nterror(req, status)) {
@@ -3028,8 +2968,11 @@ static void cli_smb2_mxac_opened(struct tevent_req *subreq)
        state->mxac = IVAL(mxac_blob->data.data, 4);
 
 close:
-       subreq = cli_smb2_close_fnum_send(
-               state, state->ev, state->cli, state->fnum);
+       subreq = cli_smb2_close_fnum_send(state,
+                                         state->ev,
+                                         state->cli,
+                                         state->fnum,
+                                         0);
        if (tevent_req_nomem(subreq, req)) {
                return;
        }
@@ -3108,113 +3051,259 @@ fail:
        return status;
 }
 
-/***************************************************************
- Wrapper that allows SMB2 to rename a file.
- Synchronous only.
-***************************************************************/
+struct cli_smb2_rename_fnum_state {
+       DATA_BLOB inbuf;
+};
+
+static void cli_smb2_rename_fnum_done(struct tevent_req *subreq);
 
-NTSTATUS cli_smb2_rename(struct cli_state *cli,
-                        const char *fname_src,
-                        const char *fname_dst,
-                        bool replace)
+static struct tevent_req *cli_smb2_rename_fnum_send(
+       TALLOC_CTX *mem_ctx,
+       struct tevent_context *ev,
+       struct cli_state *cli,
+       uint16_t fnum,
+       const char *fname_dst,
+       bool replace)
 {
-       NTSTATUS status;
-       DATA_BLOB inbuf = data_blob_null;
-       uint16_t fnum = 0xffff;
+       struct tevent_req *req = NULL, *subreq = NULL;
+       struct cli_smb2_rename_fnum_state *state = NULL;
+       size_t namelen = strlen(fname_dst);
        smb_ucs2_t *converted_str = NULL;
        size_t converted_size_bytes = 0;
-       size_t namelen = 0;
-       TALLOC_CTX *frame = talloc_stackframe();
+       size_t inbuf_size;
+       bool ok;
 
-       if (smbXcli_conn_has_async_calls(cli->conn)) {
-               /*
-                * Can't use sync call while an async call is in flight
-                */
-               status = NT_STATUS_INVALID_PARAMETER;
-               goto fail;
+       req = tevent_req_create(
+               mem_ctx, &state, struct cli_smb2_rename_fnum_state);
+       if (req == NULL) {
+               return NULL;
        }
 
-       if (smbXcli_conn_protocol(cli->conn) < PROTOCOL_SMB2_02) {
-               status = NT_STATUS_INVALID_PARAMETER;
-               goto fail;
+       /*
+        * SMB2 is pickier about pathnames. Ensure it doesn't start in
+        * a '\'
+        */
+       if (*fname_dst == '\\') {
+               fname_dst++;
        }
 
-       status = get_fnum_from_path(cli,
-                               fname_src,
-                               DELETE_ACCESS,
-                               &fnum);
+       /*
+        * SMB2 is pickier about pathnames. Ensure it doesn't end in a
+        * '\'
+        */
+       if (namelen > 0 && fname_dst[namelen-1] == '\\') {
+               fname_dst = talloc_strndup(state, fname_dst, namelen-1);
+               if (tevent_req_nomem(fname_dst, req)) {
+                       return tevent_req_post(req, ev);
+               }
+       }
+
+       ok = push_ucs2_talloc(
+               state, &converted_str, fname_dst, &converted_size_bytes);
+       if (!ok) {
+               tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+               return tevent_req_post(req, ev);
+       }
+
+       /*
+        * W2K8 insists the dest name is not null terminated. Remove
+        * the last 2 zero bytes and reduce the name length.
+        */
+       if (converted_size_bytes < 2) {
+               tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+               return tevent_req_post(req, ev);
+       }
+       converted_size_bytes -= 2;
+
+       inbuf_size = 20 + converted_size_bytes;
+       if (inbuf_size < 20) {
+               /* Integer wrap check. */
+               tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+               return tevent_req_post(req, ev);
+       }
+
+       /*
+        * The Windows 10 SMB2 server has a minimum length
+        * for a SMB2_FILE_RENAME_INFORMATION buffer of
+        * 24 bytes. It returns NT_STATUS_INFO_LENGTH_MISMATCH
+        * if the length is less. This isn't an alignment
+        * issue as Windows client accepts happily 2-byte align
+        * for larger target name sizes. Also the Windows 10
+        * SMB1 server doesn't have this restriction.
+        *
+        * BUG: https://bugzilla.samba.org/show_bug.cgi?id=14403
+        */
+       inbuf_size = MAX(inbuf_size, 24);
+
+       state->inbuf = data_blob_talloc_zero(state, inbuf_size);
+       if (tevent_req_nomem(state->inbuf.data, req)) {
+               return tevent_req_post(req, ev);
+       }
+
+       if (replace) {
+               SCVAL(state->inbuf.data, 0, 1);
+       }
+
+       SIVAL(state->inbuf.data, 16, converted_size_bytes);
+       memcpy(state->inbuf.data + 20, converted_str, converted_size_bytes);
+
+       TALLOC_FREE(converted_str);
+
+       /* setinfo on the returned handle with info_type SMB2_GETINFO_FILE (1),
+          level SMB2_FILE_RENAME_INFORMATION (SMB_FILE_RENAME_INFORMATION - 1000) */
+
+       subreq = cli_smb2_set_info_fnum_send(
+               state,          /* mem_ctx */
+               ev,             /* ev */
+               cli,            /* cli */
+               fnum,           /* fnum */
+               1,              /* in_info_type */
+               SMB_FILE_RENAME_INFORMATION - 1000, /* in_file_info_class */
+               &state->inbuf,  /* in_input_buffer */
+               0);             /* in_additional_info */
+       if (tevent_req_nomem(subreq, req)) {
+               return tevent_req_post(req, ev);
+       }
+       tevent_req_set_callback(subreq, cli_smb2_rename_fnum_done, req);
+       return req;
+}
+
+static void cli_smb2_rename_fnum_done(struct tevent_req *subreq)
+{
+       NTSTATUS status = cli_smb2_set_info_fnum_recv(subreq);
+       tevent_req_simple_finish_ntstatus(subreq, status);
+}
+
+static NTSTATUS cli_smb2_rename_fnum_recv(struct tevent_req *req)
+{
+       return tevent_req_simple_recv_ntstatus(req);
+}
+
+/***************************************************************
+ Wrapper that allows SMB2 to rename a file.
+***************************************************************/
+
+struct cli_smb2_rename_state {
+       struct tevent_context *ev;
+       struct cli_state *cli;
+       const char *fname_dst;
+       bool replace;
+       uint16_t fnum;
+
+       NTSTATUS rename_status;
+};
+
+static void cli_smb2_rename_opened(struct tevent_req *subreq);
+static void cli_smb2_rename_renamed(struct tevent_req *subreq);
+static void cli_smb2_rename_closed(struct tevent_req *subreq);
+
+struct tevent_req *cli_smb2_rename_send(
+       TALLOC_CTX *mem_ctx,
+       struct tevent_context *ev,
+       struct cli_state *cli,
+       const char *fname_src,
+       const char *fname_dst,
+       bool replace)
+{
+       struct tevent_req *req = NULL, *subreq = NULL;
+       struct cli_smb2_rename_state *state = NULL;
+       NTSTATUS status;
 
-       if (!NT_STATUS_IS_OK(status)) {
-               goto fail;
+       req = tevent_req_create(
+               mem_ctx, &state, struct cli_smb2_rename_state);
+       if (req == NULL) {
+               return NULL;
        }
 
-       /* SMB2 is pickier about pathnames. Ensure it doesn't
-          start in a '\' */
-       if (*fname_dst == '\\') {
-               fname_dst++;
+       /*
+        * Strip a MSDFS path from fname_dst if we were given one.
+        */
+       status = cli_dfs_target_check(state,
+                               cli,
+                               fname_dst,
+                               &fname_dst);
+       if (tevent_req_nterror(req, status)) {
+               return tevent_req_post(req, ev);
        }
 
-       /* SMB2 is pickier about pathnames. Ensure it doesn't
-          end in a '\' */
-       namelen = strlen(fname_dst);
-       if (namelen > 0 && fname_dst[namelen-1] == '\\') {
-               char *modname = talloc_strdup(frame, fname_dst);
-               modname[namelen-1] = '\0';
-               fname_dst = modname;
-       }
+       state->ev = ev;
+       state->cli = cli;
+       state->fname_dst = fname_dst;
+       state->replace = replace;
 
-       if (!push_ucs2_talloc(frame,
-                               &converted_str,
-                               fname_dst,
-                               &converted_size_bytes)) {
-               status = NT_STATUS_INVALID_PARAMETER;
-               goto fail;
+       subreq = get_fnum_from_path_send(
+               state, ev, cli, fname_src, DELETE_ACCESS);
+       if (tevent_req_nomem(subreq, req)) {
+               return tevent_req_post(req, ev);
        }
+       tevent_req_set_callback(subreq, cli_smb2_rename_opened, req);
+       return req;
+}
 
-       /* W2K8 insists the dest name is not null
-          terminated. Remove the last 2 zero bytes
-          and reduce the name length. */
+static void cli_smb2_rename_opened(struct tevent_req *subreq)
+{
+       struct tevent_req *req = tevent_req_callback_data(
+               subreq, struct tevent_req);
+       struct cli_smb2_rename_state *state = tevent_req_data(
+               req, struct cli_smb2_rename_state);
+       NTSTATUS status;
 
-       if (converted_size_bytes < 2) {
-               status = NT_STATUS_INVALID_PARAMETER;
-               goto fail;
+       status = get_fnum_from_path_recv(subreq, &state->fnum);
+       TALLOC_FREE(subreq);
+       if (tevent_req_nterror(req, status)) {
+               return;
        }
-       converted_size_bytes -= 2;
 
-       inbuf = data_blob_talloc_zero(frame,
-                               20 + converted_size_bytes);
-       if (inbuf.data == NULL) {
-               status = NT_STATUS_NO_MEMORY;
-               goto fail;
+       subreq = cli_smb2_rename_fnum_send(
+               state,
+               state->ev,
+               state->cli,
+               state->fnum,
+               state->fname_dst,
+               state->replace);
+       if (tevent_req_nomem(subreq, req)) {
+               return;
        }
+       tevent_req_set_callback(subreq, cli_smb2_rename_renamed, req);
+}
 
-       if (replace) {
-               SCVAL(inbuf.data, 0, 1);
-       }
+static void cli_smb2_rename_renamed(struct tevent_req *subreq)
+{
+       struct tevent_req *req = tevent_req_callback_data(
+               subreq, struct tevent_req);
+       struct cli_smb2_rename_state *state = tevent_req_data(
+               req, struct cli_smb2_rename_state);
 
-       SIVAL(inbuf.data, 16, converted_size_bytes);
-       memcpy(inbuf.data + 20, converted_str, converted_size_bytes);
+       state->rename_status = cli_smb2_rename_fnum_recv(subreq);
+       TALLOC_FREE(subreq);
 
-       /* setinfo on the returned handle with info_type SMB2_GETINFO_FILE (1),
-          level SMB2_FILE_RENAME_INFORMATION (SMB_FILE_RENAME_INFORMATION - 1000) */
+       subreq = cli_smb2_close_fnum_send(state,
+                                         state->ev,
+                                         state->cli,
+                                         state->fnum,
+                                         0);
+       if (tevent_req_nomem(subreq, req)) {
+               return;
+       }
+       tevent_req_set_callback(subreq, cli_smb2_rename_closed, req);
+}
 
-       status = cli_smb2_set_info_fnum(
-               cli,
-               fnum,
-               1,              /* in_info_type */
-               SMB_FILE_RENAME_INFORMATION - 1000, /* in_file_info_class */
-               &inbuf,            /* in_input_buffer */
-               0);                /* in_additional_info */
+static void cli_smb2_rename_closed(struct tevent_req *subreq)
+{
+       NTSTATUS status = cli_smb2_close_fnum_recv(subreq);
+       tevent_req_simple_finish_ntstatus(subreq, status);
+}
 
-  fail:
+NTSTATUS cli_smb2_rename_recv(struct tevent_req *req)
+{
+       struct cli_smb2_rename_state *state = tevent_req_data(
+               req, struct cli_smb2_rename_state);
+       NTSTATUS status = NT_STATUS_OK;
 
-       if (fnum != 0xffff) {
-               cli_smb2_close_fnum(cli, fnum);
+       if (!tevent_req_is_nterror(req, &status)) {
+               status = state->rename_status;
        }
-
-       cli->raw_status = status;
-
-       TALLOC_FREE(frame);
+       tevent_req_received(req);
        return status;
 }
 
@@ -3244,11 +3333,6 @@ NTSTATUS cli_smb2_set_ea_fnum(struct cli_state *cli,
                goto fail;
        }
 
-       if (smbXcli_conn_protocol(cli->conn) < PROTOCOL_SMB2_02) {
-               status = NT_STATUS_INVALID_PARAMETER;
-               goto fail;
-       }
-
        /* Marshall the SMB2 EA data. */
        if (ea_len > 0xFFFF) {
                status = NT_STATUS_INVALID_PARAMETER;
@@ -3324,11 +3408,6 @@ NTSTATUS cli_smb2_set_ea_path(struct cli_state *cli,
                goto fail;
        }
 
-       if (smbXcli_conn_protocol(cli->conn) < PROTOCOL_SMB2_02) {
-               status = NT_STATUS_INVALID_PARAMETER;
-               goto fail;
-       }
-
        status = get_fnum_from_path(cli,
                                name,
                                FILE_WRITE_EA,
@@ -3388,11 +3467,6 @@ NTSTATUS cli_smb2_get_ea_list_path(struct cli_state *cli,
                goto fail;
        }
 
-       if (smbXcli_conn_protocol(cli->conn) < PROTOCOL_SMB2_02) {
-               status = NT_STATUS_INVALID_PARAMETER;
-               goto fail;
-       }
-
        status = get_fnum_from_path(cli,
                                name,
                                FILE_READ_EA,
@@ -3489,11 +3563,6 @@ NTSTATUS cli_smb2_get_user_quota(struct cli_state *cli,
                goto fail;
        }
 
-       if (smbXcli_conn_protocol(cli->conn) < PROTOCOL_SMB2_02) {
-               status = NT_STATUS_INVALID_PARAMETER;
-               goto fail;
-       }
-
        sid_len = ndr_size_dom_sid(&pqt->sid, 0);
 
        query.return_single = 1;
@@ -3594,11 +3663,6 @@ NTSTATUS cli_smb2_list_user_quota_step(struct cli_state *cli,
                goto cleanup;
        }
 
-       if (smbXcli_conn_protocol(cli->conn) < PROTOCOL_SMB2_02) {
-               status = NT_STATUS_INVALID_PARAMETER;
-               goto cleanup;
-       }
-
        info.restart_scan = first ? 1 : 0;
 
        err = ndr_push_struct_blob(
@@ -3667,11 +3731,6 @@ NTSTATUS cli_smb2_get_fs_quota_info(struct cli_state *cli,
                goto cleanup;
        }
 
-       if (smbXcli_conn_protocol(cli->conn) < PROTOCOL_SMB2_02) {
-               status = NT_STATUS_INVALID_PARAMETER;
-               goto cleanup;
-       }
-
        status = cli_smb2_query_info_fnum(
                cli,
                quota_fnum,
@@ -3718,11 +3777,6 @@ NTSTATUS cli_smb2_set_user_quota(struct cli_state *cli,
                goto cleanup;
        }
 
-       if (smbXcli_conn_protocol(cli->conn) < PROTOCOL_SMB2_02) {
-               status = NT_STATUS_INVALID_PARAMETER;
-               goto cleanup;
-       }
-
        status = build_user_quota_buffer(qtl, 0, talloc_tos(), &inbuf, NULL);
        if (!NT_STATUS_IS_OK(status)) {
                goto cleanup;
@@ -3760,11 +3814,6 @@ NTSTATUS cli_smb2_set_fs_quota_info(struct cli_state *cli,
                goto cleanup;
        }
 
-       if (smbXcli_conn_protocol(cli->conn) < PROTOCOL_SMB2_02) {
-               status = NT_STATUS_INVALID_PARAMETER;
-               goto cleanup;
-       }
-
        status = build_fs_quota_buffer(talloc_tos(), pqt, &inbuf, 0);
        if (!NT_STATUS_IS_OK(status)) {
                goto cleanup;
@@ -4461,11 +4510,6 @@ static struct tevent_req *cli_smb2_shadow_copy_data_fnum_send(
                return NULL;
        }
 
-       if (smbXcli_conn_protocol(cli->conn) < PROTOCOL_SMB2_02) {
-               tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
-               return tevent_req_post(req, ev);
-       }
-
        state->cli = cli;
        state->fnum = fnum;
 
@@ -4678,11 +4722,6 @@ NTSTATUS cli_smb2_ftruncate(struct cli_state *cli,
                goto fail;
        }
 
-       if (smbXcli_conn_protocol(cli->conn) < PROTOCOL_SMB2_02) {
-               status = NT_STATUS_INVALID_PARAMETER;
-               goto fail;
-       }
-
        SBVAL(buf, 0, newsize);
 
        /* setinfo on the handle with info_type SMB2_SETINFO_FILE (1),
@@ -4733,11 +4772,6 @@ struct tevent_req *cli_smb2_notify_send(
                return NULL;
        }
 
-       if (smbXcli_conn_protocol(cli->conn) < PROTOCOL_SMB2_02) {
-               tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
-               return tevent_req_post(req, ev);
-       }
-
        status = map_fnum_to_smb2_handle(cli, fnum, &ph);
        if (tevent_req_nterror(req, status)) {
                return tevent_req_post(req, ev);
@@ -4908,197 +4942,99 @@ fail:
        return status;
 }
 
-struct cli_smb2_set_reparse_point_fnum_state {
-       struct cli_state *cli;
-       uint16_t fnum;
-       struct smb2_hnd *ph;
-       DATA_BLOB input_buffer;
-};
-
-static void cli_smb2_set_reparse_point_fnum_done(struct tevent_req *subreq);
-
-struct tevent_req *cli_smb2_set_reparse_point_fnum_send(
-                               TALLOC_CTX *mem_ctx,
-                               struct tevent_context *ev,
-                               struct cli_state *cli,
-                               uint16_t fnum,
-                               DATA_BLOB in_buf)
-{
-       struct tevent_req *req, *subreq;
-       struct cli_smb2_set_reparse_point_fnum_state *state = NULL;
-       NTSTATUS status;
-
-       req = tevent_req_create(mem_ctx, &state,
-                               struct cli_smb2_set_reparse_point_fnum_state);
-       if (req == NULL) {
-               return NULL;
-       }
-
-       if (smbXcli_conn_protocol(cli->conn) < PROTOCOL_SMB2_02) {
-               tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
-               return tevent_req_post(req, ev);
-       }
-
-       state->cli = cli;
-       state->fnum = fnum;
-
-       status = map_fnum_to_smb2_handle(cli, fnum, &state->ph);
-       if (tevent_req_nterror(req, status)) {
-               return tevent_req_post(req, ev);
-       }
-
-       state->input_buffer = data_blob_talloc(state,
-                                               in_buf.data,
-                                               in_buf.length);
-       if (state->input_buffer.data == NULL) {
-               tevent_req_nterror(req, NT_STATUS_NO_MEMORY);
-               return tevent_req_post(req, ev);
-       }
-
-       subreq = smb2cli_ioctl_send(state, ev, state->cli->conn,
-                       state->cli->timeout,
-                       state->cli->smb2.session,
-                       state->cli->smb2.tcon,
-                       state->ph->fid_persistent, /* in_fid_persistent */
-                       state->ph->fid_volatile, /* in_fid_volatile */
-                       FSCTL_SET_REPARSE_POINT,
-                       0, /* in_max_input_length */
-                       &state->input_buffer ,
-                       0,
-                       NULL,
-                       SMB2_IOCTL_FLAG_IS_FSCTL);
-
-       if (tevent_req_nomem(subreq, req)) {
-               return tevent_req_post(req, ev);
-       }
-       tevent_req_set_callback(subreq,
-                               cli_smb2_set_reparse_point_fnum_done,
-                               req);
-
-       return req;
-}
-
-static void cli_smb2_set_reparse_point_fnum_done(struct tevent_req *subreq)
-{
-       struct tevent_req *req = tevent_req_callback_data(
-               subreq, struct tevent_req);
-       struct cli_smb2_set_reparse_point_fnum_state *state = tevent_req_data(
-               req, struct cli_smb2_set_reparse_point_fnum_state);
-       NTSTATUS status;
-
-       status = smb2cli_ioctl_recv(subreq, state,
-                               NULL,
-                               NULL);
-       TALLOC_FREE(subreq);
-       if (tevent_req_nterror(req, status)) {
-               return;
-       }
-       tevent_req_done(req);
-}
-
-NTSTATUS cli_smb2_set_reparse_point_fnum_recv(struct tevent_req *req)
-{
-        return tevent_req_simple_recv_ntstatus(req);
-}
-
-struct cli_smb2_get_reparse_point_fnum_state {
-       struct cli_state *cli;
-       uint16_t fnum;
-       struct smb2_hnd *ph;
-       DATA_BLOB output_buffer;
+struct cli_smb2_fsctl_state {
+       DATA_BLOB out;
 };
 
-static void cli_smb2_get_reparse_point_fnum_done(struct tevent_req *subreq);
+static void cli_smb2_fsctl_done(struct tevent_req *subreq);
 
-struct tevent_req *cli_smb2_get_reparse_point_fnum_send(
-                               TALLOC_CTX *mem_ctx,
-                               struct tevent_context *ev,
-                               struct cli_state *cli,
-                               uint16_t fnum)
+struct tevent_req *cli_smb2_fsctl_send(
+       TALLOC_CTX *mem_ctx,
+       struct tevent_context *ev,
+       struct cli_state *cli,
+       uint16_t fnum,
+       uint32_t ctl_code,
+       const DATA_BLOB *in,
+       uint32_t max_out)
 {
-       struct tevent_req *req, *subreq;
-       struct cli_smb2_get_reparse_point_fnum_state *state = NULL;
+       struct tevent_req *req = NULL, *subreq = NULL;
+       struct cli_smb2_fsctl_state *state = NULL;
+       struct smb2_hnd *ph = NULL;
        NTSTATUS status;
 
-       req = tevent_req_create(mem_ctx, &state,
-                               struct cli_smb2_get_reparse_point_fnum_state);
+       req = tevent_req_create(mem_ctx, &state, struct cli_smb2_fsctl_state);
        if (req == NULL) {
                return NULL;
        }
 
-       if (smbXcli_conn_protocol(cli->conn) < PROTOCOL_SMB2_02) {
-               tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
-               return tevent_req_post(req, ev);
-       }
-
-       state->cli = cli;
-       state->fnum = fnum;
-
-       status = map_fnum_to_smb2_handle(cli, fnum, &state->ph);
+       status = map_fnum_to_smb2_handle(cli, fnum, &ph);
        if (tevent_req_nterror(req, status)) {
                return tevent_req_post(req, ev);
        }
 
-       subreq = smb2cli_ioctl_send(state, ev, state->cli->conn,
-                       state->cli->timeout,
-                       state->cli->smb2.session,
-                       state->cli->smb2.tcon,
-                       state->ph->fid_persistent, /* in_fid_persistent */
-                       state->ph->fid_volatile, /* in_fid_volatile */
-                       FSCTL_GET_REPARSE_POINT,
-                       0, /* in_max_input_length */
-                       NULL,
-                       64*1024,
-                       NULL,
-                       SMB2_IOCTL_FLAG_IS_FSCTL);
+       subreq = smb2cli_ioctl_send(
+               state,
+               ev,
+               cli->conn,
+               cli->timeout,
+               cli->smb2.session,
+               cli->smb2.tcon,
+               ph->fid_persistent,
+               ph->fid_volatile,
+               ctl_code,
+               0, /* in_max_input_length */
+               in,
+               max_out,
+               NULL,
+               SMB2_IOCTL_FLAG_IS_FSCTL);
 
        if (tevent_req_nomem(subreq, req)) {
                return tevent_req_post(req, ev);
        }
-       tevent_req_set_callback(subreq,
-                               cli_smb2_get_reparse_point_fnum_done,
-                               req);
-
+       tevent_req_set_callback(subreq, cli_smb2_fsctl_done, req);
        return req;
 }
 
-static void cli_smb2_get_reparse_point_fnum_done(struct tevent_req *subreq)
+static void cli_smb2_fsctl_done(struct tevent_req *subreq)
 {
        struct tevent_req *req = tevent_req_callback_data(
                subreq, struct tevent_req);
-       struct cli_smb2_get_reparse_point_fnum_state *state = tevent_req_data(
-               req, struct cli_smb2_get_reparse_point_fnum_state);
-       struct cli_state *cli = state->cli;
+       struct cli_smb2_fsctl_state *state = tevent_req_data(
+               req, struct cli_smb2_fsctl_state);
        NTSTATUS status;
 
-       status = smb2cli_ioctl_recv(subreq, state,
-                               NULL,
-                               &state->output_buffer);
-       TALLOC_FREE(subreq);
-       if (tevent_req_nterror(req, status)) {
-               cli->raw_status = status;
-               return;
-       }
-       tevent_req_done(req);
+       status = smb2cli_ioctl_recv(subreq, state, NULL, &state->out);
+       tevent_req_simple_finish_ntstatus(subreq, status);
 }
 
-NTSTATUS cli_smb2_get_reparse_point_fnum_recv(struct tevent_req *req,
-                               TALLOC_CTX *mem_ctx,
-                               DATA_BLOB *output)
+NTSTATUS cli_smb2_fsctl_recv(
+       struct tevent_req *req, TALLOC_CTX *mem_ctx, DATA_BLOB *out)
 {
-       struct cli_smb2_get_reparse_point_fnum_state *state = tevent_req_data(
-               req, struct cli_smb2_get_reparse_point_fnum_state);
+       struct cli_smb2_fsctl_state *state = tevent_req_data(
+               req, struct cli_smb2_fsctl_state);
+       NTSTATUS status = NT_STATUS_OK;
 
-       if (tevent_req_is_nterror(req, &state->cli->raw_status)) {
-               NTSTATUS status = state->cli->raw_status;
+       if (tevent_req_is_nterror(req, &status)) {
                tevent_req_received(req);
                return status;
        }
-       *output = data_blob_dup_talloc(mem_ctx, state->output_buffer);
-       if (output->data == NULL) {
-               tevent_req_received(req);
-               return NT_STATUS_NO_MEMORY;
+
+       if (state->out.length == 0) {
+               *out = (DATA_BLOB) { .data = NULL, };
+       } else {
+               /*
+                * Can't use talloc_move() here, the outblobs from
+                * smb2cli_ioctl_recv() are not standalone talloc
+                * objects but just peek into the larger buffers
+                * received, hanging off "state".
+                */
+               *out = data_blob_talloc(
+                       mem_ctx, state->out.data, state->out.length);
+               if (out->data == NULL) {
+                       status = NT_STATUS_NO_MEMORY;
+               }
        }
+
        tevent_req_received(req);
        return NT_STATUS_OK;
 }